Access JPEG EXIF ​​data for rotation in client-side JavaScript

I would like to rotate the photos based on their initial rotation set by the camera in JPEG EXIF ​​data. The trick is that all this should happen in the browser using JavaScript and <canvas> .

How can I use JavaScript to access JPEG, a local API file, local <img> or remote <img> , EXIF ​​data to read rotation information?

Server-side responses are out of order; I am looking for a client solution .

+97
javascript html5-canvas jpeg exif
Sep 28 '11 at 14:25
source share
8 answers

If you only need an orientation tag and nothing else, and you do not want to include another huge JavaScript library, I write a little code that retrieves only the orientation tag as quickly as possible (it uses DataView and readAsArrayBuffer which are available in IE10 +, but you can write your own data reader for older browsers):

 function getOrientation(file, callback) { var reader = new FileReader(); reader.onload = function(e) { var view = new DataView(e.target.result); if (view.getUint16(0, false) != 0xFFD8) { return callback(-2); } var length = view.byteLength, offset = 2; while (offset < length) { if (view.getUint16(offset+2, false) <= 8) return callback(-1); var marker = view.getUint16(offset, false); offset += 2; if (marker == 0xFFE1) { if (view.getUint32(offset += 2, false) != 0x45786966) { return callback(-1); } var little = view.getUint16(offset += 6, false) == 0x4949; offset += view.getUint32(offset + 4, little); var tags = view.getUint16(offset, little); offset += 2; for (var i = 0; i < tags; i++) { if (view.getUint16(offset + (i * 12), little) == 0x0112) { return callback(view.getUint16(offset + (i * 12) + 8, little)); } } } else if ((marker & 0xFF00) != 0xFF00) { break; } else { offset += view.getUint16(offset, false); } } return callback(-1); }; reader.readAsArrayBuffer(file); } // usage: var input = document.getElementById('input'); input.onchange = function(e) { getOrientation(input.files[0], function(orientation) { alert('orientation: ' + orientation); }); } 
 <input id='input' type='file' /> 

values:

 -2: not jpeg -1: not defined 

enter image description here

For those using Typescript, you can use the following code:

 export const getOrientation = (file: File, callback: Function) => { var reader = new FileReader(); reader.onload = (event: ProgressEvent) => { if (! event.target) { return; } const file = event.target as FileReader; const view = new DataView(file.result as ArrayBuffer); if (view.getUint16(0, false) != 0xFFD8) { return callback(-2); } const length = view.byteLength let offset = 2; while (offset < length) { if (view.getUint16(offset+2, false) <= 8) return callback(-1); let marker = view.getUint16(offset, false); offset += 2; if (marker == 0xFFE1) { if (view.getUint32(offset += 2, false) != 0x45786966) { return callback(-1); } let little = view.getUint16(offset += 6, false) == 0x4949; offset += view.getUint32(offset + 4, little); let tags = view.getUint16(offset, little); offset += 2; for (let i = 0; i < tags; i++) { if (view.getUint16(offset + (i * 12), little) == 0x0112) { return callback(view.getUint16(offset + (i * 12) + 8, little)); } } } else if ((marker & 0xFF00) != 0xFF00) { break; } else { offset += view.getUint16(offset, false); } } return callback(-1); }; reader.readAsArrayBuffer(file); } 
+196
Sep 09 '15 at 23:18
source share

