mirror of
https://github.com/codedread/bitjs
synced 2025-10-03 17:49:16 +02:00
PngParser: Add support for tEXt chunk
This commit is contained in:
parent
8694b6cad8
commit
afb1a67f2d
4 changed files with 87 additions and 27 deletions
|
@ -108,7 +108,7 @@ const mimeType = findMimeType(someArrayBuffer);
|
||||||
### bitjs.image
|
### bitjs.image
|
||||||
|
|
||||||
This package includes code for dealing with binary images. It includes general event-based parsers
|
This package includes code for dealing with binary images. It includes general event-based parsers
|
||||||
for images (GIF and JPEG only, at the moment). It also includes a module for converting WebP images
|
for images (GIF, JPEG, PNG). It also includes a module for converting WebP images
|
||||||
into alternative raster graphics formats (PNG/JPG). This latter module is deprecated, now that WebP
|
into alternative raster graphics formats (PNG/JPG). This latter module is deprecated, now that WebP
|
||||||
images are well-supported in all browsers.
|
images are well-supported in all browsers.
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { ByteStream } from '../../io/bytestream.js';
|
||||||
// https://www.w3.org/TR/png-3/
|
// https://www.w3.org/TR/png-3/
|
||||||
// https://en.wikipedia.org/wiki/PNG#File_format
|
// https://en.wikipedia.org/wiki/PNG#File_format
|
||||||
|
|
||||||
// TODO: Ancillary chunks bKGD, eXIf, hIST, iTXt, pHYs, sPLT, tEXt, tIME, zTXt.
|
// TODO: Ancillary chunks bKGD, eXIf, hIST, iTXt, pHYs, sPLT, tIME, zTXt.
|
||||||
|
|
||||||
// let DEBUG = true;
|
// let DEBUG = true;
|
||||||
let DEBUG = false;
|
let DEBUG = false;
|
||||||
|
@ -22,13 +22,17 @@ const SIG = new Uint8Array([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
|
||||||
|
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
export const PngParseEventType = {
|
export const PngParseEventType = {
|
||||||
|
// Critical chunks.
|
||||||
|
IDAT: 'image_data',
|
||||||
IHDR: 'image_header',
|
IHDR: 'image_header',
|
||||||
|
PLTE: 'palette',
|
||||||
|
|
||||||
|
// Ancillary chunks.
|
||||||
|
cHRM: 'chromaticities_white_point',
|
||||||
gAMA: 'image_gamma',
|
gAMA: 'image_gamma',
|
||||||
sBIT: 'significant_bits',
|
sBIT: 'significant_bits',
|
||||||
cHRM: 'chromaticities_white_point',
|
tEXt: 'textual_data',
|
||||||
PLTE: 'palette',
|
|
||||||
tRNS: 'transparency',
|
tRNS: 'transparency',
|
||||||
IDAT: 'image_data',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @enum {number} */
|
/** @enum {number} */
|
||||||
|
@ -167,6 +171,21 @@ export class PngImageDataEvent extends Event {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef PngTextualData
|
||||||
|
* @property {string} keyword
|
||||||
|
* @property {string=} textString
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class PngTextualDataEvent extends Event {
|
||||||
|
/** @param {PngTextualData} textualData */
|
||||||
|
constructor(textualData) {
|
||||||
|
super(PngParseEventType.tEXt);
|
||||||
|
/** @type {PngTextualData} */
|
||||||
|
this.textualData = textualData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef PngChunk Internal use only.
|
* @typedef PngChunk Internal use only.
|
||||||
* @property {number} length
|
* @property {number} length
|
||||||
|
@ -203,12 +222,12 @@ export class PngParser extends EventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type-safe way to bind a listener for a PngImageHeaderEvent.
|
* Type-safe way to bind a listener for a PngChromaticiesEvent.
|
||||||
* @param {function(PngImageHeaderEvent): void} listener
|
* @param {function(PngChromaticiesEvent): void} listener
|
||||||
* @returns {PngParser} for chaining
|
* @returns {PngParser} for chaining
|
||||||
*/
|
*/
|
||||||
onImageHeader(listener) {
|
onChromaticities(listener) {
|
||||||
super.addEventListener(PngParseEventType.IHDR, listener);
|
super.addEventListener(PngParseEventType.cHRM, listener);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,22 +242,22 @@ export class PngParser extends EventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type-safe way to bind a listener for a PngSignificantBitsEvent.
|
* Type-safe way to bind a listener for a PngImageDataEvent.
|
||||||
* @param {function(PngSignificantBitsEvent): void} listener
|
* @param {function(PngImageDataEvent): void} listener
|
||||||
* @returns {PngParser} for chaining
|
* @returns {PngParser} for chaining
|
||||||
*/
|
*/
|
||||||
onSignificantBits(listener) {
|
onImageData(listener) {
|
||||||
super.addEventListener(PngParseEventType.sBIT, listener);
|
super.addEventListener(PngParseEventType.IDAT, listener);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type-safe way to bind a listener for a PngChromaticiesEvent.
|
* Type-safe way to bind a listener for a PngImageHeaderEvent.
|
||||||
* @param {function(PngChromaticiesEvent): void} listener
|
* @param {function(PngImageHeaderEvent): void} listener
|
||||||
* @returns {PngParser} for chaining
|
* @returns {PngParser} for chaining
|
||||||
*/
|
*/
|
||||||
onChromaticities(listener) {
|
onImageHeader(listener) {
|
||||||
super.addEventListener(PngParseEventType.cHRM, listener);
|
super.addEventListener(PngParseEventType.IHDR, listener);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,6 +271,26 @@ export class PngParser extends EventTarget {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type-safe way to bind a listener for a PngSignificantBitsEvent.
|
||||||
|
* @param {function(PngSignificantBitsEvent): void} listener
|
||||||
|
* @returns {PngParser} for chaining
|
||||||
|
*/
|
||||||
|
onSignificantBits(listener) {
|
||||||
|
super.addEventListener(PngParseEventType.sBIT, listener);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type-safe way to bind a listener for a PngTextualDataEvent.
|
||||||
|
* @param {function(PngTextualDataEvent): void} listener
|
||||||
|
* @returns {PngParser} for chaining
|
||||||
|
*/
|
||||||
|
onTextualData(listener) {
|
||||||
|
super.addEventListener(PngParseEventType.tEXt, listener);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type-safe way to bind a listener for a PngTransparencyEvent.
|
* Type-safe way to bind a listener for a PngTransparencyEvent.
|
||||||
* @param {function(PngTransparencyEvent): void} listener
|
* @param {function(PngTransparencyEvent): void} listener
|
||||||
|
@ -262,16 +301,6 @@ export class PngParser extends EventTarget {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Type-safe way to bind a listener for a PngImageDataEvent.
|
|
||||||
* @param {function(PngImageDataEvent): void} listener
|
|
||||||
* @returns {PngParser} for chaining
|
|
||||||
*/
|
|
||||||
onImageData(listener) {
|
|
||||||
super.addEventListener(PngParseEventType.IDAT, listener);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @returns {Promise<void>} A Promise that resolves when the parsing is complete. */
|
/** @returns {Promise<void>} A Promise that resolves when the parsing is complete. */
|
||||||
async start() {
|
async start() {
|
||||||
const sigLength = SIG.byteLength;
|
const sigLength = SIG.byteLength;
|
||||||
|
@ -404,6 +433,18 @@ export class PngParser extends EventTarget {
|
||||||
this.dispatchEvent(new PngPaletteEvent(this.palette));
|
this.dispatchEvent(new PngPaletteEvent(this.palette));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// https://www.w3.org/TR/png-3/#11tEXt
|
||||||
|
case 'tEXt':
|
||||||
|
const byteArr = chStream.peekBytes(length);
|
||||||
|
const nullIndex = byteArr.indexOf(0);
|
||||||
|
/** @type {PngTextualData} */
|
||||||
|
const textualData = {
|
||||||
|
keyword: chStream.readString(nullIndex),
|
||||||
|
textString: chStream.skip(1).readString(length - nullIndex - 1),
|
||||||
|
};
|
||||||
|
this.dispatchEvent(new PngTextualDataEvent(textualData));
|
||||||
|
break;
|
||||||
|
|
||||||
// https://www.w3.org/TR/png-3/#11tRNS
|
// https://www.w3.org/TR/png-3/#11tRNS
|
||||||
case 'tRNS':
|
case 'tRNS':
|
||||||
if (this.colorType === undefined) throw `tRNS before IHDR`;
|
if (this.colorType === undefined) throw `tRNS before IHDR`;
|
||||||
|
@ -507,6 +548,9 @@ async function main() {
|
||||||
parser.onImageData(evt => {
|
parser.onImageData(evt => {
|
||||||
// console.dir(evt);
|
// console.dir(evt);
|
||||||
});
|
});
|
||||||
|
parser.onTextualData(evt => {
|
||||||
|
// console.dir(evt.textualData);
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await parser.start();
|
await parser.start();
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { PngColorType, PngInterlaceMethod, PngParser } from '../image/parsers/pn
|
||||||
/** @typedef {import('../image/parsers/png.js').PngImageHeader} PngImageHeader */
|
/** @typedef {import('../image/parsers/png.js').PngImageHeader} PngImageHeader */
|
||||||
/** @typedef {import('../image/parsers/png.js').PngPalette} PngPalette */
|
/** @typedef {import('../image/parsers/png.js').PngPalette} PngPalette */
|
||||||
/** @typedef {import('../image/parsers/png.js').PngSignificantBits} PngSignificantBits */
|
/** @typedef {import('../image/parsers/png.js').PngSignificantBits} PngSignificantBits */
|
||||||
|
/** @typedef {import('../image/parsers/png.js').PngTextualData} PngTextualData */
|
||||||
/** @typedef {import('../image/parsers/png.js').PngTransparency} PngTransparency */
|
/** @typedef {import('../image/parsers/png.js').PngTransparency} PngTransparency */
|
||||||
|
|
||||||
function getPngParser(fileName) {
|
function getPngParser(fileName) {
|
||||||
|
@ -150,4 +151,19 @@ describe('bitjs.image.parsers.PngParser', () => {
|
||||||
expect(data.rawImageData.byteLength).equals(2205);
|
expect(data.rawImageData.byteLength).equals(2205);
|
||||||
expect(data.rawImageData[0]).equals(120);
|
expect(data.rawImageData[0]).equals(120);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('extracts tEXt', async () => {
|
||||||
|
/** @type {PngTextualData[]} */
|
||||||
|
let textualDataArr = [];
|
||||||
|
|
||||||
|
await getPngParser('tests/image-testfiles/ctzn0g04.png')
|
||||||
|
.onTextualData(evt => { textualDataArr.push(evt.textualData) })
|
||||||
|
.start();
|
||||||
|
|
||||||
|
expect(textualDataArr.length).equals(2);
|
||||||
|
expect(textualDataArr[0].keyword).equals('Title');
|
||||||
|
expect(textualDataArr[0].textString).equals('PngSuite');
|
||||||
|
expect(textualDataArr[1].keyword).equals('Author');
|
||||||
|
expect(textualDataArr[1].textString).equals('Willem A.J. van Schaik\n(willem@schaik.com)');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
BIN
tests/image-testfiles/ctzn0g04.png
Normal file
BIN
tests/image-testfiles/ctzn0g04.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 753 B |
Loading…
Add table
Add a link
Reference in a new issue