Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
154 lines
4.9 KiB
Plaintext
154 lines
4.9 KiB
Plaintext
// 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 };
|