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; }({}));