11
11
import * as fs from 'node:fs' ; // TODO: Remove.
12
12
import { ByteStream } from '../../io/bytestream.js' ;
13
13
14
- // https://en.wikipedia.org/wiki/PNG#File_format
15
14
// https://www.w3.org/TR/2003/REC-PNG-20031110
15
+ // https://en.wikipedia.org/wiki/PNG#File_format
16
+
17
+ // TODO: Ancillary chunks bKGD, cHRM, hIST, iTXt, pHYs, sPLT, tEXt, tIME, zTXt.
16
18
17
19
// let DEBUG = true;
18
20
let DEBUG = false ;
@@ -24,6 +26,7 @@ export const PngParseEventType = {
24
26
gAMA : 'image_gamma' ,
25
27
sBIT : 'significant_bits' ,
26
28
PLTE : 'palette' ,
29
+ tRNS : 'transparency' ,
27
30
IDAT : 'image_data' ,
28
31
} ;
29
32
@@ -110,6 +113,24 @@ export class PngPaletteEvent extends Event {
110
113
}
111
114
}
112
115
116
+ /**
117
+ * @typedef PngTransparency
118
+ * @property {number= } greySampleValue Populated for color type 0.
119
+ * @property {number= } redSampleValue Populated for color type 2.
120
+ * @property {number= } blueSampleValue Populated for color type 2.
121
+ * @property {number= } greenSampleValue Populated for color type 2.
122
+ * @property {number[]= } alphaPalette Populated for color type 3.
123
+ */
124
+
125
+ export class PngTransparencyEvent extends Event {
126
+ /** @param {PngTransparency } */
127
+ constructor ( transparency ) {
128
+ super ( PngParseEventType . tRNS ) ;
129
+ /** @type {PngTransparency } */
130
+ this . transparency = transparency ;
131
+ }
132
+ }
133
+
113
134
/**
114
135
* @typedef PngImageData
115
136
* @property {Uint8Array } rawImageData
@@ -145,6 +166,13 @@ export class PngParser extends EventTarget {
145
166
*/
146
167
colorType ;
147
168
169
+ /**
170
+ * @type {PngPalette }
171
+ * @private
172
+ */
173
+ palette ;
174
+
175
+
148
176
/** @param {ArrayBuffer } ab */
149
177
constructor ( ab ) {
150
178
super ( ) ;
@@ -192,6 +220,16 @@ export class PngParser extends EventTarget {
192
220
return this ;
193
221
}
194
222
223
+ /**
224
+ * Type-safe way to bind a listener for a PngTransparencyEvent.
225
+ * @param {function(PngTransparencyEvent): void } listener
226
+ * @returns {PngParser } for chaining
227
+ */
228
+ onTransparency ( listener ) {
229
+ super . addEventListener ( PngParseEventType . tRNS , listener ) ;
230
+ return this ;
231
+ }
232
+
195
233
/**
196
234
* Type-safe way to bind a listener for a PngImageDataEvent.
197
235
* @param {function(PngImageDataEvent): void } listener
@@ -266,22 +304,22 @@ export class PngParser extends EventTarget {
266
304
/** @type {PngSignificantBits } */
267
305
const sigBits = { } ;
268
306
269
- const badLengthErr = `Weird sBIT length for color type ${ this . colorType } : ${ length } ` ;
307
+ const sbitBadLengthErr = `Weird sBIT length for color type ${ this . colorType } : ${ length } ` ;
270
308
if ( this . colorType === PngColorType . GREYSCALE ) {
271
- if ( length !== 1 ) throw badLengthErr ;
309
+ if ( length !== 1 ) throw sbitBadLengthErr ;
272
310
sigBits . significant_greyscale = chStream . readNumber ( 1 ) ;
273
311
} else if ( this . colorType === PngColorType . TRUE_COLOR ||
274
312
this . colorType === PngColorType . INDEXED_COLOR ) {
275
- if ( length !== 3 ) throw badLengthErr ;
313
+ if ( length !== 3 ) throw sbitBadLengthErr ;
276
314
sigBits . significant_red = chStream . readNumber ( 1 ) ;
277
315
sigBits . significant_green = chStream . readNumber ( 1 ) ;
278
316
sigBits . significant_blue = chStream . readNumber ( 1 ) ;
279
317
} else if ( this . colorType === PngColorType . GREYSCALE_WITH_ALPHA ) {
280
- if ( length !== 2 ) throw badLengthErr ;
318
+ if ( length !== 2 ) throw sbitBadLengthErr ;
281
319
sigBits . significant_greyscale = chStream . readNumber ( 1 ) ;
282
320
sigBits . significant_alpha = chStream . readNumber ( 1 ) ;
283
321
} else if ( this . colorType === PngColorType . TRUE_COLOR_WITH_ALPHA ) {
284
- if ( length !== 4 ) throw badLengthErr ;
322
+ if ( length !== 4 ) throw sbitBadLengthErr ;
285
323
sigBits . significant_red = chStream . readNumber ( 1 ) ;
286
324
sigBits . significant_green = chStream . readNumber ( 1 ) ;
287
325
sigBits . significant_blue = chStream . readNumber ( 1 ) ;
@@ -309,11 +347,45 @@ export class PngParser extends EventTarget {
309
347
}
310
348
311
349
/** @type {PngPalette } */
312
- const palette = {
350
+ this . palette = {
313
351
entries : paletteEntries ,
314
352
} ;
315
353
316
- this . dispatchEvent ( new PngPaletteEvent ( palette ) ) ;
354
+ this . dispatchEvent ( new PngPaletteEvent ( this . palette ) ) ;
355
+ break ;
356
+
357
+ // https://www.w3.org/TR/2003/REC-PNG-20031110/#11tRNS
358
+ case 'tRNS' :
359
+ if ( this . colorType === undefined ) throw `tRNS before IHDR` ;
360
+ if ( this . colorType === PngColorType . GREYSCALE_WITH_ALPHA ||
361
+ this . colorType === PngColorType . TRUE_COLOR_WITH_ALPHA ) {
362
+ throw `tRNS with color type ${ this . colorType } ` ;
363
+ }
364
+
365
+ /** @type {PngTransparency } */
366
+ const transparency = { } ;
367
+
368
+ const trnsBadLengthErr = `Weird sBIT length for color type ${ this . colorType } : ${ length } ` ;
369
+ if ( this . colorType === PngColorType . GREYSCALE ) {
370
+ if ( length !== 2 ) throw trnsBadLengthErr ;
371
+ transparency . greySampleValue = chStream . readNumber ( 2 ) ;
372
+ } else if ( this . colorType === PngColorType . TRUE_COLOR ) {
373
+ if ( length !== 6 ) throw trnsBadLengthErr ;
374
+ // Oddly the order is RBG instead of RGB :-/
375
+ transparency . redSampleValue = chStream . readNumber ( 2 ) ;
376
+ transparency . blueSampleValue = chStream . readNumber ( 2 ) ;
377
+ transparency . greenSampleValue = chStream . readNumber ( 2 ) ;
378
+ } else if ( this . colorType === PngColorType . INDEXED_COLOR ) {
379
+ if ( ! this . palette ) throw `tRNS before PLTE` ;
380
+ if ( length > this . palette . entries . length ) throw `More tRNS entries than palette` ;
381
+
382
+ transparency . alphaPalette = [ ] ;
383
+ for ( let a = 0 ; a < length ; ++ a ) {
384
+ transparency . alphaPalette . push ( chStream . readNumber ( 1 ) ) ;
385
+ }
386
+ }
387
+
388
+ this . dispatchEvent ( new PngTransparencyEvent ( transparency ) ) ;
317
389
break ;
318
390
319
391
// https://www.w3.org/TR/2003/REC-PNG-20031110/#11IDAT
@@ -371,11 +443,14 @@ async function main() {
371
443
// console.dir(evt.imageGamma);
372
444
} ) ;
373
445
parser . onSignificantBits ( evt => {
374
- console . dir ( evt . sigBits ) ;
446
+ // console.dir(evt.sigBits);
375
447
} ) ;
376
448
parser . onPalette ( evt => {
377
449
// console.dir(evt.palette);
378
450
} ) ;
451
+ parser . onTransparency ( evt => {
452
+ // console.dir(evt.transparency);
453
+ } ) ;
379
454
parser . onImageData ( evt => {
380
455
// console.dir(evt);
381
456
} ) ;
0 commit comments