1
0
Fork 0
mirror of https://github.com/codedread/bitjs synced 2025-10-03 09:39:16 +02:00

Move EXIF profile extraction from jpeg.js into exif.js

This commit is contained in:
Jeff Schiller 2024-01-18 21:00:11 -08:00
parent 17b3850745
commit 29ca69e874
2 changed files with 75 additions and 66 deletions

View file

@ -223,6 +223,78 @@ export function getExifValue(stream, lookAheadStream, DEBUG = false) {
return exifValue;
}
/**
* Reads an Image File Directory from stream, populating the map.
* @param {ByteStream} stream The stream to extract the Exif value descriptors.
* @param {ByteStream} lookAheadStream The lookahead stream if the offset is used.
* @param {Map<number, ExifValue} exifValueMap This map to add the Exif values.
* @returns {number} The next IFD offset.
*/
function getExifIfd(stream, lookAheadStream, exifValueMap) {
let exifOffsetStream;
const numDirectoryEntries = stream.readNumber(2);
for (let entry = 0; entry < numDirectoryEntries; ++entry) {
const exifValue = getExifValue(stream, lookAheadStream);
const exifTagNumber = exifValue.tagNumber;
exifValueMap.set(exifTagNumber, exifValue);
if (exifValue.tagNumber === ExifTagNumber.EXIF_OFFSET) {
exifOffsetStream = lookAheadStream.tee().skip(exifValue.numericalValue);
}
} // Loop over Directory Entries.
if (exifOffsetStream) {
getExifIfd(exifOffsetStream, lookAheadStream, exifValueMap);
}
const nextIfdOffset = stream.readNumber(4);
return nextIfdOffset;
}
/**
* Reads the entire EXIF profile. The first 2 bytes in the stream must be the TIFF marker (II/MM).
* @param {ByteStream} stream
* @returns {Map<number, ExifValue} A map of all EXIF values found. The key is the EXIF tag number.
*/
export function getExifProfile(stream) {
const lookAheadStream = stream.tee();
const tiffByteAlign = stream.readString(2);
if (tiffByteAlign === 'II') {
stream.setLittleEndian();
lookAheadStream.setLittleEndian();
} else if (tiffByteAlign === 'MM') {
stream.setBigEndian();
lookAheadStream.setBigEndian();
} else {
throw `Invalid TIFF byte align symbol: ${tiffByteAlign}`;
}
const tiffMarker = stream.readNumber(2);
if (tiffMarker !== 0x002A) {
throw `Invalid marker, not 0x002a: 0x${tiffMarker.toString(16)}`;
}
/** @type {Map<number, ExifValue} */
const exifValueMap = new Map();
// The offset includes the tiffByteAlign (2), marker (2), and the offset field itself (4).
// It is usually 0x00000008, which means ifdOffsetSkip will almost always be zero.
const ifdOffsetSkip = stream.readNumber(4) - 8;
let ifdStream = stream.skip(ifdOffsetSkip).tee();
let nextIfdOffset;
while (true) {
nextIfdOffset = getExifIfd(ifdStream, lookAheadStream, exifValueMap);
// No more IFDs, so stop the loop.
if (nextIfdOffset === 0) break;
// Else, we have another IFD to read, point the stream at it.
ifdStream = lookAheadStream.tee().skip(nextIfdOffset);
}
return exifValueMap;
}
/**
* @param {Object} obj A numeric enum.
* @param {number} valToFind The value to find.

View file

@ -9,11 +9,9 @@
*/
import { ByteStream } from '../../io/bytestream.js';
import { ExifTagNumber, getExifValue } from './exif.js';
import { getExifProfile } from './exif.js';
/**
* @typedef {import('./exif.js').ExifValue} ExifValue
*/
/** @typedef {import('./exif.js').ExifValue} ExifValue */
// https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format
// https://www.media.mit.edu/pia/Research/deepview/exif.html
@ -388,41 +386,7 @@ export class JpegParser extends EventTarget {
}
if (this.bstream.readNumber(2) !== 0) throw `No null byte termination`;
const lookAheadStream = this.bstream.tee();
const tiffByteAlign = this.bstream.readString(2);
if (tiffByteAlign === 'II') {
this.bstream.setLittleEndian();
lookAheadStream.setLittleEndian();
} else if (tiffByteAlign === 'MM') {
this.bstream.setBigEndian();
lookAheadStream.setBigEndian();
} else {
throw `Invalid TIFF byte align symbol: ${tiffByteAlign}`;
}
const tiffMarker = this.bstream.readNumber(2);
if (tiffMarker !== 0x002A) {
throw `Invalid marker, not 0x002a: 0x${tiffMarker.toString(16)}`;
}
/** @type {Map<number, ExifValue} */
const exifValueMap = new Map();
// The offset includes the tiffByteAlign (2), marker (2), and the offset field itself (4).
const ifdOffset = this.bstream.readNumber(4) - 8;
let ifdStream = this.bstream.tee();
let nextIfdOffset;
while (true) {
nextIfdOffset = this.readExifIfd(ifdStream, lookAheadStream, exifValueMap);
// No more IFDs, so stop the loop.
if (nextIfdOffset === 0) break;
// Else, we have another IFD to read, point the stream at it.
ifdStream = lookAheadStream.tee().skip(nextIfdOffset);
}
const exifValueMap = getExifProfile(this.bstream);
this.dispatchEvent(new JpegApp1ExifEvent(exifValueMap));
this.bstream = skipAheadStream;
@ -582,31 +546,4 @@ export class JpegParser extends EventTarget {
}
} while (jpegMarker !== JpegSegmentType.EOI);
}
/**
* Reads an Image File Directory from stream.
* @param {ByteStream} stream The stream to extract the Exif value descriptor.
* @param {ByteStream} lookAheadStream The lookahead stream if the offset is used.
* @param {Map<number, ExifValue} exifValueMap This map to add the Exif values.
* @returns {number} The next IFD offset.
*/
readExifIfd(stream, lookAheadStream, exifValueMap) {
let exifOffsetStream;
const numDirectoryEntries = stream.readNumber(2);
for (let entry = 0; entry < numDirectoryEntries; ++entry) {
const exifValue = getExifValue(stream, lookAheadStream, DEBUG);
const exifTagNumber = exifValue.tagNumber;
exifValueMap.set(exifTagNumber, exifValue);
if (exifValue.tagNumber === ExifTagNumber.EXIF_OFFSET) {
exifOffsetStream = lookAheadStream.tee().skip(exifValue.numericalValue);
}
} // Loop over Directory Entries.
if (exifOffsetStream) {
this.readExifIfd(exifOffsetStream, lookAheadStream, exifValueMap);
}
const nextIfdOffset = stream.readNumber(4);
return nextIfdOffset;
}
}