diff --git a/image/parsers/README.md b/image/parsers/README.md index 7ea5ff0..17b0168 100644 --- a/image/parsers/README.md +++ b/image/parsers/README.md @@ -2,5 +2,5 @@ General-purpose, event-based parsers for digital images. Currently supports GIF, JPEG, and PNG. -Some nice implementations of Exif parsing for PNG, HEIF, TIFF here: +Some nice implementations of Exif parsing for HEIF, TIFF here: https://github.com/MikeKovarik/exifr/tree/master/src/file-parsers \ No newline at end of file diff --git a/image/parsers/png.js b/image/parsers/png.js index f5c3399..10a859a 100644 --- a/image/parsers/png.js +++ b/image/parsers/png.js @@ -17,7 +17,7 @@ import { getExifProfile } from './exif.js'; // https://www.w3.org/TR/png-3/ // https://en.wikipedia.org/wiki/PNG#File_format -// TODO: Ancillary chunks: hIST, sPLT. +// TODO: Ancillary chunks: sPLT. // let DEBUG = true; let DEBUG = false; @@ -35,6 +35,7 @@ export const PngParseEventType = { cHRM: 'chromaticities_white_point', eXIf: 'exif_profile', gAMA: 'image_gamma', + hIST: 'histogram', iTXt: 'intl_text_data', pHYs: 'physical_pixel_dims', sBIT: 'significant_bits', @@ -299,6 +300,20 @@ export class PngExifProfileEvent extends Event { } } +/** + * @typedef PngHistogram + * @property {number[]} frequencies The # of frequencies matches the # of palette entries. + */ + +export class PngHistogramEvent extends Event { + /** @param {PngHistogram} histogram */ + constructor(histogram) { + super(PngParseEventType.hIST); + /** @type {PngHistogram} */ + this.histogram = histogram; + } +} + /** * @typedef PngChunk Internal use only. * @property {number} length @@ -326,7 +341,6 @@ export class PngParser extends EventTarget { */ palette; - /** @param {ArrayBuffer} ab */ constructor(ab) { super(); @@ -384,6 +398,16 @@ export class PngParser extends EventTarget { return this; } + /** + * Type-safe way to bind a listener for a PngHistogramEvent. + * @param {function(PngHistogramEvent): void} listener + * @returns {PngParser} for chaining + */ + onHistogram(listener) { + super.addEventListener(PngParseEventType.hIST, listener); + return this; + } + /** * Type-safe way to bind a listener for a PngImageDataEvent. * @param {function(PngImageDataEvent): void} listener @@ -746,6 +770,20 @@ export class PngParser extends EventTarget { this.dispatchEvent(new PngExifProfileEvent(exifValueMap)); break; + // https://www.w3.org/TR/png-3/#11hIST + case 'hIST': + if (!this.palette) throw `hIST before PLTE`; + if (length !== this.palette.entries.length * 2) throw `Bad # of hIST frequencies: ${length / 2}`; + + /** @type {PngHistogram} */ + const hist = { frequencies: [] }; + for (let f = 0; f < this.palette.entries.length; ++f) { + hist.frequencies.push(chStream.readNumber(2)); + } + + this.dispatchEvent(new PngHistogramEvent(hist)); + break; + // https://www.w3.org/TR/png-3/#11IDAT case 'IDAT': /** @type {PngImageData} */ @@ -836,6 +874,9 @@ async function main() { parser.onExifProfile(evt => { // console.dir(evt.exifProfile); }); + parser.onHistogram(evt => { + // console.dir(evt.histogram); + }); try { await parser.start(); diff --git a/tests/image-testfiles/ch1n3p04.png b/tests/image-testfiles/ch1n3p04.png new file mode 100644 index 0000000..17cd12d Binary files /dev/null and b/tests/image-testfiles/ch1n3p04.png differ