You can use the exif-js library in conjunction with the HTML5 file API: http://jsfiddle.net/xQnMd/1/ .

 $("input").change(function() { var file = this.files[0]; // file fr = new FileReader; // to read file contents fr.onloadend = function() { // get EXIF data var exif = EXIF.readFromBinaryFile(new BinaryFile(this.result)); // alert a value alert(exif.Make); }; fr.readAsBinaryString(file); // read the file }); 
+20
Sep 28 2018-11-11T00:
source share

Firefox 26 supports image-orientation: from-image : images are displayed in portrait or landscape image-orientation: from-image , depending on EXIF ​​data. (See Sethfowler.org/blog/2013/09/13/new-in-firefox-26-css-image-orientation .)

There is also a bug to implement this in Chrome .

Remember that this property is supported only by Firefox and is most likely not recommended for use .

+17
Sep 20 '13 at 9:09
source share

https://github.com/blueimp/JavaScript-Load-Image is a modern javascript library that can not only extract the exif orientation flag - it can also correctly display / rotate JPEG images on the client side.

I just solved the same problem with this library: Exif client-side orientation JS: rotate and mirror images in JPEG format

+11
Dec 15 '13 at 22:47
source share

If you need a cross browser, it is best to do this on the server. You may have an API that accepts the file URL and returns EXIF ​​data; PHP has a module for this .

This can be done using Ajax so that it is seamless for the user. If you don't need cross-browser compatibility and you can rely on HTML5 , look in the JsJPEGmeta library, which will allow you to get this data in native JavaScript.

+4
Sep 28 '11 at 14:28
source share

Check out the module I wrote (you can use it in a browser) that converts exif orientation to CSS conversion: https://github.com/Sobesednik/exif2css

There is also a node program for creating JPEG fixtures with all orientations: https://github.com/Sobesednik/generate-exif-fixtures

+3
Jan 6 '17 at 5:43 on
source share

I am loading the extension code to show the photo from the android camera in html, as usual, on some img tag with the correct rotaion, especially for the img tag, whose width is wider than the height. I know this code is ugly, but you do not need to install any other packages. (I used the code above to get the exif rotation value, thanks.)

 function getOrientation(file, callback) { var reader = new FileReader(); reader.onload = function(e) { var view = new DataView(e.target.result); if (view.getUint16(0, false) != 0xFFD8) return callback(-2); var length = view.byteLength, offset = 2; while (offset < length) { var marker = view.getUint16(offset, false); offset += 2; if (marker == 0xFFE1) { if (view.getUint32(offset += 2, false) != 0x45786966) return callback(-1); var little = view.getUint16(offset += 6, false) == 0x4949; offset += view.getUint32(offset + 4, little); var tags = view.getUint16(offset, little); offset += 2; for (var i = 0; i < tags; i++) if (view.getUint16(offset + (i * 12), little) == 0x0112) return callback(view.getUint16(offset + (i * 12) + 8, little)); } else if ((marker & 0xFF00) != 0xFF00) break; else offset += view.getUint16(offset, false); } return callback(-1); }; reader.readAsArrayBuffer(file); } var isChanged = false; function rotate(elem, orientation) { if (isIPhone()) return; var degree = 0; switch (orientation) { case 1: degree = 0; break; case 2: degree = 0; break; case 3: degree = 180; break; case 4: degree = 180; break; case 5: degree = 90; break; case 6: degree = 90; break; case 7: degree = 270; break; case 8: degree = 270; break; } $(elem).css('transform', 'rotate('+ degree +'deg)') if(degree == 90 || degree == 270) { if (!isChanged) { changeWidthAndHeight(elem) isChanged = true } } else if ($(elem).css('height') > $(elem).css('width')) { if (!isChanged) { changeWidthAndHeightWithOutMargin(elem) isChanged = true } else if(degree == 180 || degree == 0) { changeWidthAndHeightWithOutMargin(elem) if (!isChanged) isChanged = true else isChanged = false } } } function changeWidthAndHeight(elem){ var e = $(elem) var width = e.css('width') var height = e.css('height') e.css('width', height) e.css('height', width) e.css('margin-top', ((getPxInt(height) - getPxInt(width))/2).toString() + 'px') e.css('margin-left', ((getPxInt(width) - getPxInt(height))/2).toString() + 'px') } function changeWidthAndHeightWithOutMargin(elem){ var e = $(elem) var width = e.css('width') var height = e.css('height') e.css('width', height) e.css('height', width) e.css('margin-top', '0') e.css('margin-left', '0') } function getPxInt(pxValue) { return parseInt(pxValue.trim("px")) } function isIPhone(){ return ( (navigator.platform.indexOf("iPhone") != -1) || (navigator.platform.indexOf("iPod") != -1) ); } 

and then use for example

 $("#banner-img").change(function () { var reader = new FileReader(); getOrientation(this.files[0], function(orientation) { rotate($('#banner-img-preview'), orientation, 1) }); reader.onload = function (e) { $('#banner-img-preview').attr('src', e.target.result) $('#banner-img-preview').css('display', 'inherit') }; // read the image file as a data URL. reader.readAsDataURL(this.files[0]); }); 
+3
Apr 04 '17 at 8:30
source share

Improving / adding more functionality to Ali's answer from earlier, I created a util method in Typescript that met my needs for this problem. This version returns the rotation in degrees that your project may also need.

 ImageUtils.ts /** * Based on StackOverflow answer: https://stackoverflow.com/a/32490603 * * @param imageFile The image file to inspect * @param onRotationFound callback when the rotation is discovered. Will return 0 if if it fails, otherwise 0, 90, 180, or 270 */ export function getOrientation(imageFile: File, onRotationFound: (rotationInDegrees: number) => void) { const reader = new FileReader(); reader.onload = (event: ProgressEvent) => { if (!event.target) { return; } const innerFile = event.target as FileReader; const view = new DataView(innerFile.result as ArrayBuffer); if (view.getUint16(0, false) !== 0xffd8) { return onRotationFound(convertRotationToDegrees(-2)); } const length = view.byteLength; let offset = 2; while (offset < length) { if (view.getUint16(offset + 2, false) <= 8) { return onRotationFound(convertRotationToDegrees(-1)); } const marker = view.getUint16(offset, false); offset += 2; if (marker === 0xffe1) { if (view.getUint32((offset += 2), false) !== 0x45786966) { return onRotationFound(convertRotationToDegrees(-1)); } const little = view.getUint16((offset += 6), false) === 0x4949; offset += view.getUint32(offset + 4, little); const tags = view.getUint16(offset, little); offset += 2; for (let i = 0; i < tags; i++) { if (view.getUint16(offset + i * 12, little) === 0x0112) { return onRotationFound(convertRotationToDegrees(view.getUint16(offset + i * 12 + 8, little))); } } // tslint:disable-next-line:no-bitwise } else if ((marker & 0xff00) !== 0xff00) { break; } else { offset += view.getUint16(offset, false); } } return onRotationFound(convertRotationToDegrees(-1)); }; reader.readAsArrayBuffer(imageFile); } /** * Based off snippet here: https://github.com/mosch/react-avatar-editor/issues/123#issuecomment-354896008 * @param rotation converts the int into a degrees rotation. */ function convertRotationToDegrees(rotation: number): number { let rotationInDegrees = 0; switch (rotation) { case 8: rotationInDegrees = 270; break; case 6: rotationInDegrees = 90; break; case 3: rotationInDegrees = 180; break; default: rotationInDegrees = 0; } return rotationInDegrees; } 

Using:

 import { getOrientation } from './ImageUtils'; ... onDrop = (pics: any) => { getOrientation(pics[0], rotationInDegrees => { this.setState({ image: pics[0], rotate: rotationInDegrees }); }); }; 
+1
Oct 26 '18 at 21:14
source share



All Articles