tiff
Version:
TIFF image decoder written entirely in JavaScript
176 lines (170 loc) • 5.01 kB
text/typescript
import Ifd from './ifd.ts';
// eslint-disable-next-line prefer-named-capture-group
const dateTimeRegex = /^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/;
export default class TiffIfd extends Ifd {
public constructor() {
super('standard');
}
// Custom fields
public get size(): number {
return this.width * this.height;
}
public get width(): number {
return this.imageWidth;
}
public get height(): number {
return this.imageLength;
}
public get components(): number {
return this.samplesPerPixel;
}
public get date(): Date {
const date = new Date();
const result = dateTimeRegex.exec(this.dateTime);
if (result === null) {
throw new Error(`invalid dateTime: ${this.dateTime}`);
}
date.setFullYear(
Number(result[1]),
Number(result[2]) - 1,
Number(result[3]),
);
date.setHours(Number(result[4]), Number(result[5]), Number(result[6]));
return date;
}
// IFD fields
public get newSubfileType(): number {
return this.get('NewSubfileType');
}
public get imageWidth(): number {
return this.get('ImageWidth');
}
public get imageLength(): number {
return this.get('ImageLength');
}
public get bitsPerSample(): number {
const data = this.get('BitsPerSample');
if (data && typeof data !== 'number') {
return data[0];
}
return data;
}
public get alpha(): boolean {
const extraSamples = this.extraSamples;
if (!extraSamples) return false;
return extraSamples[0] !== 0;
}
public get associatedAlpha(): boolean {
const extraSamples = this.extraSamples;
if (!extraSamples) return false;
return extraSamples[0] === 1;
}
public get extraSamples(): number[] | undefined {
return alwaysArray(this.get('ExtraSamples'));
}
public get compression(): number {
return this.get('Compression') || 1;
}
public get type(): number {
return this.get('PhotometricInterpretation');
}
public get fillOrder(): number {
return this.get('FillOrder') || 1;
}
public get documentName(): string | undefined {
return this.get('DocumentName');
}
public get imageDescription(): string | undefined {
return this.get('ImageDescription');
}
public get stripOffsets(): number[] {
return alwaysArray(this.get('StripOffsets'));
}
public get orientation(): number {
return this.get('Orientation');
}
public get samplesPerPixel(): number {
return this.get('SamplesPerPixel') || 1;
}
public get rowsPerStrip(): number {
return this.get('RowsPerStrip') || 2 ** 32 - 1;
}
public get stripByteCounts(): number[] {
return alwaysArray(this.get('StripByteCounts'));
}
public get minSampleValue(): number {
return this.get('MinSampleValue') || 0;
}
public get maxSampleValue(): number {
return this.get('MaxSampleValue') || 2 ** this.bitsPerSample - 1;
}
public get xResolution(): number {
return this.get('XResolution');
}
public get yResolution(): number {
return this.get('YResolution');
}
public get planarConfiguration(): number {
return this.get('PlanarConfiguration') || 1;
}
public get resolutionUnit(): number {
return this.get('ResolutionUnit') || 2;
}
public get dateTime(): string {
return this.get('DateTime');
}
public get predictor(): number {
return this.get('Predictor') || 1;
}
public get sampleFormat(): number {
const data = alwaysArray(this.get('SampleFormat') || 1);
return data[0];
}
public get sMinSampleValue(): number {
return this.get('SMinSampleValue') || this.minSampleValue;
}
public get sMaxSampleValue(): number {
return this.get('SMaxSampleValue') || this.maxSampleValue;
}
public get palette(): Array<[number, number, number]> | undefined {
const totalColors = 2 ** this.bitsPerSample;
const colorMap: number[] = this.get('ColorMap');
if (!colorMap) return undefined;
if (colorMap.length !== 3 * totalColors) {
throw new Error(`ColorMap size must be ${totalColors}`);
}
const palette: Array<[number, number, number]> = [];
for (let i = 0; i < totalColors; i++) {
palette.push([
colorMap[i],
colorMap[i + totalColors],
colorMap[i + 2 * totalColors],
]);
}
return palette;
}
public get tileWidth(): number | undefined {
return this.get('TileWidth');
}
public get tileHeight(): number | undefined {
return this.get('TileLength');
}
public get tileOffsets(): number[] {
return alwaysArray(this.get('TileOffsets'));
}
public get tileByteCounts(): number[] {
return alwaysArray(this.get('TileByteCounts'));
}
public get tiled(): boolean {
return (
this.tileWidth !== undefined &&
this.tileHeight !== undefined &&
this.tileOffsets !== undefined &&
this.tileByteCounts !== undefined
);
}
}
function alwaysArray(value: number | number[]): number[] {
if (typeof value === 'number') return [value];
return value;
}