Skip to content

Commit 6ff9333

Browse files
committed
PngParser: Handle sBIT chunk.
1 parent 1d0abca commit 6ff9333

File tree

3 files changed

+78
-3
lines changed

3 files changed

+78
-3
lines changed

image/parsers/png.js

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const SIG = new Uint8Array([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
2222
export const PngParseEventType = {
2323
IHDR: 'image_header',
2424
gAMA: 'image_gamma',
25+
sBIT: 'significant_bits',
2526
PLTE: 'palette',
2627
IDAT: 'image_data',
2728
};
@@ -70,6 +71,24 @@ export class PngImageGammaEvent extends Event {
7071
}
7172
}
7273

74+
/**
75+
* @typedef PngSignificantBits
76+
* @property {number=} significant_greyscale Populated for color types 0, 4.
77+
* @property {number=} significant_red Populated for color types 2, 3, 6.
78+
* @property {number=} significant_green Populated for color types 2, 3, 6.
79+
* @property {number=} significant_blue Populated for color types 2, 3, 6.
80+
* @property {number=} significant_alpha Populated for color types 4, 6.
81+
*/
82+
83+
export class PngSignificantBitsEvent extends Event {
84+
/** @param {PngSignificantBits} */
85+
constructor(sigBits) {
86+
super(PngParseEventType.sBIT);
87+
/** @type {PngSignificantBits} */
88+
this.sigBits = sigBits;
89+
}
90+
}
91+
7392
/**
7493
* @typedef PngColor
7594
* @property {number} red
@@ -153,6 +172,16 @@ export class PngParser extends EventTarget {
153172
return this;
154173
}
155174

175+
/**
176+
* Type-safe way to bind a listener for a PngSignificantBitsEvent.
177+
* @param {function(PngSignificantBitsEvent): void} listener
178+
* @returns {PngParser} for chaining
179+
*/
180+
onSignificantBits(listener) {
181+
super.addEventListener(PngParseEventType.sBIT, listener);
182+
return this;
183+
}
184+
156185
/**
157186
* Type-safe way to bind a listener for a PngPaletteEvent.
158187
* @param {function(PngPaletteEvent): void} listener
@@ -231,6 +260,37 @@ export class PngParser extends EventTarget {
231260
this.dispatchEvent(new PngImageGammaEvent(chStream.readNumber(4)));
232261
break;
233262

263+
// https://www.w3.org/TR/2003/REC-PNG-20031110/#11sBIT
264+
case 'sBIT':
265+
if (this.colorType === undefined) throw `sBIT before IHDR`;
266+
/** @type {PngSignificantBits} */
267+
const sigBits = {};
268+
269+
const badLengthErr = `Weird sBIT length for color type ${this.colorType}: ${length}`;
270+
if (this.colorType === PngColorType.GREYSCALE) {
271+
if (length !== 1) throw badLengthErr;
272+
sigBits.significant_greyscale = chStream.readNumber(1);
273+
} else if (this.colorType === PngColorType.TRUE_COLOR ||
274+
this.colorType === PngColorType.INDEXED_COLOR) {
275+
if (length !== 3) throw badLengthErr;
276+
sigBits.significant_red = chStream.readNumber(1);
277+
sigBits.significant_green = chStream.readNumber(1);
278+
sigBits.significant_blue = chStream.readNumber(1);
279+
} else if (this.colorType === PngColorType.GREYSCALE_WITH_ALPHA) {
280+
if (length !== 2) throw badLengthErr;
281+
sigBits.significant_greyscale = chStream.readNumber(1);
282+
sigBits.significant_alpha = chStream.readNumber(1);
283+
} else if (this.colorType === PngColorType.TRUE_COLOR_WITH_ALPHA) {
284+
if (length !== 4) throw badLengthErr;
285+
sigBits.significant_red = chStream.readNumber(1);
286+
sigBits.significant_green = chStream.readNumber(1);
287+
sigBits.significant_blue = chStream.readNumber(1);
288+
sigBits.significant_alpha = chStream.readNumber(1);
289+
}
290+
291+
this.dispatchEvent(new PngSignificantBitsEvent(sigBits));
292+
break;
293+
234294
// https://www.w3.org/TR/2003/REC-PNG-20031110/#11PLTE
235295
case 'PLTE':
236296
if (this.colorType === undefined) throw `PLTE before IHDR`;
@@ -300,8 +360,6 @@ basn0g02.png bgbn4a08.png cs5n3p08.png f03n0g08.png g10n2c08.png ps2n2c16.png s0
300360

301361
async function main() {
302362
for (const fileName of FILES) {
303-
if (!fileName.includes('3p')) continue;
304-
305363
console.log(`file: ${fileName}`);
306364
const nodeBuf = fs.readFileSync(fileName);
307365
const ab = nodeBuf.buffer.slice(nodeBuf.byteOffset, nodeBuf.byteOffset + nodeBuf.length);
@@ -312,6 +370,9 @@ async function main() {
312370
parser.onGamma(evt => {
313371
// console.dir(evt.imageGamma);
314372
});
373+
parser.onSignificantBits(evt => {
374+
console.dir(evt.sigBits);
375+
});
315376
parser.onPalette(evt => {
316377
// console.dir(evt.palette);
317378
});

tests/image-parsers-png.spec.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import 'mocha';
33
import { expect } from 'chai';
44
import { PngColorType, PngInterlaceMethod, PngParser } from '../image/parsers/png.js';
55

6-
/** @typedef {import('../image/parsers/png.js').PngImageHeader} PngImageHeader */
76
/** @typedef {import('../image/parsers/png.js').PngImageData} PngImageData */
87
/** @typedef {import('../image/parsers/png.js').PngImageGamma} PngImageGamma */
8+
/** @typedef {import('../image/parsers/png.js').PngImageHeader} PngImageHeader */
99
/** @typedef {import('../image/parsers/png.js').PngPalette} PngPalette */
10+
/** @typedef {import('../image/parsers/png.js').PngSignificantBits} PngSignificantBits */
1011

1112
function getPngParser(fileName) {
1213
const nodeBuf = fs.readFileSync(fileName);
@@ -57,6 +58,19 @@ describe('bitjs.image.parsers.PngParser', () => {
5758
expect(gamma).equals(55000);
5859
});
5960

61+
it('extracts sBIT', async () => {
62+
/** @type {PngSignificantBits} */
63+
let sBits;
64+
await getPngParser('tests/image-testfiles/cs3n2c16.png')
65+
.onSignificantBits(evt => sBits = evt.sigBits)
66+
.start();
67+
expect(sBits.significant_red).equals(13);
68+
expect(sBits.significant_green).equals(13);
69+
expect(sBits.significant_blue).equals(13);
70+
expect(sBits.significant_greyscale).equals(undefined);
71+
expect(sBits.significant_alpha).equals(undefined);
72+
});
73+
6074
it('extracts PLTE', async () => {
6175
/** @type {PngPalette} */
6276
let palette;

tests/image-testfiles/cs3n2c16.png

214 Bytes
Loading

0 commit comments

Comments
 (0)