@@ -17,7 +17,7 @@ import { getExifProfile } from './exif.js';
1717// https://www.w3.org/TR/png-3/
1818// https://en.wikipedia.org/wiki/PNG#File_format
1919
20- // TODO: Ancillary chunks: hIST, sPLT.
20+ // TODO: Ancillary chunks: sPLT.
2121
2222// let DEBUG = true;
2323let DEBUG = false ;
@@ -35,6 +35,7 @@ export const PngParseEventType = {
3535 cHRM : 'chromaticities_white_point' ,
3636 eXIf : 'exif_profile' ,
3737 gAMA : 'image_gamma' ,
38+ hIST : 'histogram' ,
3839 iTXt : 'intl_text_data' ,
3940 pHYs : 'physical_pixel_dims' ,
4041 sBIT : 'significant_bits' ,
@@ -299,6 +300,20 @@ export class PngExifProfileEvent extends Event {
299300 }
300301}
301302
303+ /**
304+ * @typedef PngHistogram
305+ * @property {number[] } frequencies The # of frequencies matches the # of palette entries.
306+ */
307+
308+ export class PngHistogramEvent extends Event {
309+ /** @param {PngHistogram } histogram */
310+ constructor ( histogram ) {
311+ super ( PngParseEventType . hIST ) ;
312+ /** @type {PngHistogram } */
313+ this . histogram = histogram ;
314+ }
315+ }
316+
302317/**
303318 * @typedef PngChunk Internal use only.
304319 * @property {number } length
@@ -326,7 +341,6 @@ export class PngParser extends EventTarget {
326341 */
327342 palette ;
328343
329-
330344 /** @param {ArrayBuffer } ab */
331345 constructor ( ab ) {
332346 super ( ) ;
@@ -384,6 +398,16 @@ export class PngParser extends EventTarget {
384398 return this ;
385399 }
386400
401+ /**
402+ * Type-safe way to bind a listener for a PngHistogramEvent.
403+ * @param {function(PngHistogramEvent): void } listener
404+ * @returns {PngParser } for chaining
405+ */
406+ onHistogram ( listener ) {
407+ super . addEventListener ( PngParseEventType . hIST , listener ) ;
408+ return this ;
409+ }
410+
387411 /**
388412 * Type-safe way to bind a listener for a PngImageDataEvent.
389413 * @param {function(PngImageDataEvent): void } listener
@@ -746,6 +770,20 @@ export class PngParser extends EventTarget {
746770 this . dispatchEvent ( new PngExifProfileEvent ( exifValueMap ) ) ;
747771 break ;
748772
773+ // https://www.w3.org/TR/png-3/#11hIST
774+ case 'hIST' :
775+ if ( ! this . palette ) throw `hIST before PLTE` ;
776+ if ( length !== this . palette . entries . length * 2 ) throw `Bad # of hIST frequencies: ${ length / 2 } ` ;
777+
778+ /** @type {PngHistogram } */
779+ const hist = { frequencies : [ ] } ;
780+ for ( let f = 0 ; f < this . palette . entries . length ; ++ f ) {
781+ hist . frequencies . push ( chStream . readNumber ( 2 ) ) ;
782+ }
783+
784+ this . dispatchEvent ( new PngHistogramEvent ( hist ) ) ;
785+ break ;
786+
749787 // https://www.w3.org/TR/png-3/#11IDAT
750788 case 'IDAT' :
751789 /** @type {PngImageData } */
@@ -836,6 +874,9 @@ async function main() {
836874 parser . onExifProfile ( evt => {
837875 // console.dir(evt.exifProfile);
838876 } ) ;
877+ parser . onHistogram ( evt => {
878+ // console.dir(evt.histogram);
879+ } ) ;
839880
840881 try {
841882 await parser . start ( ) ;
0 commit comments