Skip to content

Instantly share code, notes, and snippets.

@laziel
Last active June 30, 2020 08:50
Show Gist options
  • Save laziel/1141609653e02bf1519db44136b9043a to your computer and use it in GitHub Desktop.
Save laziel/1141609653e02bf1519db44136b9043a to your computer and use it in GitHub Desktop.
Get image orientation from EXIF header
/*
JPEG orientation transform
See https://magnushoff.com/jpeg-orientation.html
*/
img[data-orientation="2"] {
transform: rotateY(180deg);
}
img[data-orientation="3"] {
transform: rotate(180deg);
}
img[data-orientation="4"] {
transform: rotate(180deg) rotateY(180deg);
}
img[data-orientation="5"] {
transform: rotate(270deg) rotateY(180deg);
transform-origin: top left;
}
img[data-orientation="6"] {
transform: translateY(-100%) rotate(90deg);
transform-origin: bottom left;
}
img[data-orientation="7"] {
transform: translateY(-100%) translateX(-100%) rotate(90deg) rotateY(180deg);
transform-origin: bottom right;
}
img[data-orientation="8"] {
transform: translateX(-100%) rotate(270deg);
transform-origin: top right;
}
/**
* Send request a URL, and get a response as ArrayBuffer.
*
* Note:
* - Only the URL which is http(s):// protocol allowed to use fetch API.
* - XHR.onload doesn't works. Use 'readystatechange' event instead.
*
* @param {String} url
* @return {Promise}
*/
const fetchAsArrayBuffer = url => {
return new Promise(resolve => {
const xhr = new XMLHttpRequest();
xhr.open(`get`, url, true);
xhr.responseType = `arraybuffer`;
xhr.timeout = 2000;
xhr.ontimeout = () => { throw new Error(`Request timed out`); };
xhr.onabort = () => { throw new Error(`Request has aborted`); };
xhr.onerror = () => { throw new Error(`Cannot get the file`); };
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
resolve(xhr.response);
}
};
xhr.send();
});
};
/**
* Get image orientation from EXIF header
*
* Referenced:
* - https://gist.github.com/nezed/d536ccdace84c6f2ef13da47a8fd6bdb
* - https://gist.github.com/wonnage/9d2e73d9228f5a0b300d75babe2c3796
* - https://github.com/blueimp/JavaScript-Load-Image/blob/master/js/load-image-meta.js
*
* @param {String} imageUrl
* @return {Promise}
*/
const getImageOrientation = async imageUrl => {
try {
const arrayBuffer = await fetchAsArrayBuffer(imageUrl);
const dataView = new DataView(arrayBuffer.slice(0, 64 * 1024));
// Check for the JPEG marker
if (dataView.getUint16(0, false) !== 0xFFD8) {
throw new Error(`File is not a .jpeg`);
}
let offset = 2; // advance past the jpeg header
while (offset < dataView.byteLength) {
const marker = dataView.getUint16(offset, false);
offset += 2;
if (marker == 0xFFE1) { // APP1 marker
// ascii hex values of the string 'Exif'
if (dataView.getUint32(offset += 2, false) !== 0x45786966) {
throw new Error(`No EXIF start tag found`);
}
// Now determine the endianness of the tags
// by checking the 8 byte TIFF header
const littleEndian =
dataView.getUint16(offset += 6, false) === 0x4949;
offset += dataView.getUint32(offset + 4, littleEndian);
const tags = dataView.getUint16(offset, littleEndian);
offset += 2;
for (let i = 0; i < tags; i++) {
const pos = offset + i * 12;
const tag = dataView.getUint16(pos, littleEndian);
if (tag === 0x0112) { // orientaion tag found
return dataView.getUint16(pos + 8, littleEndian);
}
}
} else if ((marker & 0xFF00) !== 0xFF00) {
break;
} else {
offset += dataView.getUint16(offset, false);
}
}
throw new Error(`No orientation defined`);
} catch (e) {
return 0;
}
};
window.addEventListener(`load`, () => {
document.querySelectorAll(`img`).forEach(async el => {
const orientation = await getImageOrientation(el.src);
el.dataset.orientation = orientation;
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment