1111import * as fs from 'node:fs' ; // TODO: Remove.
1212import { ByteStream } from '../../io/bytestream.js' ;
1313
14- // https://en.wikipedia.org/wiki/PNG#File_format
1514// 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.
1618
1719// let DEBUG = true;
1820let DEBUG = false ;
@@ -24,6 +26,7 @@ export const PngParseEventType = {
2426 gAMA : 'image_gamma' ,
2527 sBIT : 'significant_bits' ,
2628 PLTE : 'palette' ,
29+ tRNS : 'transparency' ,
2730 IDAT : 'image_data' ,
2831} ;
2932
@@ -110,6 +113,24 @@ export class PngPaletteEvent extends Event {
110113 }
111114}
112115
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+
113134/**
114135 * @typedef PngImageData
115136 * @property {Uint8Array } rawImageData
@@ -145,6 +166,13 @@ export class PngParser extends EventTarget {
145166 */
146167 colorType ;
147168
169+ /**
170+ * @type {PngPalette }
171+ * @private
172+ */
173+ palette ;
174+
175+
148176 /** @param {ArrayBuffer } ab */
149177 constructor ( ab ) {
150178 super ( ) ;
@@ -192,6 +220,16 @@ export class PngParser extends EventTarget {
192220 return this ;
193221 }
194222
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+
195233 /**
196234 * Type-safe way to bind a listener for a PngImageDataEvent.
197235 * @param {function(PngImageDataEvent): void } listener
@@ -266,22 +304,22 @@ export class PngParser extends EventTarget {
266304 /** @type {PngSignificantBits } */
267305 const sigBits = { } ;
268306
269- const badLengthErr = `Weird sBIT length for color type ${ this . colorType } : ${ length } ` ;
307+ const sbitBadLengthErr = `Weird sBIT length for color type ${ this . colorType } : ${ length } ` ;
270308 if ( this . colorType === PngColorType . GREYSCALE ) {
271- if ( length !== 1 ) throw badLengthErr ;
309+ if ( length !== 1 ) throw sbitBadLengthErr ;
272310 sigBits . significant_greyscale = chStream . readNumber ( 1 ) ;
273311 } else if ( this . colorType === PngColorType . TRUE_COLOR ||
274312 this . colorType === PngColorType . INDEXED_COLOR ) {
275- if ( length !== 3 ) throw badLengthErr ;
313+ if ( length !== 3 ) throw sbitBadLengthErr ;
276314 sigBits . significant_red = chStream . readNumber ( 1 ) ;
277315 sigBits . significant_green = chStream . readNumber ( 1 ) ;
278316 sigBits . significant_blue = chStream . readNumber ( 1 ) ;
279317 } else if ( this . colorType === PngColorType . GREYSCALE_WITH_ALPHA ) {
280- if ( length !== 2 ) throw badLengthErr ;
318+ if ( length !== 2 ) throw sbitBadLengthErr ;
281319 sigBits . significant_greyscale = chStream . readNumber ( 1 ) ;
282320 sigBits . significant_alpha = chStream . readNumber ( 1 ) ;
283321 } else if ( this . colorType === PngColorType . TRUE_COLOR_WITH_ALPHA ) {
284- if ( length !== 4 ) throw badLengthErr ;
322+ if ( length !== 4 ) throw sbitBadLengthErr ;
285323 sigBits . significant_red = chStream . readNumber ( 1 ) ;
286324 sigBits . significant_green = chStream . readNumber ( 1 ) ;
287325 sigBits . significant_blue = chStream . readNumber ( 1 ) ;
@@ -309,11 +347,45 @@ export class PngParser extends EventTarget {
309347 }
310348
311349 /** @type {PngPalette } */
312- const palette = {
350+ this . palette = {
313351 entries : paletteEntries ,
314352 } ;
315353
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 ) ) ;
317389 break ;
318390
319391 // https://www.w3.org/TR/2003/REC-PNG-20031110/#11IDAT
@@ -371,11 +443,14 @@ async function main() {
371443 // console.dir(evt.imageGamma);
372444 } ) ;
373445 parser . onSignificantBits ( evt => {
374- console . dir ( evt . sigBits ) ;
446+ // console.dir(evt.sigBits);
375447 } ) ;
376448 parser . onPalette ( evt => {
377449 // console.dir(evt.palette);
378450 } ) ;
451+ parser . onTransparency ( evt => {
452+ // console.dir(evt.transparency);
453+ } ) ;
379454 parser . onImageData ( evt => {
380455 // console.dir(evt);
381456 } ) ;
0 commit comments