Skip to content

Commit

Permalink
feat: Add support for uint16 palette images
Browse files Browse the repository at this point in the history
  • Loading branch information
yegor-pelykh committed Apr 12, 2024
1 parent 5bfdca1 commit 63b9caf
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 45 deletions.
17 changes: 10 additions & 7 deletions src/formats/tiff/tiff-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,7 @@ export class TiffImage {
format: format,
numChannels: numChannels,
withPalette: hasPalette,
paletteFormat: format,
});

if (hasPalette) {
Expand All @@ -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]);
}
}

Expand Down
16 changes: 13 additions & 3 deletions src/image/image-data-uint16.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class MemoryImageDataUint16 implements MemoryImageData, Iterable<Pixel> {
}

public get maxChannelValue(): number {
return 0xffff;
return this._palette?.maxChannelValue ?? 0xffff;
}

public get maxIndexValue(): number {
Expand All @@ -74,8 +74,9 @@ export class MemoryImageDataUint16 implements MemoryImageData, Iterable<Pixel> {
return this.palette !== undefined;
}

private _palette: Palette | undefined;
public get palette(): Palette | undefined {
return undefined;
return this._palette;
}

public get isHdrFormat(): boolean {
Expand Down Expand Up @@ -103,19 +104,28 @@ export class MemoryImageDataUint16 implements MemoryImageData, Iterable<Pixel> {
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
): MemoryImageDataUint16 {
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(
Expand Down
83 changes: 59 additions & 24 deletions src/image/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,18 +103,6 @@ export class MemoryImage implements Iterable<Pixel> {
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.
*/
Expand Down Expand Up @@ -206,7 +194,8 @@ export class MemoryImage implements Iterable<Pixel> {
this.format === Format.uint1 ||
this.format === Format.uint2 ||
this.format === Format.uint4 ||
this.format === Format.uint8
this.format === Format.uint8 ||
this.format === Format.uint16
);
}

Expand Down Expand Up @@ -405,6 +394,20 @@ export class MemoryImage implements Iterable<Pixel> {
}
}

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,
Expand Down Expand Up @@ -666,7 +669,7 @@ export class MemoryImage implements Iterable<Pixel> {
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);
Expand Down Expand Up @@ -709,7 +712,11 @@ export class MemoryImage implements Iterable<Pixel> {
}
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);
Expand All @@ -736,6 +743,7 @@ export class MemoryImage implements Iterable<Pixel> {
}

private createPalette(
format: Format,
paletteFormat: Format,
numChannels: number
): Palette | undefined {
Expand All @@ -747,23 +755,50 @@ export class MemoryImage implements Iterable<Pixel> {
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.');
}
Expand Down
42 changes: 32 additions & 10 deletions src/image/pixel-uint16.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export class PixelUint16 implements Pixel, Iterable<Pixel>, Iterator<Pixel> {
}

public get length(): number {
return this._image.numChannels;
return this.palette?.numChannels ?? this._image.numChannels;
}

public get numChannels(): number {
Expand Down Expand Up @@ -97,11 +97,15 @@ export class PixelUint16 implements Pixel, Iterable<Pixel>, Iterator<Pixel> {
}

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) {
Expand All @@ -110,7 +114,11 @@ export class PixelUint16 implements Pixel, Iterable<Pixel>, Iterator<Pixel> {
}

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) {
Expand All @@ -119,7 +127,11 @@ export class PixelUint16 implements Pixel, Iterable<Pixel>, Iterator<Pixel> {
}

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) {
Expand All @@ -128,7 +140,11 @@ export class PixelUint16 implements Pixel, Iterable<Pixel>, Iterator<Pixel> {
}

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) {
Expand Down Expand Up @@ -215,7 +231,7 @@ export class PixelUint16 implements Pixel, Iterable<Pixel>, Iterator<Pixel> {
};
}
}
this._index += this.numChannels;
this._index += this.palette === undefined ? this.numChannels : 1;
return <IteratorResult<Pixel>>{
done: this._index >= this.image.data.length,
value: this,
Expand All @@ -238,10 +254,16 @@ export class PixelUint16 implements Pixel, Iterable<Pixel>, Iterator<Pixel> {
}

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;
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/image/pixel-uint8.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ export class PixelUint8 implements Pixel, Iterable<Pixel>, Iterator<Pixel> {
if (channel === Channel.luminance) {
return this.luminance;
} else {
return channel < this.data.length
return channel < this.numChannels
? this.data[this._index + channel]
: 0;
}
Expand Down
Binary file added test/_input/tiff/CNSW_crop.tif
Binary file not shown.
29 changes: 29 additions & 0 deletions test/format/format.tiff.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 63b9caf

Please sign in to comment.