From be8f2cef80e2caa14e57f7a6e4c6393a99bf106a Mon Sep 17 00:00:00 2001 From: microshine Date: Thu, 4 Nov 2021 00:48:14 +0300 Subject: [PATCH 1/4] feat: add rgb method --- src/__tests__/rgb.test.ts | 86 +++++++++++++++++++++++++++++++++++++++ src/index.ts | 53 +++++++++++++++++++++++- src/types.ts | 2 + 3 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 src/__tests__/rgb.test.ts diff --git a/src/__tests__/rgb.test.ts b/src/__tests__/rgb.test.ts new file mode 100644 index 0000000..2d86b62 --- /dev/null +++ b/src/__tests__/rgb.test.ts @@ -0,0 +1,86 @@ +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..421a7d6 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,53 @@ 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 From 9f3e959e7a44f6282cef41e2c33d9cbc18546ab4 Mon Sep 17 00:00:00 2001 From: microshine Date: Thu, 4 Nov 2021 00:48:47 +0300 Subject: [PATCH 2/4] chore: add eslint required dependencies --- package.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.json b/package.json index 2d6a09d..64a079b 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,12 @@ "devDependencies": { "@types/jest": "^27.0.2", "@types/node": "^16.11.6", + "@typescript-eslint/eslint-plugin": "^5.3.0", + "@typescript-eslint/parser": "^5.3.0", "eslint": "^8.1.0", "eslint-config-cheminfo-typescript": "^10.1.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-jest": "^25.2.2", "jest": "^27.3.1", "pngjs": "^6.0.0", "prettier": "^2.4.1", From 0ae308d1efa08812b7fc4fd3214e41b7b4105189 Mon Sep 17 00:00:00 2001 From: microshine Date: Fri, 5 Nov 2021 17:22:51 +0300 Subject: [PATCH 3/4] chore: Remove eslint dev dependencies --- package.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/package.json b/package.json index 64a079b..2d6a09d 100644 --- a/package.json +++ b/package.json @@ -44,12 +44,8 @@ "devDependencies": { "@types/jest": "^27.0.2", "@types/node": "^16.11.6", - "@typescript-eslint/eslint-plugin": "^5.3.0", - "@typescript-eslint/parser": "^5.3.0", "eslint": "^8.1.0", "eslint-config-cheminfo-typescript": "^10.1.1", - "eslint-plugin-import": "^2.25.2", - "eslint-plugin-jest": "^25.2.2", "jest": "^27.3.1", "pngjs": "^6.0.0", "prettier": "^2.4.1", From f14992822457d4d97fd695a0d01593a9f08da768 Mon Sep 17 00:00:00 2001 From: microshine Date: Fri, 5 Nov 2021 17:49:46 +0300 Subject: [PATCH 4/4] chore: Fix lint errors --- src/__tests__/rgb.test.ts | 42 +++++++++++++++++++++++++++------------ src/index.ts | 10 +++++++--- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/__tests__/rgb.test.ts b/src/__tests__/rgb.test.ts index 2d86b62..9b9a2fa 100644 --- a/src/__tests__/rgb.test.ts +++ b/src/__tests__/rgb.test.ts @@ -5,20 +5,22 @@ import path from 'path'; import { rgb, IndexedColors, decode, encode, IndexedColorBitDepth } from '..'; describe('rgb', () => { - it('1 bit', () => { - const data = new Uint8Array([27]) + 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") + assert.strictEqual( + Buffer.from(view).toString('hex'), + '000001000001000001000002000002000001000002000002', + ); }); it('2 bit', () => { - const data = new Uint8Array([27]) + const data = new Uint8Array([27]); const palette: IndexedColors = [ [0, 0, 1], [0, 0, 2], @@ -27,11 +29,14 @@ describe('rgb', () => { ]; const view = rgb(data, 2, palette); - assert.strictEqual(Buffer.from(view).toString("hex"), "000001000002000003000004") + assert.strictEqual( + Buffer.from(view).toString('hex'), + '000001000002000003000004', + ); }); it('4 bit', () => { - const data = new Uint8Array([18, 52, 86, 120]) + const data = new Uint8Array([18, 52, 86, 120]); const palette: IndexedColors = [ [0, 0, 0], [0, 0, 1], @@ -45,11 +50,14 @@ describe('rgb', () => { ]; const view = rgb(data, 4, palette); - assert.strictEqual(Buffer.from(view).toString("hex"), "000001000002000003000004000005000006000007000008") + assert.strictEqual( + Buffer.from(view).toString('hex'), + '000001000002000003000004000005000006000007000008', + ); }); it('8 bit', () => { - const data = new Uint8Array([1, 2, 3, 4]) + const data = new Uint8Array([1, 2, 3, 4]); const palette: IndexedColors = [ [0, 0, 0], [0, 0, 1], @@ -59,16 +67,25 @@ describe('rgb', () => { ]; const view = rgb(data, 8, palette); - assert.strictEqual(Buffer.from(view).toString("hex"), "000001000002000003000004") + 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")); + 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 data = rgb( + img.data as Uint8Array, + img.depth as IndexedColorBitDepth, + img.palette, + ); const newImg = encode({ width: img.width, height: img.height, @@ -82,5 +99,4 @@ describe('rgb', () => { const newImageParsed = decode(newImg); assert.strictEqual(newImageParsed.data.byteLength, 90000); }); - }); diff --git a/src/index.ts b/src/index.ts index 421a7d6..759ed19 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,7 +25,11 @@ function encodePng(png: ImageData, options?: PngEncoderOptions): Uint8Array { return encoder.encode(); } -function rgb(data: Uint8Array, depth: IndexedColorBitDepth, palette: IndexedColors) { +function rgb( + data: Uint8Array, + depth: IndexedColorBitDepth, + palette: IndexedColors, +) { const indexSize = data.length * (8 / depth); const resSize = indexSize * 3; const res = new Uint8Array(resSize); @@ -48,7 +52,7 @@ function rgb(data: Uint8Array, depth: IndexedColorBitDepth, palette: IndexedColo bit = 0xff; break; default: - throw new Error("Incorrect depth value") + throw new Error('Incorrect depth value'); } for (const item of data) { @@ -65,7 +69,7 @@ function rgb(data: Uint8Array, depth: IndexedColorBitDepth, palette: IndexedColo for (const index of indexes) { const color = palette[index]; if (!color) { - throw new Error("Incorrect index of palette color"); + throw new Error('Incorrect index of palette color'); } res.set(color, offset); offset += 3;