Skip to content

Commit

Permalink
PngParser: Add support for pHYs chunk
Browse files Browse the repository at this point in the history
  • Loading branch information
codedread committed Jan 19, 2024
1 parent 1869cd7 commit 17b3850
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 5 deletions.
58 changes: 54 additions & 4 deletions image/parsers/png.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { ByteStream } from '../../io/bytestream.js';
// https://www.w3.org/TR/png-3/
// https://en.wikipedia.org/wiki/PNG#File_format

// TODO: Ancillary chunks eXIf, hIST, pHYs, sPLT.
// TODO: Ancillary chunks eXIf, hIST, sPLT.

// let DEBUG = true;
let DEBUG = false;
Expand All @@ -32,6 +32,7 @@ export const PngParseEventType = {
cHRM: 'chromaticities_white_point',
gAMA: 'image_gamma',
iTXt: 'intl_text_data',
pHYs: 'physical_pixel_dims',
sBIT: 'significant_bits',
tEXt: 'textual_data',
tIME: 'last_mod_time',
Expand Down Expand Up @@ -262,6 +263,27 @@ export class PngLastModTimeEvent extends Event {
}
}

export const PngUnitSpecifier = {
UNKNOWN: 0,
METRE: 1,
};

/**
* @typedef PngPhysicalPixelDimensions
* @property {number} pixelPerUnitX
* @property {number} pixelPerUnitY
* @property {PngUnitSpecifier} unitSpecifier
*/

export class PngPhysicalPixelDimensionsEvent extends Event {
/** @param {PngPhysicalPixelDimensions} physicalPixelDimensions */
constructor(physicalPixelDimensions) {
super(PngParseEventType.pHYs);
/** @type {PngPhysicalPixelDimensions} */
this.physicalPixelDimensions = physicalPixelDimensions;
}
}

/**
* @typedef PngChunk Internal use only.
* @property {number} length
Expand Down Expand Up @@ -368,8 +390,8 @@ export class PngParser extends EventTarget {
}

/**
* Type-safe way to bind a listener for a PngLastModTime.
* @param {function(PngLastModTime): void} listener
* Type-safe way to bind a listener for a PngLastModTimeEvent.
* @param {function(PngLastModTimeEvent): void} listener
* @returns {PngParser} for chaining
*/
onLastModTime(listener) {
Expand All @@ -387,6 +409,16 @@ export class PngParser extends EventTarget {
return this;
}

/**
* Type-safe way to bind a listener for a PngPhysicalPixelDimensionsEvent.
* @param {function(PngPhysicalPixelDimensionsEvent): void} listener
* @returns {PngParser} for chaining
*/
onPhysicalPixelDimensions(listener) {
super.addEventListener(PngParseEventType.pHYs, listener);
return this;
}

/**
* Type-safe way to bind a listener for a PngSignificantBitsEvent.
* @param {function(PngSignificantBitsEvent): void} listener
Expand Down Expand Up @@ -571,6 +603,21 @@ export class PngParser extends EventTarget {
this.dispatchEvent(new PngPaletteEvent(this.palette));
break;

// https://www.w3.org/TR/png-3/#11pHYs
case 'pHYs':
/** @type {physicalPixelDimensions} */
const pixelDims = {
pixelPerUnitX: chStream.readNumber(4),
pixelPerUnitY: chStream.readNumber(4),
unitSpecifier: chStream.readNumber(1),
};
if (!Object.values(PngUnitSpecifier).includes(pixelDims.unitSpecifier)) {
throw `Bad pHYs unit specifier: ${pixelDims.unitSpecifier}`;
}

this.dispatchEvent(new PngPhysicalPixelDimensionsEvent(pixelDims));
break;

// https://www.w3.org/TR/png-3/#11tEXt
case 'tEXt':
const byteArr = chStream.peekBytes(length);
Expand Down Expand Up @@ -750,7 +797,10 @@ async function main() {
// console.dir(evt.backgroundColor);
});
parser.onLastModTime(evt => {
console.dir(evt.lastModTime);
// console.dir(evt.lastModTime);
});
parser.onPhysicalPixelDimensions(evt => {
// console.dir(evt.physicalPixelDimensions);
});

try {
Expand Down
14 changes: 13 additions & 1 deletion tests/image-parsers-png.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as fs from 'node:fs';
import 'mocha';
import { expect } from 'chai';
import { PngColorType, PngInterlaceMethod, PngParser } from '../image/parsers/png.js';
import { PngColorType, PngInterlaceMethod, PngUnitSpecifier, PngParser } from '../image/parsers/png.js';

/** @typedef {import('../image/parsers/png.js').PngBackgroundColor} PngBackgroundColor */
/** @typedef {import('../image/parsers/png.js').PngChromaticies} PngChromaticies */
Expand All @@ -12,6 +12,7 @@ import { PngColorType, PngInterlaceMethod, PngParser } from '../image/parsers/pn
/** @typedef {import('../image/parsers/png.js').PngIntlTextualData} PngIntlTextualData */
/** @typedef {import('../image/parsers/png.js').PngLastModTime} PngLastModTime */
/** @typedef {import('../image/parsers/png.js').PngPalette} PngPalette */
/** @typedef {import('../image/parsers/png.js').PngPhysicalPixelDimensions} PngPhysicalPixelDimensions */
/** @typedef {import('../image/parsers/png.js').PngSignificantBits} PngSignificantBits */
/** @typedef {import('../image/parsers/png.js').PngTextualData} PngTextualData */
/** @typedef {import('../image/parsers/png.js').PngTransparency} PngTransparency */
Expand Down Expand Up @@ -248,4 +249,15 @@ describe('bitjs.image.parsers.PngParser', () => {
expect(lastModTime.minute).equals(59);
expect(lastModTime.second).equals(59);
});

it('extracts pHYs', async () => {
/** @type {PngPhysicalPixelDimensions} */
let pixelDims;
await getPngParser('tests/image-testfiles/cdun2c08.png')
.onPhysicalPixelDimensions(evt => { pixelDims = evt.physicalPixelDimensions })
.start();
expect(pixelDims.pixelPerUnitX).equals(1000);
expect(pixelDims.pixelPerUnitY).equals(1000);
expect(pixelDims.unitSpecifier).equals(PngUnitSpecifier.METRE);
});
});
Binary file added tests/image-testfiles/cdun2c08.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 17b3850

Please sign in to comment.