I'd like to rotate photos based on their original rotation, as set by the camera in JPEG EXIF image data. The trick is that all this should happen in the browser, using JavaScript and <canvas>
.
How could JavaScript access JPEG, a local file API object, local <img>
or remote <img>
, EXIF data to read the rotation information?
Server-side answers are not OK; I am looking for a client-side solution.
This question is related to
javascript
jpeg
exif
html5-canvas
You can use the exif-js library in combination 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
});
Firefox 26 supports image-orientation: from-image
: images are displayed portrait or landscape, 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.
Beware that this property is only supported by Firefox and is likely to be deprecated.
If you want it cross-browser, your best bet is to do it on the server. You could have an API that takes a file URL and returns you the EXIF data; PHP has a module for that.
This could be done using Ajax so it would be seamless to the user. If you don't care about cross-browser compatibility, and can rely on HTML5 file functionality, look into the library JsJPEGmeta that will allow you to get that data in native JavaScript.
Check out a module I've written (you can use it in browser) which converts exif orientation to CSS transform: https://github.com/Sobesednik/exif2css
There is also this node program to generate JPEG fixtures with all orientations: https://github.com/Sobesednik/generate-exif-fixtures
Improving / Adding more functionality to Ali's answer from earlier, I created a util method in Typescript that suited my needs for this issue. This version returns rotation in degrees that you might also need for your project.
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;
}
Usage:
import { getOrientation } from './ImageUtils';
...
onDrop = (pics: any) => {
getOrientation(pics[0], rotationInDegrees => {
this.setState({ image: pics[0], rotate: rotationInDegrees });
});
};
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 mirror/rotate JPEG images on the client side.
I just solved the same problem with this library: JS Client-Side Exif Orientation: Rotate and Mirror JPEG Images
I upload expansion code to show photo by android camera on html as normal on some img tag with right rotaion, especially for img tag whose width is wider than height. I know this code is ugly but you don't need to install any other packages. (I used above code to obtain exif rotation value, Thank you.)
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 such as
$("#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]);
});
Source: Stackoverflow.com