Skip to content

Commit 29ca69e

Browse files
committed
Move EXIF profile extraction from jpeg.js into exif.js
1 parent 17b3850 commit 29ca69e

File tree

2 files changed

+75
-66
lines changed

2 files changed

+75
-66
lines changed

image/parsers/exif.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,78 @@ export function getExifValue(stream, lookAheadStream, DEBUG = false) {
223223
return exifValue;
224224
}
225225

226+
/**
227+
* Reads an Image File Directory from stream, populating the map.
228+
* @param {ByteStream} stream The stream to extract the Exif value descriptors.
229+
* @param {ByteStream} lookAheadStream The lookahead stream if the offset is used.
230+
* @param {Map<number, ExifValue} exifValueMap This map to add the Exif values.
231+
* @returns {number} The next IFD offset.
232+
*/
233+
function getExifIfd(stream, lookAheadStream, exifValueMap) {
234+
let exifOffsetStream;
235+
const numDirectoryEntries = stream.readNumber(2);
236+
for (let entry = 0; entry < numDirectoryEntries; ++entry) {
237+
const exifValue = getExifValue(stream, lookAheadStream);
238+
const exifTagNumber = exifValue.tagNumber;
239+
exifValueMap.set(exifTagNumber, exifValue);
240+
if (exifValue.tagNumber === ExifTagNumber.EXIF_OFFSET) {
241+
exifOffsetStream = lookAheadStream.tee().skip(exifValue.numericalValue);
242+
}
243+
} // Loop over Directory Entries.
244+
245+
if (exifOffsetStream) {
246+
getExifIfd(exifOffsetStream, lookAheadStream, exifValueMap);
247+
}
248+
249+
const nextIfdOffset = stream.readNumber(4);
250+
return nextIfdOffset;
251+
}
252+
253+
/**
254+
* Reads the entire EXIF profile. The first 2 bytes in the stream must be the TIFF marker (II/MM).
255+
* @param {ByteStream} stream
256+
* @returns {Map<number, ExifValue} A map of all EXIF values found. The key is the EXIF tag number.
257+
*/
258+
export function getExifProfile(stream) {
259+
const lookAheadStream = stream.tee();
260+
const tiffByteAlign = stream.readString(2);
261+
if (tiffByteAlign === 'II') {
262+
stream.setLittleEndian();
263+
lookAheadStream.setLittleEndian();
264+
} else if (tiffByteAlign === 'MM') {
265+
stream.setBigEndian();
266+
lookAheadStream.setBigEndian();
267+
} else {
268+
throw `Invalid TIFF byte align symbol: ${tiffByteAlign}`;
269+
}
270+
271+
const tiffMarker = stream.readNumber(2);
272+
if (tiffMarker !== 0x002A) {
273+
throw `Invalid marker, not 0x002a: 0x${tiffMarker.toString(16)}`;
274+
}
275+
276+
/** @type {Map<number, ExifValue} */
277+
const exifValueMap = new Map();
278+
279+
// The offset includes the tiffByteAlign (2), marker (2), and the offset field itself (4).
280+
// It is usually 0x00000008, which means ifdOffsetSkip will almost always be zero.
281+
const ifdOffsetSkip = stream.readNumber(4) - 8;
282+
283+
let ifdStream = stream.skip(ifdOffsetSkip).tee();
284+
let nextIfdOffset;
285+
while (true) {
286+
nextIfdOffset = getExifIfd(ifdStream, lookAheadStream, exifValueMap);
287+
288+
// No more IFDs, so stop the loop.
289+
if (nextIfdOffset === 0) break;
290+
291+
// Else, we have another IFD to read, point the stream at it.
292+
ifdStream = lookAheadStream.tee().skip(nextIfdOffset);
293+
}
294+
295+
return exifValueMap;
296+
}
297+
226298
/**
227299
* @param {Object} obj A numeric enum.
228300
* @param {number} valToFind The value to find.

image/parsers/jpeg.js

Lines changed: 3 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@
99
*/
1010

