-
-
Save runeb/c11f864cd7ead969a5f0 to your computer and use it in GitHub Desktop.
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> | |
<input id="file" type="file" accept="image/*" /> | |
<br/> | |
<h2>As read:</h2> | |
<img id="placeholder1" width=300/><br/> | |
<h2>Rotated by exif data:</h2> | |
<img id="placeholder2" width=300/> | |
<script> | |
// Exif orientation value to css transform mapping | |
// Does not include flipped orientations | |
var rotation = { | |
1: 'rotate(0deg)', | |
3: 'rotate(180deg)', | |
6: 'rotate(90deg)', | |
8: 'rotate(270deg)' | |
}; | |
function _arrayBufferToBase64( buffer ) { | |
var binary = '' | |
var bytes = new Uint8Array( buffer ) | |
var len = bytes.byteLength; | |
for (var i = 0; i < len; i++) { | |
binary += String.fromCharCode( bytes[ i ] ) | |
} | |
return window.btoa( binary ); | |
} | |
var orientation = function(file, callback) { | |
var fileReader = new FileReader(); | |
fileReader.onloadend = function() { | |
var base64img = "data:"+file.type+";base64," + _arrayBufferToBase64(fileReader.result); | |
var scanner = new DataView(fileReader.result); | |
var idx = 0; | |
var value = 1; // Non-rotated is the default | |
if(fileReader.result.length < 2 || scanner.getUint16(idx) != 0xFFD8) { | |
// Not a JPEG | |
if(callback) { | |
callback(base64img, value); | |
} | |
return; | |
} | |
idx += 2; | |
var maxBytes = scanner.byteLength; | |
while(idx < maxBytes - 2) { | |
var uint16 = scanner.getUint16(idx); | |
idx += 2; | |
switch(uint16) { | |
case 0xFFE1: // Start of EXIF | |
var exifLength = scanner.getUint16(idx); | |
maxBytes = exifLength - idx; | |
idx += 2; | |
break; | |
case 0x0112: // Orientation tag | |
// Read the value, its 6 bytes further out | |
// See page 102 at the following URL | |
// http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf | |
value = scanner.getUint16(idx + 6, false); | |
maxBytes = 0; // Stop scanning | |
break; | |
} | |
} | |
if(callback) { | |
callback(base64img, value); | |
} | |
} | |
fileReader.readAsArrayBuffer(file); | |
}; | |
$(function() { | |
$('#file').change(function() { | |
var file = $(this)[0].files[0]; | |
if(file) { | |
orientation(file, function(base64img, value) { | |
$('#placeholder1').attr('src', base64img); | |
console.log(rotation[value]); | |
var rotated = $('#placeholder2').attr('src', base64img); | |
if(value) { | |
rotated.css('transform', rotation[value]); | |
} | |
}); | |
} | |
}); | |
}); | |
</script> |
This post solves some issues with EXIF rotation with Samsung or iPhone:
https://stackoverflow.com/questions/46354195/exif-data-from-mobiles-cameras-in-portrait-mode-messing-with-image-preview-i
I'm having the same issue as tamihelcich the fiddle doesn't seem to work at all.
The code above worked for some images but not others. I tracked it down to the byte ordering
and added in a check for the 'endian-ness' of the Exif data.
var orientation = function (file, callback) {
var fileReader = new FileReader();
fileReader.onloadend = function () {
var base64img = "data:" + file.type + ";base64," + _arrayBufferToBase64(fileReader.result);
var scanner = new DataView(fileReader.result);
var idx = 0;
var value = 1; // Non-rotated is the default
if (fileReader.result.length < 2 || scanner.getUint16(idx) != 0xFFD8) {
// Not a JPEG
if (callback) {
callback(base64img, value);
}
return;
}
idx += 2;
var maxBytes = scanner.byteLength;
var littleEndian = false;
while (idx < maxBytes - 2) {
var uint16 = scanner.getUint16(idx, littleEndian);
idx += 2;
switch (uint16) {
case 0xFFE1: // Start of EXIF
var endianNess = scanner.getUint16(idx + 8);
// II (0x4949) Indicates Intel format - Little Endian
// MM (0x4D4D) Indicates Motorola format - Big Endian
if (endianNess === 0x4949) {
littleEndian = true;
}
var exifLength = scanner.getUint16(idx, littleEndian);
maxBytes = exifLength - idx;
idx += 2;
break;
case 0x0112: // Orientation tag
// Read the value, its 6 bytes further out
// See page 102 at the following URL
// http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf
value = scanner.getUint16(idx + 6, littleEndian);
maxBytes = 0; // Stop scanning
break;
}
}
if (callback) {
callback(base64img, value);
}
}
fileReader.readAsArrayBuffer(file);
};
The code above worked for some images but not others. I tracked it down to the byte ordering
and added in a check for the 'endian-ness' of the Exif data.var orientation = function (file, callback) { var fileReader = new FileReader(); fileReader.onloadend = function () { var base64img = "data:" + file.type + ";base64," + _arrayBufferToBase64(fileReader.result); var scanner = new DataView(fileReader.result); var idx = 0; var value = 1; // Non-rotated is the default if (fileReader.result.length < 2 || scanner.getUint16(idx) != 0xFFD8) { // Not a JPEG if (callback) { callback(base64img, value); } return; } idx += 2; var maxBytes = scanner.byteLength; var littleEndian = false; while (idx < maxBytes - 2) { var uint16 = scanner.getUint16(idx, littleEndian); idx += 2; switch (uint16) { case 0xFFE1: // Start of EXIF var endianNess = scanner.getUint16(idx + 8); // II (0x4949) Indicates Intel format - Little Endian // MM (0x4D4D) Indicates Motorola format - Big Endian if (endianNess === 0x4949) { littleEndian = true; } var exifLength = scanner.getUint16(idx, littleEndian); maxBytes = exifLength - idx; idx += 2; break; case 0x0112: // Orientation tag // Read the value, its 6 bytes further out // See page 102 at the following URL // http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf value = scanner.getUint16(idx + 6, littleEndian); maxBytes = 0; // Stop scanning break; } } if (callback) { callback(base64img, value); } } fileReader.readAsArrayBuffer(file); };
thanks to author and you
There is another issue in line 53 of the original implementation:
maxBytes = exifLength - idx;
maxBytes
is relative to the whole file and idx
is the offset within this file, where we detected the beginning of the EXIF header. We need to add the exifLength
to the current offset to get the proper end:
maxBytes = exifLength + idx;
@tamihelcich / @nicolassaad Any chance you still have the images you were trying so I can test if this fixes the issue? 🙂
Thanks a lot for this gist @runeb and for the fixes @kedrovski! I used both as the base for a new, class-based version.
Improvements:
- Replaced the callback with async/await
- Handles all 8 possible orientations
- Made the base64 part optional (it might not be needed for everyone—I didn't need it).
- Can return the raw orientation value (1–8)
- Replaced the loop with a functional programming-based solution
- I broke up the code to smaller methods to make it a bit more documented and easier to modify.
Here you go, I hope someone finds it useful: https://gist.github.com/vdavid/3f9b66b60f52204317a4cc0e77097913
I tried the fiddle for this: the image I am using is indeed a jpeg image with exif data from my computer after being pulled from the camera. The image did not get rotated. I am getting the results of value 1 in the console, so I'm thinking it doesn't know its a jpeg, or maybe something else?