You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
102 lines
3.4 KiB
102 lines
3.4 KiB
// ---------------------------------------------------------------------------
|
|
// Pixel pack/unpack helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Pack 1-byte-per-pixel array into 1bpp.
|
|
* Excess bits in the last byte are set to 0.
|
|
* Size = ceil(width * height / 8).
|
|
*/
|
|
export function packPixels(pixels: Uint8Array, width: number, height: number): Uint8Array {
|
|
const total = width * height;
|
|
const packed = new Uint8Array((total + 7) >> 3);
|
|
for (let i = 0; i < total; i++) {
|
|
if (pixels[i]) packed[i >> 3] = (packed[i >> 3] ?? 0) | (1 << (i & 7));
|
|
}
|
|
return packed;
|
|
}
|
|
|
|
/**
|
|
* Unpack 1bpp data to 1-byte-per-pixel. Excess bits beyond width*height are ignored.
|
|
*/
|
|
export function unpackPixels(packed: Uint8Array, width: number, height: number): Uint8Array {
|
|
const total = width * height;
|
|
const pixels = new Uint8Array(total);
|
|
for (let i = 0; i < total; i++) {
|
|
pixels[i] = ((packed[i >> 3] ?? 0) >> (i & 7)) & 1;
|
|
}
|
|
return pixels;
|
|
}
|
|
|
|
/** Byte count of packed 1bpp image */
|
|
export function packedSize(width: number, height: number): number {
|
|
return (width * height + 7) >> 3;
|
|
}
|
|
|
|
/** Padding needed to align `byteCount` to a 4-byte boundary */
|
|
export function pad32(byteCount: number): number {
|
|
return (4 - (byteCount & 3)) & 3;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// BinaryReader — little-endian cursor over an ArrayBuffer
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export class BinaryReader {
|
|
private view: DataView;
|
|
private pos: number = 0;
|
|
|
|
constructor(buffer: ArrayBuffer | ArrayBufferView) {
|
|
this.view = buffer instanceof ArrayBuffer
|
|
? new DataView(buffer)
|
|
: new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
}
|
|
|
|
get offset(): number { return this.pos; }
|
|
get byteLength(): number { return this.view.byteLength; }
|
|
get remaining(): number { return this.view.byteLength - this.pos; }
|
|
|
|
readByte(): number { this.#need(1); return this.view.getUint8(this.pos++); }
|
|
readUint16(): number { this.#need(2); const v = this.view.getUint16(this.pos, true); this.pos += 2; return v; }
|
|
readShort(): number { return this.readUint16(); }
|
|
readInt16(): number { this.#need(2); const v = this.view.getInt16(this.pos, true); this.pos += 2; return v; }
|
|
|
|
readUint24(): number {
|
|
this.#need(3);
|
|
const v = this.view.getUint8(this.pos) | (this.view.getUint8(this.pos+1) << 8) | (this.view.getUint8(this.pos+2) << 16);
|
|
this.pos += 3;
|
|
return v;
|
|
}
|
|
|
|
readUint32(): number { this.#need(4); const v = this.view.getUint32(this.pos, true); this.pos += 4; return v; }
|
|
|
|
readUint64(): bigint {
|
|
this.#need(8);
|
|
const lo = BigInt(this.view.getUint32(this.pos, true));
|
|
const hi = BigInt(this.view.getUint32(this.pos+4, true));
|
|
this.pos += 8;
|
|
return (hi << 32n) | lo;
|
|
}
|
|
|
|
readBytes(n: number): Uint8Array {
|
|
this.#need(n);
|
|
const s = new Uint8Array(this.view.buffer, this.pos, n);
|
|
this.pos += n;
|
|
return s;
|
|
}
|
|
|
|
readUtf8(n: number): string { return new TextDecoder().decode(this.readBytes(n)); }
|
|
|
|
seek(pos: number): void {
|
|
if (pos < 0 || pos > this.view.byteLength) {
|
|
throw new RangeError(`seek(${pos}) out of [0, ${this.view.byteLength}]`);
|
|
}
|
|
this.pos = pos;
|
|
}
|
|
|
|
#need(n: number): void {
|
|
if (this.remaining < n) throw new RangeError(
|
|
`BinaryReader: need ${n} byte(s) at offset ${this.pos}, ${this.remaining} remain`
|
|
);
|
|
}
|
|
}
|
|
|