1111
import { ByteStream } from '../../io/bytestream.js';
12-
import { ExifTagNumber, getExifValue } from './exif.js';
12+
import { getExifProfile } from './exif.js';
1313

14-
/**
15-
* @typedef {import('./exif.js').ExifValue} ExifValue
16-
*/
14+
/** @typedef {import('./exif.js').ExifValue} ExifValue */
1715

1816
// https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format
1917
// https://www.media.mit.edu/pia/Research/deepview/exif.html
@@ -388,41 +386,7 @@ export class JpegParser extends EventTarget {
388386
}
389387
if (this.bstream.readNumber(2) !== 0) throw `No null byte termination`;
390388

391-
const lookAheadStream = this.bstream.tee();
392-
const tiffByteAlign = this.bstream.readString(2);
393-
if (tiffByteAlign === 'II') {
394-
this.bstream.setLittleEndian();
395-
lookAheadStream.setLittleEndian();
396-
} else if (tiffByteAlign === 'MM') {
397-
this.bstream.setBigEndian();
398-
lookAheadStream.setBigEndian();
399-
} else {
400-
throw `Invalid TIFF byte align symbol: ${tiffByteAlign}`;
401-
}
402-
403-
const tiffMarker = this.bstream.readNumber(2);
404-
if (tiffMarker !== 0x002A) {
405-
throw `Invalid marker, not 0x002a: 0x${tiffMarker.toString(16)}`;
406-
}
407-
408-
/** @type {Map<number, ExifValue} */
409-
const exifValueMap = new Map();
410-
411-
// The offset includes the tiffByteAlign (2), marker (2), and the offset field itself (4).
412-
const ifdOffset = this.bstream.readNumber(4) - 8;
413-
414-
let ifdStream = this.bstream.tee();
415-
let nextIfdOffset;
416-
while (true) {
417-
nextIfdOffset = this.readExifIfd(ifdStream, lookAheadStream, exifValueMap);
418-
419-
// No more IFDs, so stop the loop.
420-
if (nextIfdOffset === 0) break;
421-
422-
// Else, we have another IFD to read, point the stream at it.
423-
ifdStream = lookAheadStream.tee().skip(nextIfdOffset);
424-
}
425-
389+
const exifValueMap = getExifProfile(this.bstream);
426390
this.dispatchEvent(new JpegApp1ExifEvent(exifValueMap));
427391

428392
this.bstream = skipAheadStream;
@@ -582,31 +546,4 @@ export class JpegParser extends EventTarget {
582546
}
583547
} while (jpegMarker !== JpegSegmentType.EOI);
584548
}
585-
586-
/**
587-
* Reads an Image File Directory from stream.
588-
* @param {ByteStream} stream The stream to extract the Exif value descriptor.
589-
* @param {ByteStream} lookAheadStream The lookahead stream if the offset is used.
590-
* @param {Map<number, ExifValue} exifValueMap This map to add the Exif values.
591-
* @returns {number} The next IFD offset.
592-
*/
593-
readExifIfd(stream, lookAheadStream, exifValueMap) {
594-
let exifOffsetStream;
595-
const numDirectoryEntries = stream.readNumber(2);
596-
for (let entry = 0; entry < numDirectoryEntries; ++entry) {
597-
const exifValue = getExifValue(stream, lookAheadStream, DEBUG);
598-
const exifTagNumber = exifValue.tagNumber;
599-
exifValueMap.set(exifTagNumber, exifValue);
600-
if (exifValue.tagNumber === ExifTagNumber.EXIF_OFFSET) {
601-
exifOffsetStream = lookAheadStream.tee().skip(exifValue.numericalValue);
602-
}
603-
} // Loop over Directory Entries.
604-
605-
if (exifOffsetStream) {
606-
this.readExifIfd(exifOffsetStream, lookAheadStream, exifValueMap);
607-
}
608-
609-
const nextIfdOffset = stream.readNumber(4);
610-
return nextIfdOffset;
611-
}
612549
}

0 commit comments

Comments
 (0)