UNPKG

image-size

Version:
154 lines (149 loc) 5.03 kB
// lib/utils/bit-reader.ts var BitReader = class { constructor(input, endianness) { this.input = input; this.endianness = endianness; // Skip the first 16 bits (2 bytes) of signature this.byteOffset = 2; this.bitOffset = 0; } /** Reads a specified number of bits, and move the offset */ getBits(length = 1) { let result = 0; let bitsRead = 0; while (bitsRead < length) { if (this.byteOffset >= this.input.length) { throw new Error("Reached end of input"); } const currentByte = this.input[this.byteOffset]; const bitsLeft = 8 - this.bitOffset; const bitsToRead = Math.min(length - bitsRead, bitsLeft); if (this.endianness === "little-endian") { const mask = (1 << bitsToRead) - 1; const bits = currentByte >> this.bitOffset & mask; result |= bits << bitsRead; } else { const mask = (1 << bitsToRead) - 1 << 8 - this.bitOffset - bitsToRead; const bits = (currentByte & mask) >> 8 - this.bitOffset - bitsToRead; result = result << bitsToRead | bits; } bitsRead += bitsToRead; this.bitOffset += bitsToRead; if (this.bitOffset === 8) { this.byteOffset++; this.bitOffset = 0; } } return result; } }; // lib/types/utils.ts var decoder = new TextDecoder(); var toUTF8String = (input, start = 0, end = input.length) => decoder.decode(input.slice(start, end)); var toHexString = (input, start = 0, end = input.length) => input.slice(start, end).reduce((memo, i) => memo + `0${i.toString(16)}`.slice(-2), ""); var getView = (input, offset) => new DataView(input.buffer, input.byteOffset + offset); var readUInt32BE = (input, offset = 0) => getView(input, offset).getUint32(0, false); function readBox(input, offset) { if (input.length - offset < 4) return; const boxSize = readUInt32BE(input, offset); if (input.length - offset < boxSize) return; return { name: toUTF8String(input, 4 + offset, 8 + offset), offset, size: boxSize }; } function findBox(input, boxName, currentOffset) { while (currentOffset < input.length) { const box = readBox(input, currentOffset); if (!box) break; if (box.name === boxName) return box; currentOffset += box.size > 0 ? box.size : 8; } } // lib/types/jxl-stream.ts function calculateImageDimension(reader, isSmallImage) { if (isSmallImage) { return 8 * (1 + reader.getBits(5)); } const sizeClass = reader.getBits(2); const extraBits = [9, 13, 18, 30][sizeClass]; return 1 + reader.getBits(extraBits); } function calculateImageWidth(reader, isSmallImage, widthMode, height) { if (isSmallImage && widthMode === 0) { return 8 * (1 + reader.getBits(5)); } if (widthMode === 0) { return calculateImageDimension(reader, false); } const aspectRatios = [1, 1.2, 4 / 3, 1.5, 16 / 9, 5 / 4, 2]; return Math.floor(height * aspectRatios[widthMode - 1]); } var JXLStream = { validate: (input) => { return toHexString(input, 0, 2) === "ff0a"; }, calculate(input) { const reader = new BitReader(input, "little-endian"); const isSmallImage = reader.getBits(1) === 1; const height = calculateImageDimension(reader, isSmallImage); const widthMode = reader.getBits(3); const width = calculateImageWidth(reader, isSmallImage, widthMode, height); return { width, height }; } }; // lib/types/jxl.ts function extractCodestream(input) { const jxlcBox = findBox(input, "jxlc", 0); if (jxlcBox) { return input.slice(jxlcBox.offset + 8, jxlcBox.offset + jxlcBox.size); } const partialStreams = extractPartialStreams(input); if (partialStreams.length > 0) { return concatenateCodestreams(partialStreams); } return void 0; } function extractPartialStreams(input) { const partialStreams = []; let offset = 0; while (offset < input.length) { const jxlpBox = findBox(input, "jxlp", offset); if (!jxlpBox) break; partialStreams.push( input.slice(jxlpBox.offset + 12, jxlpBox.offset + jxlpBox.size) ); offset = jxlpBox.offset + jxlpBox.size; } return partialStreams; } function concatenateCodestreams(partialCodestreams) { const totalLength = partialCodestreams.reduce( (acc, curr) => acc + curr.length, 0 ); const codestream = new Uint8Array(totalLength); let position = 0; for (const partial of partialCodestreams) { codestream.set(partial, position); position += partial.length; } return codestream; } var JXL = { validate: (input) => { const boxType = toUTF8String(input, 4, 8); if (boxType !== "JXL ") return false; const ftypBox = findBox(input, "ftyp", 0); if (!ftypBox) return false; const brand = toUTF8String(input, ftypBox.offset + 8, ftypBox.offset + 12); return brand === "jxl "; }, calculate(input) { const codestream = extractCodestream(input); if (codestream) return JXLStream.calculate(codestream); throw new Error("No codestream found in JXL container"); } }; export { JXL };