Skip to content

Instantly share code, notes, and snippets.

@yepitschunked
Created October 19, 2016 22:25
Show Gist options
  • Save yepitschunked/9d2e73d9228f5a0b300d75babe2c3796 to your computer and use it in GitHub Desktop.
Save yepitschunked/9d2e73d9228f5a0b300d75babe2c3796 to your computer and use it in GitHub Desktop.
extract image orientation from exif
export function extractOrientation(imgBlob) {
// written based on https://www.media.mit.edu/pia/Research/deepview/exif.html
// the exif orientation values are pretty nonsensical.
const orientationValueToRotateDegrees = {
1: 0,
3: 180,
6: 90,
8: 270,
};
return new Promise((resolve) => {
const fileReader = new FileReader();
fileReader.onload = () => resolve(fileReader);
fileReader.readAsArrayBuffer(imgBlob);
})
.then((reader) => {
const dataView = new DataView(reader.result);
let cursor = 0;
// look for jpeg headers
if (dataView.getUint16(cursor) !== 0xFFD8) {
throw new Error('not a valid jpeg');
}
// advance past the jpeg header
cursor += 2;
while (cursor < dataView.byteLength - 1) { // we read two bytes at a time
if (dataView.getUint16(cursor) === 0xFFE1) { // look for exif header
cursor += 2; // advance past exif header
// read the size of exif
// NB: this includes the 2 bytes of the size field itself, so we have to subtract 2
const exifSize = dataView.getUint16(cursor) - 2;
cursor += 2; // advance past exif size field
// return a new dataview with the exif region
return new DataView(reader.result, cursor, exifSize);
}
cursor += 2;
}
throw new Error('could not find exif start tag');
})
.then((exifView) => {
let cursor = 0; // skip the Exif string header
if (exifView.getUint32(cursor) !== 0x45786966) { // ascii hex values of the string 'Exif'
throw new Error('could not find Exif header');
}
cursor += 4; // advance past the exif header
// Now determine the endianness of the tags by checking the 8 byte TIFF header
const littleEndian = exifView.getUint16(cursor) === 0x4949;
cursor += 8; // advance past the TIFF header
// Now sloppily search for the orientation tag id
while (cursor < exifView.byteLength - 1) {
if (exifView.getUint16(cursor, littleEndian) === 0x0112) {
// advance past 2 byte tag id + 2 bytes of tag data type + 4 bytes of tag value count
cursor += 8;
const orientation = exifView.getUint16(cursor, littleEndian);
const rotate = orientationValueToRotateDegrees[orientation] || 0;
return rotate;
}
cursor += 2;
}
throw new Error('no orientation defined');
})
.catch(() => 0); // default to 0 rotation
}
@jeradg
Copy link

jeradg commented Nov 9, 2019

Ran into a bug with detection of the Exif byte order (II or MM).

On line 44 you look for the Exif header as Exif:

if (exifView.getUint32(cursor) !== 0x45786966) { // ascii hex values of the string 'Exif'

Then you advance the cursor by 4 bytes in order to look for the byte order marker.

The problem is that the Exif header is not Exif but Exif00. Which means you have to advance the cursor by 6, not 4. (See https://www.media.mit.edu/pia/Research/deepview/exif.html#ExifData)

As written here, littleEndian is never true because exifView.getUint16(cursor) always equals 0 right after the Exif part of the Exif header.

If you change line 47 from cursor += 4 to cursor +=6, then you're at the correct position to look for the byte-order marker.

Thanks for the code, works great otherwise! Extremely helpful.

@hnq90
Copy link

hnq90 commented Apr 13, 2020

👍 @jeradg

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment