diff --git a/src/__tests__/rgb.test.ts b/src/__tests__/rgb.test.ts new file mode 100644 index 0000000..9b9a2fa --- /dev/null +++ b/src/__tests__/rgb.test.ts @@ -0,0 +1,102 @@ +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; + +import { rgb, IndexedColors, decode, encode, IndexedColorBitDepth } from '..'; + +describe('rgb', () => { + it('1 bit', () => { + const data = new Uint8Array([27]); + const palette: IndexedColors = [ + [0, 0, 1], + [0, 0, 2], + ]; + + const view = rgb(data, 1, palette); + assert.strictEqual( + Buffer.from(view).toString('hex'), + '000001000001000001000002000002000001000002000002', + ); + }); + + it('2 bit', () => { + const data = new Uint8Array([27]); + const palette: IndexedColors = [ + [0, 0, 1], + [0, 0, 2], + [0, 0, 3], + [0, 0, 4], + ]; + + const view = rgb(data, 2, palette); + assert.strictEqual( + Buffer.from(view).toString('hex'), + '000001000002000003000004', + ); + }); + + it('4 bit', () => { + const data = new Uint8Array([18, 52, 86, 120]); + const palette: IndexedColors = [ + [0, 0, 0], + [0, 0, 1], + [0, 0, 2], + [0, 0, 3], + [0, 0, 4], + [0, 0, 5], + [0, 0, 6], + [0, 0, 7], + [0, 0, 8], + ]; + + const view = rgb(data, 4, palette); + assert.strictEqual( + Buffer.from(view).toString('hex'), + '000001000002000003000004000005000006000007000008', + ); + }); + + it('8 bit', () => { + const data = new Uint8Array([1, 2, 3, 4]); + const palette: IndexedColors = [ + [0, 0, 0], + [0, 0, 1], + [0, 0, 2], + [0, 0, 3], + [0, 0, 4], + ]; + + const view = rgb(data, 8, palette); + assert.strictEqual( + Buffer.from(view).toString('hex'), + '000001000002000003000004', + ); + }); + + it('convert palette.png tp simple file', () => { + const imgFile = fs.readFileSync( + path.join(__dirname, '../../img/palette.png'), + ); + const img = decode(imgFile); + assert.ok(img.palette); + assert.notStrictEqual(img.depth, 16); + + const data = rgb( + img.data as Uint8Array, + img.depth as IndexedColorBitDepth, + img.palette, + ); + const newImg = encode({ + width: img.width, + height: img.height, + channels: 3, + depth: 8, + data, + }); + // Uncomment the next line for manual testing + // fs.writeFileSync(path.join(__dirname, "../../img/palette.new.png"), newImg, { flag: "w+" }); + + const newImageParsed = decode(newImg); + assert.strictEqual(newImageParsed.data.byteLength, 90000); + }); +}); diff --git a/src/index.ts b/src/index.ts index 580c0f3..759ed19 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,8 @@ import { DecodedPng, ImageData, PngEncoderOptions, + IndexedColorBitDepth, + IndexedColors, } from './types'; export * from './types'; @@ -23,4 +25,57 @@ function encodePng(png: ImageData, options?: PngEncoderOptions): Uint8Array { return encoder.encode(); } -export { decodePng as decode, encodePng as encode }; +function rgb( + data: Uint8Array, + depth: IndexedColorBitDepth, + palette: IndexedColors, +) { + const indexSize = data.length * (8 / depth); + const resSize = indexSize * 3; + const res = new Uint8Array(resSize); + + let offset = 0; + let indexPos = 0; + const indexes = new Uint8Array(indexSize); + let bit = 0xff; + switch (depth) { + case 1: + bit = 0x80; + break; + case 2: + bit = 0xc0; + break; + case 4: + bit = 0xf0; + break; + case 8: + bit = 0xff; + break; + default: + throw new Error('Incorrect depth value'); + } + + for (const item of data) { + let bit2 = bit; + let shift = 8; + while (bit2) { + shift -= depth; + indexes[indexPos++] = (item & bit2) >> shift; + + bit2 >>= depth; + } + } + + for (const index of indexes) { + const color = palette[index]; + if (!color) { + throw new Error('Incorrect index of palette color'); + } + res.set(color, offset); + offset += 3; + } + + return res; +} + +export { decodePng as decode, encodePng as encode, rgb }; diff --git a/src/types.ts b/src/types.ts index e17d375..70ac807 100644 --- a/src/types.ts +++ b/src/types.ts @@ -9,6 +9,8 @@ export type DecoderInputType = IOBuffer | ArrayBufferLike | ArrayBufferView; export type BitDepth = 1 | 2 | 4 | 8 | 16; +export type IndexedColorBitDepth = 1 | 2 | 4 | 8; + export interface PngResolution { /** * Pixels per unit, X axis