| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365 |
- import {Enum} from "../Enum.js";
- var GeoTIFF = (function (exports) {
- 'use strict';
- const Endianness = new Enum({
- LITTLE: "II",
- BIG: "MM",
- });
- const Type = new Enum({
- BYTE: {value: 1, bytes: 1},
- ASCII: {value: 2, bytes: 1},
- SHORT: {value: 3, bytes: 2},
- LONG: {value: 4, bytes: 4},
- RATIONAL: {value: 5, bytes: 8},
- SBYTE: {value: 6, bytes: 1},
- UNDEFINED: {value: 7, bytes: 1},
- SSHORT: {value: 8, bytes: 2},
- SLONG: {value: 9, bytes: 4},
- SRATIONAL: {value: 10, bytes: 8},
- FLOAT: {value: 11, bytes: 4},
- DOUBLE: {value: 12, bytes: 8},
- });
- const Tag = new Enum({
- IMAGE_WIDTH: 256,
- IMAGE_HEIGHT: 257,
- BITS_PER_SAMPLE: 258,
- COMPRESSION: 259,
- PHOTOMETRIC_INTERPRETATION: 262,
- STRIP_OFFSETS: 273,
- ORIENTATION: 274,
- SAMPLES_PER_PIXEL: 277,
- ROWS_PER_STRIP: 278,
- STRIP_BYTE_COUNTS: 279,
- X_RESOLUTION: 282,
- Y_RESOLUTION: 283,
- PLANAR_CONFIGURATION: 284,
- RESOLUTION_UNIT: 296,
- SOFTWARE: 305,
- COLOR_MAP: 320,
- SAMPLE_FORMAT: 339,
- MODEL_PIXEL_SCALE: 33550, // [GeoTIFF] TYPE: double N: 3
- MODEL_TIEPOINT: 33922, // [GeoTIFF] TYPE: double N: 6 * NUM_TIEPOINTS
- GEO_KEY_DIRECTORY: 34735, // [GeoTIFF] TYPE: short N: >= 4
- GEO_DOUBLE_PARAMS: 34736, // [GeoTIFF] TYPE: short N: variable
- GEO_ASCII_PARAMS: 34737, // [GeoTIFF] TYPE: ascii N: variable
- });
- const typeMapping = new Map([
- [Type.BYTE, Uint8Array],
- [Type.ASCII, Uint8Array],
- [Type.SHORT, Uint16Array],
- [Type.LONG, Uint32Array],
- [Type.RATIONAL, Uint32Array],
- [Type.SBYTE, Int8Array],
- [Type.UNDEFINED, Uint8Array],
- [Type.SSHORT, Int16Array],
- [Type.SLONG, Int32Array],
- [Type.SRATIONAL, Int32Array],
- [Type.FLOAT, Float32Array],
- [Type.DOUBLE, Float64Array],
- ]);
- class IFDEntry{
- constructor(tag, type, count, offset, value){
- this.tag = tag;
- this.type = type;
- this.count = count;
- this.offset = offset;
- this.value = value;
- }
- }
- class Image{
- constructor(){
- this.width = 0;
- this.height = 0;
- this.buffer = null;
- this.metadata = [];
- }
- }
- class Reader{
- constructor(){
- }
- static read(data){
- let endiannessTag = String.fromCharCode(...Array.from(data.slice(0, 2)));
- let endianness = Endianness.fromValue(endiannessTag);
- let tiffCheckTag = data.readUInt8(2);
- if(tiffCheckTag !== 42){
- throw new Error("not a valid tiff file");
- }
- let offsetToFirstIFD = data.readUInt32LE(4);
- console.log("offsetToFirstIFD", offsetToFirstIFD);
- let ifds = [];
- let IFDsRead = false;
- let currentIFDOffset = offsetToFirstIFD;
- let i = 0;
- while(IFDsRead || i < 100){
- console.log("currentIFDOffset", currentIFDOffset);
- let numEntries = data.readUInt16LE(currentIFDOffset);
- let nextIFDOffset = data.readUInt32LE(currentIFDOffset + 2 + numEntries * 12);
- console.log("next offset: ", currentIFDOffset + 2 + numEntries * 12);
- let entryBuffer = data.slice(currentIFDOffset + 2, currentIFDOffset + 2 + 12 * numEntries);
- for(let i = 0; i < numEntries; i++){
- let tag = Tag.fromValue(entryBuffer.readUInt16LE(i * 12));
- let type = Type.fromValue(entryBuffer.readUInt16LE(i * 12 + 2));
- let count = entryBuffer.readUInt32LE(i * 12 + 4);
- let offsetOrValue = entryBuffer.readUInt32LE(i * 12 + 8);
- let valueBytes = type.bytes * count;
- let value;
- if(valueBytes <= 4){
- value = offsetOrValue;
- }else{
- let valueBuffer = new Uint8Array(valueBytes);
- valueBuffer.set(data.slice(offsetOrValue, offsetOrValue + valueBytes));
-
- let ArrayType = typeMapping.get(type);
- value = new ArrayType(valueBuffer.buffer);
- if(type === Type.ASCII){
- value = String.fromCharCode(...value);
- }
- }
- let ifd = new IFDEntry(tag, type, count, offsetOrValue, value);
- ifds.push(ifd);
- }
- console.log("nextIFDOffset", nextIFDOffset);
- if(nextIFDOffset === 0){
- break;
- }
- currentIFDOffset = nextIFDOffset;
- i++;
- }
- let ifdForTag = (tag) => {
- for(let entry of ifds){
- if(entry.tag === tag){
- return entry;
- }
- }
- return null;
- };
- let width = ifdForTag(Tag.IMAGE_WIDTH, ifds).value;
- let height = ifdForTag(Tag.IMAGE_HEIGHT, ifds).value;
- let compression = ifdForTag(Tag.COMPRESSION, ifds).value;
- let rowsPerStrip = ifdForTag(Tag.ROWS_PER_STRIP, ifds).value;
- let ifdStripOffsets = ifdForTag(Tag.STRIP_OFFSETS, ifds);
- let ifdStripByteCounts = ifdForTag(Tag.STRIP_BYTE_COUNTS, ifds);
- let numStrips = Math.ceil(height / rowsPerStrip);
- let stripByteCounts = [];
- for(let i = 0; i < ifdStripByteCounts.count; i++){
- let type = ifdStripByteCounts.type;
- let offset = ifdStripByteCounts.offset + i * type.bytes;
- let value;
- if(type === Type.SHORT){
- value = data.readUInt16LE(offset);
- }else if(type === Type.LONG){
- value = data.readUInt32LE(offset);
- }
- stripByteCounts.push(value);
- }
- let stripOffsets = [];
- for(let i = 0; i < ifdStripOffsets.count; i++){
- let type = ifdStripOffsets.type;
- let offset = ifdStripOffsets.offset + i * type.bytes;
- let value;
- if(type === Type.SHORT){
- value = data.readUInt16LE(offset);
- }else if(type === Type.LONG){
- value = data.readUInt32LE(offset);
- }
- stripOffsets.push(value);
- }
- let imageBuffer = new Uint8Array(width * height * 3);
-
- let linesProcessed = 0;
- for(let i = 0; i < numStrips; i++){
- let stripOffset = stripOffsets[i];
- let stripBytes = stripByteCounts[i];
- let stripData = data.slice(stripOffset, stripOffset + stripBytes);
- let lineBytes = width * 3;
- for(let y = 0; y < rowsPerStrip; y++){
- let line = stripData.slice(y * lineBytes, y * lineBytes + lineBytes);
- imageBuffer.set(line, linesProcessed * lineBytes);
-
- if(line.length === lineBytes){
- linesProcessed++;
- }else{
- break;
- }
- }
- }
- console.log(`width: ${width}`);
- console.log(`height: ${height}`);
- console.log(`numStrips: ${numStrips}`);
- console.log("stripByteCounts", stripByteCounts.join(", "));
- console.log("stripOffsets", stripOffsets.join(", "));
- let image = new Image();
- image.width = width;
- image.height = height;
- image.buffer = imageBuffer;
- image.metadata = ifds;
- return image;
- }
- }
- class Exporter{
- constructor(){
- }
- static toTiffBuffer(image, params = {}){
- let offsetToFirstIFD = 8;
-
- let headerBuffer = new Uint8Array([0x49, 0x49, 42, 0, offsetToFirstIFD, 0, 0, 0]);
- let [width, height] = [image.width, image.height];
- let ifds = [
- new IFDEntry(Tag.IMAGE_WIDTH, Type.SHORT, 1, null, width),
- new IFDEntry(Tag.IMAGE_HEIGHT, Type.SHORT, 1, null, height),
- new IFDEntry(Tag.BITS_PER_SAMPLE, Type.SHORT, 4, null, new Uint16Array([8, 8, 8, 8])),
- new IFDEntry(Tag.COMPRESSION, Type.SHORT, 1, null, 1),
- new IFDEntry(Tag.PHOTOMETRIC_INTERPRETATION, Type.SHORT, 1, null, 2),
- new IFDEntry(Tag.ORIENTATION, Type.SHORT, 1, null, 1),
- new IFDEntry(Tag.SAMPLES_PER_PIXEL, Type.SHORT, 1, null, 4),
- new IFDEntry(Tag.ROWS_PER_STRIP, Type.LONG, 1, null, height),
- new IFDEntry(Tag.STRIP_BYTE_COUNTS, Type.LONG, 1, null, width * height * 3),
- new IFDEntry(Tag.PLANAR_CONFIGURATION, Type.SHORT, 1, null, 1),
- new IFDEntry(Tag.RESOLUTION_UNIT, Type.SHORT, 1, null, 1),
- new IFDEntry(Tag.SOFTWARE, Type.ASCII, 6, null, "......"),
- new IFDEntry(Tag.STRIP_OFFSETS, Type.LONG, 1, null, null),
- new IFDEntry(Tag.X_RESOLUTION, Type.RATIONAL, 1, null, new Uint32Array([1, 1])),
- new IFDEntry(Tag.Y_RESOLUTION, Type.RATIONAL, 1, null, new Uint32Array([1, 1])),
- ];
- if(params.ifdEntries){
- ifds.push(...params.ifdEntries);
- }
- let valueOffset = offsetToFirstIFD + 2 + ifds.length * 12 + 4;
- // create 12 byte buffer for each ifd and variable length buffers for ifd values
- let ifdEntryBuffers = new Map();
- let ifdValueBuffers = new Map();
- for(let ifd of ifds){
- let entryBuffer = new ArrayBuffer(12);
- let entryView = new DataView(entryBuffer);
- let valueBytes = ifd.type.bytes * ifd.count;
- entryView.setUint16(0, ifd.tag.value, true);
- entryView.setUint16(2, ifd.type.value, true);
- entryView.setUint32(4, ifd.count, true);
- if(ifd.count === 1 && ifd.type.bytes <= 4){
- entryView.setUint32(8, ifd.value, true);
- }else{
- entryView.setUint32(8, valueOffset, true);
- let valueBuffer = new Uint8Array(ifd.count * ifd.type.bytes);
- if(ifd.type === Type.ASCII){
- valueBuffer.set(new Uint8Array(ifd.value.split("").map(c => c.charCodeAt(0))));
- }else{
- valueBuffer.set(new Uint8Array(ifd.value.buffer));
- }
- ifdValueBuffers.set(ifd.tag, valueBuffer);
- valueOffset = valueOffset + valueBuffer.byteLength;
- }
- ifdEntryBuffers.set(ifd.tag, entryBuffer);
- }
- let imageBufferOffset = valueOffset;
- new DataView(ifdEntryBuffers.get(Tag.STRIP_OFFSETS)).setUint32(8, imageBufferOffset, true);
- let concatBuffers = (buffers) => {
- let totalLength = buffers.reduce( (sum, buffer) => (sum + buffer.byteLength), 0);
- let merged = new Uint8Array(totalLength);
- let offset = 0;
- for(let buffer of buffers){
- merged.set(new Uint8Array(buffer), offset);
- offset += buffer.byteLength;
- }
- return merged;
- };
-
- let ifdBuffer = concatBuffers([
- new Uint16Array([ifds.length]),
- ...ifdEntryBuffers.values(),
- new Uint32Array([0])]);
- let ifdValueBuffer = concatBuffers([...ifdValueBuffers.values()]);
- let tiffBuffer = concatBuffers([
- headerBuffer,
- ifdBuffer,
- ifdValueBuffer,
- image.buffer
- ]);
- return {width: width, height: height, buffer: tiffBuffer};
- }
- }
- exports.Tag = Tag;
- exports.Type = Type;
- exports.IFDEntry = IFDEntry;
- exports.Image = Image;
- exports.Reader = Reader;
- exports.Exporter = Exporter;
- return exports;
- }({}));
|