diff --git a/src/formats/tiff/tiff-image.ts b/src/formats/tiff/tiff-image.ts index c7afea5..4e8b9e6 100644 --- a/src/formats/tiff/tiff-image.ts +++ b/src/formats/tiff/tiff-image.ts @@ -986,6 +986,7 @@ export class TiffImage { format: format, numChannels: numChannels, withPalette: hasPalette, + paletteFormat: format, }); if (hasPalette) { @@ -994,13 +995,15 @@ export class TiffImage { const numChannels = 3; // Only support RGB palettes const numColors = Math.trunc(cm.length / numChannels); - for (let i = 0; i < numColors; ++i) { - p.setRgb( - i, - cm[this._colorMapRed + i], - cm[this._colorMapGreen + i], - cm[this._colorMapBlue + i] - ); + let ri = this._colorMapRed; + let gi = this._colorMapGreen; + let bi = this._colorMapBlue; + const colorMapSize = cm.length; + for (let i = 0; i < numColors; ++i, ++ri, ++gi, ++bi) { + if (bi >= colorMapSize) { + break; + } + p.setRgb(i, cm[ri], cm[gi], cm[bi]); } } diff --git a/src/image/image-data-uint16.ts b/src/image/image-data-uint16.ts index 16940af..62b0342 100644 --- a/src/image/image-data-uint16.ts +++ b/src/image/image-data-uint16.ts @@ -63,7 +63,7 @@ export class MemoryImageDataUint16 implements MemoryImageData, Iterable { } public get maxChannelValue(): number { - return 0xffff; + return this._palette?.maxChannelValue ?? 0xffff; } public get maxIndexValue(): number { @@ -74,8 +74,9 @@ export class MemoryImageDataUint16 implements MemoryImageData, Iterable { return this.palette !== undefined; } + private _palette: Palette | undefined; public get palette(): Palette | undefined { - return undefined; + return this._palette; } public get isHdrFormat(): boolean { @@ -103,6 +104,13 @@ export class MemoryImageDataUint16 implements MemoryImageData, Iterable { data ?? new Uint16Array(this._width * this._height * this._numChannels); } + public static palette(width: number, height: number, palette?: Palette) { + const data = new Uint16Array(width * height); + const d = new MemoryImageDataUint16(width, height, 1, data); + d._palette = palette; + return d; + } + public static from( other: MemoryImageDataUint16, skipPixels = false @@ -110,12 +118,14 @@ export class MemoryImageDataUint16 implements MemoryImageData, Iterable { const data = skipPixels ? new Uint16Array(other.data.length) : other.data.slice(); - return new MemoryImageDataUint16( + const d = new MemoryImageDataUint16( other.width, other.height, other._numChannels, data ); + d._palette = other.palette?.clone(); + return d; } public getRange( diff --git a/src/image/image.ts b/src/image/image.ts index c060167..22134a4 100644 --- a/src/image/image.ts +++ b/src/image/image.ts @@ -103,18 +103,6 @@ export class MemoryImage implements Iterable { return this._data; } - private get numPixelColors(): number { - return this.format === Format.uint1 - ? 2 - : this.format === Format.uint2 - ? 4 - : this.format === Format.uint4 - ? 16 - : this.format === Format.uint8 - ? 256 - : 0; - } - /** * The format of the image pixels. */ @@ -206,7 +194,8 @@ export class MemoryImage implements Iterable { this.format === Format.uint1 || this.format === Format.uint2 || this.format === Format.uint4 || - this.format === Format.uint8 + this.format === Format.uint8 || + this.format === Format.uint16 ); } @@ -405,6 +394,20 @@ export class MemoryImage implements Iterable { } } + private static getNumPixelColors(format: Format): number { + return format === Format.uint1 + ? 2 + : format === Format.uint2 + ? 4 + : format === Format.uint4 + ? 16 + : format === Format.uint8 + ? 256 + : format === Format.uint16 + ? 65536 + : 0; + } + public static fromResized( other: MemoryImage, width: number, @@ -666,7 +669,7 @@ export class MemoryImage implements Iterable { const palette = opt.palette ?? (withPalette && this.supportsPalette - ? this.createPalette(paletteFormat, numChannels) + ? this.createPalette(format, paletteFormat, numChannels) : undefined); this.createImageData(opt.width, opt.height, format, numChannels, palette); @@ -709,7 +712,11 @@ export class MemoryImage implements Iterable { } break; case Format.uint16: - this._data = new MemoryImageDataUint16(width, height, numChannels); + if (palette === undefined) { + this._data = new MemoryImageDataUint16(width, height, numChannels); + } else { + this._data = MemoryImageDataUint16.palette(width, height, palette); + } break; case Format.uint32: this._data = new MemoryImageDataUint32(width, height, numChannels); @@ -736,6 +743,7 @@ export class MemoryImage implements Iterable { } private createPalette( + format: Format, paletteFormat: Format, numChannels: number ): Palette | undefined { @@ -747,23 +755,50 @@ export class MemoryImage implements Iterable { case Format.uint4: return undefined; case Format.uint8: - return new PaletteUint8(this.numPixelColors, numChannels); + return new PaletteUint8( + MemoryImage.getNumPixelColors(format), + numChannels + ); case Format.uint16: - return new PaletteUint16(this.numPixelColors, numChannels); + return new PaletteUint16( + MemoryImage.getNumPixelColors(format), + numChannels + ); case Format.uint32: - return new PaletteUint32(this.numPixelColors, numChannels); + return new PaletteUint32( + MemoryImage.getNumPixelColors(format), + numChannels + ); case Format.int8: - return new PaletteInt8(this.numPixelColors, numChannels); + return new PaletteInt8( + MemoryImage.getNumPixelColors(format), + numChannels + ); case Format.int16: - return new PaletteInt16(this.numPixelColors, numChannels); + return new PaletteInt16( + MemoryImage.getNumPixelColors(format), + numChannels + ); case Format.int32: - return new PaletteInt32(this.numPixelColors, numChannels); + return new PaletteInt32( + MemoryImage.getNumPixelColors(format), + numChannels + ); case Format.float16: - return new PaletteFloat16(this.numPixelColors, numChannels); + return new PaletteFloat16( + MemoryImage.getNumPixelColors(format), + numChannels + ); case Format.float32: - return new PaletteFloat32(this.numPixelColors, numChannels); + return new PaletteFloat32( + MemoryImage.getNumPixelColors(format), + numChannels + ); case Format.float64: - return new PaletteFloat64(this.numPixelColors, numChannels); + return new PaletteFloat64( + MemoryImage.getNumPixelColors(format), + numChannels + ); } throw new LibError('Unknown palette format.'); } diff --git a/src/image/pixel-uint16.ts b/src/image/pixel-uint16.ts index 5736ef7..7263871 100644 --- a/src/image/pixel-uint16.ts +++ b/src/image/pixel-uint16.ts @@ -65,7 +65,7 @@ export class PixelUint16 implements Pixel, Iterable, Iterator { } public get length(): number { - return this._image.numChannels; + return this.palette?.numChannels ?? this._image.numChannels; } public get numChannels(): number { @@ -97,11 +97,15 @@ export class PixelUint16 implements Pixel, Iterable, Iterator { } public get palette(): Palette | undefined { - return undefined; + return this._image.palette; } public get r(): number { - return this.numChannels > 0 ? this.data[this._index] : 0; + return this.palette === undefined + ? this.numChannels > 0 + ? this.data[this._index] + : 0 + : this.palette.getRed(this.data[this._index]); } public set r(r: number) { if (this.numChannels > 0) { @@ -110,7 +114,11 @@ export class PixelUint16 implements Pixel, Iterable, Iterator { } public get g(): number { - return this.numChannels > 1 ? this.data[this._index + 1] : 0; + return this.palette === undefined + ? this.numChannels > 1 + ? this.data[this._index + 1] + : 0 + : this.palette.getGreen(this.data[this._index]); } public set g(g: number) { if (this.numChannels > 1) { @@ -119,7 +127,11 @@ export class PixelUint16 implements Pixel, Iterable, Iterator { } public get b(): number { - return this.numChannels > 2 ? this.data[this._index + 2] : 0; + return this.palette === undefined + ? this.numChannels > 2 + ? this.data[this._index + 2] + : 0 + : this.palette.getBlue(this.data[this._index]); } public set b(b: number) { if (this.numChannels > 2) { @@ -128,7 +140,11 @@ export class PixelUint16 implements Pixel, Iterable, Iterator { } public get a(): number { - return this.numChannels > 3 ? this.data[this._index + 3] : 0; + return this.palette === undefined + ? this.numChannels > 3 + ? this.data[this._index + 3] + : 0 + : this.palette.getAlpha(this.data[this._index]); } public set a(a: number) { if (this.numChannels > 3) { @@ -215,7 +231,7 @@ export class PixelUint16 implements Pixel, Iterable, Iterator { }; } } - this._index += this.numChannels; + this._index += this.palette === undefined ? this.numChannels : 1; return >{ done: this._index >= this.image.data.length, value: this, @@ -238,10 +254,16 @@ export class PixelUint16 implements Pixel, Iterable, Iterator { } public getChannel(channel: number | Channel): number { - if (channel === Channel.luminance) { - return this.luminance; + if (this.palette !== undefined) { + return this.palette.get(this.data[this._index], channel); } else { - return channel < this.numChannels ? this.data[this._index + channel] : 0; + if (channel === Channel.luminance) { + return this.luminance; + } else { + return channel < this.numChannels + ? this.data[this._index + channel] + : 0; + } } } diff --git a/src/image/pixel-uint8.ts b/src/image/pixel-uint8.ts index c5404ed..ad259bb 100644 --- a/src/image/pixel-uint8.ts +++ b/src/image/pixel-uint8.ts @@ -275,7 +275,7 @@ export class PixelUint8 implements Pixel, Iterable, Iterator { if (channel === Channel.luminance) { return this.luminance; } else { - return channel < this.data.length + return channel < this.numChannels ? this.data[this._index + channel] : 0; } diff --git a/test/_input/tiff/CNSW_crop.tif b/test/_input/tiff/CNSW_crop.tif new file mode 100644 index 0000000..5dae7d4 Binary files /dev/null and b/test/_input/tiff/CNSW_crop.tif differ diff --git a/test/format/format.tiff.test.ts b/test/format/format.tiff.test.ts index 4b126c2..6baa078 100644 --- a/test/format/format.tiff.test.ts +++ b/test/format/format.tiff.test.ts @@ -37,6 +37,35 @@ type TiffFileInfo = { }; describe('Format: TIFF', () => { + test('16bit colormap', () => { + const input = TestUtils.readFromFile( + TestFolder.input, + TestSection.tiff, + 'CNSW_crop.tif' + ); + const tiff = decodeTiff({ + data: input, + }); + expect(tiff).toBeDefined(); + if (tiff === undefined) { + return; + } + + const conv = tiff.convert({ + format: Format.uint8, + }); + + const output = encodePng({ + image: conv, + }); + TestUtils.writeToFile( + TestFolder.output, + TestSection.tiff, + 'CNSW_crop.png', + output + ); + }); + test('encode', () => { const i0 = new MemoryImage({ width: 256,