Skip to content

Commit afb1a67

Browse files
committed
PngParser: Add support for tEXt chunk
1 parent 8694b6c commit afb1a67

File tree

4 files changed

+85
-25
lines changed

4 files changed

+85
-25
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ const mimeType = findMimeType(someArrayBuffer);
108108
### bitjs.image
109109

110110
This package includes code for dealing with binary images. It includes general event-based parsers
111-
for images (GIF and JPEG only, at the moment). It also includes a module for converting WebP images
111+
for images (GIF, JPEG, PNG). It also includes a module for converting WebP images
112112
into alternative raster graphics formats (PNG/JPG). This latter module is deprecated, now that WebP
113113
images are well-supported in all browsers.
114114

image/parsers/png.js

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,25 @@ import { ByteStream } from '../../io/bytestream.js';
1414
// https://www.w3.org/TR/png-3/
1515
// https://en.wikipedia.org/wiki/PNG#File_format
1616

17-
// TODO: Ancillary chunks bKGD, eXIf, hIST, iTXt, pHYs, sPLT, tEXt, tIME, zTXt.
17+
// TODO: Ancillary chunks bKGD, eXIf, hIST, iTXt, pHYs, sPLT, tIME, zTXt.
1818

1919
// let DEBUG = true;
2020
let DEBUG = false;
2121
const SIG = new Uint8Array([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
2222

2323
/** @enum {string} */
2424
export const PngParseEventType = {
25+
// Critical chunks.
26+
IDAT: 'image_data',
2527
IHDR: 'image_header',
28+
PLTE: 'palette',
29+
30+
// Ancillary chunks.
31+
cHRM: 'chromaticities_white_point',
2632
gAMA: 'image_gamma',
2733
sBIT: 'significant_bits',
28-
cHRM: 'chromaticities_white_point',
29-
PLTE: 'palette',
34+
tEXt: 'textual_data',
3035
tRNS: 'transparency',
31-
IDAT: 'image_data',
3236
};
3337

3438
/** @enum {number} */
@@ -167,6 +171,21 @@ export class PngImageDataEvent extends Event {
167171
}
168172
}
169173

174+
/**
175+
* @typedef PngTextualData
176+
* @property {string} keyword
177+
* @property {string=} textString
178+
*/
179+
180+
export class PngTextualDataEvent extends Event {
181+
/** @param {PngTextualData} textualData */
182+
constructor(textualData) {
183+
super(PngParseEventType.tEXt);
184+
/** @type {PngTextualData} */
185+
this.textualData = textualData;
186+
}
187+
}
188+
170189
/**
171190
* @typedef PngChunk Internal use only.
172191
* @property {number} length
@@ -203,12 +222,12 @@ export class PngParser extends EventTarget {
203222
}
204223

205224
/**
206-
* Type-safe way to bind a listener for a PngImageHeaderEvent.
207-
* @param {function(PngImageHeaderEvent): void} listener
225+
* Type-safe way to bind a listener for a PngChromaticiesEvent.
226+
* @param {function(PngChromaticiesEvent): void} listener
208227
* @returns {PngParser} for chaining
209228
*/
210-
onImageHeader(listener) {
211-
super.addEventListener(PngParseEventType.IHDR, listener);
229+
onChromaticities(listener) {
230+
super.addEventListener(PngParseEventType.cHRM, listener);
212231
return this;
213232
}
214233

@@ -223,22 +242,22 @@ export class PngParser extends EventTarget {
223242
}
224243

225244
/**
226-
* Type-safe way to bind a listener for a PngSignificantBitsEvent.
227-
* @param {function(PngSignificantBitsEvent): void} listener
245+
* Type-safe way to bind a listener for a PngImageDataEvent.
246+
* @param {function(PngImageDataEvent): void} listener
228247
* @returns {PngParser} for chaining
229248
*/
230-
onSignificantBits(listener) {
231-
super.addEventListener(PngParseEventType.sBIT, listener);
249+
onImageData(listener) {
250+
super.addEventListener(PngParseEventType.IDAT, listener);
232251
return this;
233252
}
234253

235254
/**
236-
* Type-safe way to bind a listener for a PngChromaticiesEvent.
237-
* @param {function(PngChromaticiesEvent): void} listener
255+
* Type-safe way to bind a listener for a PngImageHeaderEvent.
256+
* @param {function(PngImageHeaderEvent): void} listener
238257
* @returns {PngParser} for chaining
239258
*/
240-
onChromaticities(listener) {
241-
super.addEventListener(PngParseEventType.cHRM, listener);
259+
onImageHeader(listener) {
260+
super.addEventListener(PngParseEventType.IHDR, listener);
242261
return this;
243262
}
244263

@@ -253,22 +272,32 @@ export class PngParser extends EventTarget {
253272
}
254273

255274
/**
256-
* Type-safe way to bind a listener for a PngTransparencyEvent.
257-
* @param {function(PngTransparencyEvent): void} listener
275+
* Type-safe way to bind a listener for a PngSignificantBitsEvent.
276+
* @param {function(PngSignificantBitsEvent): void} listener
258277
* @returns {PngParser} for chaining
259278
*/
260-
onTransparency(listener) {
261-
super.addEventListener(PngParseEventType.tRNS, listener);
279+
onSignificantBits(listener) {
280+
super.addEventListener(PngParseEventType.sBIT, listener);
262281
return this;
263282
}
264283

265284
/**
266-
* Type-safe way to bind a listener for a PngImageDataEvent.
267-
* @param {function(PngImageDataEvent): void} listener
285+
* Type-safe way to bind a listener for a PngTextualDataEvent.
286+
* @param {function(PngTextualDataEvent): void} listener
268287
* @returns {PngParser} for chaining
269288
*/
270-
onImageData(listener) {
271-
super.addEventListener(PngParseEventType.IDAT, listener);
289+
onTextualData(listener) {
290+
super.addEventListener(PngParseEventType.tEXt, listener);
291+
return this;
292+
}
293+
294+
/**
295+
* Type-safe way to bind a listener for a PngTransparencyEvent.
296+
* @param {function(PngTransparencyEvent): void} listener
297+
* @returns {PngParser} for chaining
298+
*/
299+
onTransparency(listener) {
300+
super.addEventListener(PngParseEventType.tRNS, listener);
272301
return this;
273302
}
274303

@@ -404,6 +433,18 @@ export class PngParser extends EventTarget {
404433
this.dispatchEvent(new PngPaletteEvent(this.palette));
405434
break;
406435

436+
// https://www.w3.org/TR/png-3/#11tEXt
437+
case 'tEXt':
438+
const byteArr = chStream.peekBytes(length);
439+
const nullIndex = byteArr.indexOf(0);
440+
/** @type {PngTextualData} */
441+
const textualData = {
442+
keyword: chStream.readString(nullIndex),
443+
textString: chStream.skip(1).readString(length - nullIndex - 1),
444+
};
445+
this.dispatchEvent(new PngTextualDataEvent(textualData));
446+
break;
447+
407448
// https://www.w3.org/TR/png-3/#11tRNS
408449
case 'tRNS':
409450
if (this.colorType === undefined) throw `tRNS before IHDR`;
@@ -507,6 +548,9 @@ async function main() {
507548
parser.onImageData(evt => {
508549
// console.dir(evt);
509550
});
551+
parser.onTextualData(evt => {
552+
// console.dir(evt.textualData);
553+
});
510554

511555
try {
512556
await parser.start();

tests/image-parsers-png.spec.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { PngColorType, PngInterlaceMethod, PngParser } from '../image/parsers/pn
99
/** @typedef {import('../image/parsers/png.js').PngImageHeader} PngImageHeader */
1010
/** @typedef {import('../image/parsers/png.js').PngPalette} PngPalette */
1111
/** @typedef {import('../image/parsers/png.js').PngSignificantBits} PngSignificantBits */
12+
/** @typedef {import('../image/parsers/png.js').PngTextualData} PngTextualData */
1213
/** @typedef {import('../image/parsers/png.js').PngTransparency} PngTransparency */
1314

1415
function getPngParser(fileName) {
@@ -150,4 +151,19 @@ describe('bitjs.image.parsers.PngParser', () => {
150151
expect(data.rawImageData.byteLength).equals(2205);
151152
expect(data.rawImageData[0]).equals(120);
152153
});
154+
155+
it('extracts tEXt', async () => {
156+
/** @type {PngTextualData[]} */
157+
let textualDataArr = [];
158+
159+
await getPngParser('tests/image-testfiles/ctzn0g04.png')
160+
.onTextualData(evt => { textualDataArr.push(evt.textualData) })
161+
.start();
162+
163+
expect(textualDataArr.length).equals(2);
164+
expect(textualDataArr[0].keyword).equals('Title');
165+
expect(textualDataArr[0].textString).equals('PngSuite');
166+
expect(textualDataArr[1].keyword).equals('Author');
167+
expect(textualDataArr[1].textString).equals('Willem A.J. van Schaik\n([email protected])');
168+
});
153169
});

tests/image-testfiles/ctzn0g04.png

753 Bytes
Loading

0 commit comments

Comments
 (0)