tiff
Version:
TIFF image decoder written entirely in JavaScript
143 lines (124 loc) • 4.07 kB
text/typescript
import { IOBuffer } from 'iobuffer';
const CLEAR_CODE = 256;
const EOI_CODE = 257;
// 0-255 from the table + 256 for clear code + 257 for end of information code.
const TABLE_START = 258;
const MIN_BIT_LENGTH = 9;
let stringTable: number[][] = [];
function initializeStringTable() {
if (stringTable.length === 0) {
for (let i = 0; i < 256; i++) {
stringTable.push([i]);
}
// Fill the table with dummy data.
// Elements at indices > 257 will be replaced during decompression.
const dummyString: number[] = [];
for (let i = 256; i < 4096; i++) {
stringTable.push(dummyString);
}
}
}
const andTable = [511, 1023, 2047, 4095];
const bitJumps = [0, 0, 0, 0, 0, 0, 0, 0, 0, 511, 1023, 2047, 4095];
class LzwDecoder {
private stripArray: Uint8Array;
private nextData = 0;
private nextBits = 0;
private bytePointer = 0;
private tableLength = TABLE_START;
private currentBitLength = MIN_BIT_LENGTH;
private outData: IOBuffer;
public constructor(data: DataView) {
this.stripArray = new Uint8Array(
data.buffer,
data.byteOffset,
data.byteLength,
);
this.outData = new IOBuffer(data.byteLength);
this.initializeTable();
}
public decode(): DataView {
let code = 0;
let oldCode = 0;
while ((code = this.getNextCode()) !== EOI_CODE) {
if (code === CLEAR_CODE) {
this.initializeTable();
code = this.getNextCode();
if (code === EOI_CODE) {
break;
}
this.writeString(this.stringFromCode(code));
oldCode = code;
} else if (this.isInTable(code)) {
this.writeString(this.stringFromCode(code));
this.addStringToTable(
this.stringFromCode(oldCode).concat(this.stringFromCode(code)[0]),
);
oldCode = code;
} else {
const outString = this.stringFromCode(oldCode).concat(
this.stringFromCode(oldCode)[0],
);
this.writeString(outString);
this.addStringToTable(outString);
oldCode = code;
}
}
const outArray = this.outData.toArray();
return new DataView(
outArray.buffer,
outArray.byteOffset,
outArray.byteLength,
);
}
private initializeTable(): void {
initializeStringTable();
this.tableLength = TABLE_START;
this.currentBitLength = MIN_BIT_LENGTH;
}
private writeString(string: number[]): void {
this.outData.writeBytes(string);
}
private stringFromCode(code: number): number[] {
// At this point, `code` must be defined in the table.
return stringTable[code];
}
private isInTable(code: number): boolean {
return code < this.tableLength;
}
private addStringToTable(string: number[]): void {
stringTable[this.tableLength++] = string;
if (stringTable.length > 4096) {
stringTable = [];
throw new Error(
'LZW decoding error. Please open an issue at https://github.com/image-js/tiff/issues/new/choose (include a test image).',
);
}
if (this.tableLength === bitJumps[this.currentBitLength]) {
this.currentBitLength++;
}
}
private getNextCode(): number {
this.nextData =
(this.nextData << 8) | (this.stripArray[this.bytePointer++] & 0xff);
this.nextBits += 8;
if (this.nextBits < this.currentBitLength) {
this.nextData =
(this.nextData << 8) | (this.stripArray[this.bytePointer++] & 0xff);
this.nextBits += 8;
}
const code =
(this.nextData >> (this.nextBits - this.currentBitLength)) &
andTable[this.currentBitLength - 9];
this.nextBits -= this.currentBitLength;
// This should not really happen but is present in other codes as well.
// See: https://github.com/sugark/Tiffus/blob/15a60123813d1612f4ae9e4fab964f9f7d71cf63/src/org/eclipse/swt/internal/image/TIFFLZWDecoder.java
if (this.bytePointer > this.stripArray.length) {
return 257;
}
return code;
}
}
export function decompressLzw(stripData: DataView): DataView {
return new LzwDecoder(stripData).decode();
}