How to draw photo with correct orientation in canvas after capture photo by using input[type='file'] in mobile web browser?
Solution 1
You'll need to read the exif data and check if exif.Orientation is one of the following:
fileReader.onloadend = function() {
var exif = EXIF.readFromBinaryFile(new BinaryFile(this.result));
switch(exif.Orientation){
case 8:
ctx.rotate(90*Math.PI/180);
break;
case 3:
ctx.rotate(180*Math.PI/180);
break;
case 6:
ctx.rotate(-90*Math.PI/180);
break;
}
};
Solution 2
Ben's great answer pointed me in the right direction but as far as I can tell the actual rotations are incorrect (at least they were for me) and don't cover all possible cases. The solution below worked for me. It is based on the one found in the JavaScript-Load-Image library (which I found via this great SO question). Note that I also had to translate the Canvas context to the center as it originates from the top left corner when rotating).
fileReader.onloadend = function() {
var exif = EXIF.readFromBinaryFile(new BinaryFile(this.result));
switch(exif.Orientation){
case 2:
// horizontal flip
ctx.translate(canvas.width, 0);
ctx.scale(-1, 1);
break;
case 3:
// 180° rotate left
ctx.translate(canvas.width, canvas.height);
ctx.rotate(Math.PI);
break;
case 4:
// vertical flip
ctx.translate(0, canvas.height);
ctx.scale(1, -1);
break;
case 5:
// vertical flip + 90 rotate right
ctx.rotate(0.5 * Math.PI);
ctx.scale(1, -1);
break;
case 6:
// 90° rotate right
ctx.rotate(0.5 * Math.PI);
ctx.translate(0, -canvas.height);
break;
case 7:
// horizontal flip + 90 rotate right
ctx.rotate(0.5 * Math.PI);
ctx.translate(canvas.width, -canvas.height);
ctx.scale(-1, 1);
break;
case 8:
// 90° rotate left
ctx.rotate(-0.5 * Math.PI);
ctx.translate(-canvas.width, 0);
break;
}
};
Solution 3
add exif.js to your project, then:
EXIF.getData(file,function() {
var orientation = EXIF.getTag(this,"Orientation");
var can = document.createElement("canvas");
var ctx = can.getContext('2d');
var thisImage = new Image;
thisImage.onload = function() {
can.width = thisImage.width;
can.height = thisImage.height;
ctx.save();
var width = can.width; var styleWidth = can.style.width;
var height = can.height; var styleHeight = can.style.height;
if (orientation) {
if (orientation > 4) {
can.width = height; can.style.width = styleHeight;
can.height = width; can.style.height = styleWidth;
}
switch (orientation) {
case 2: ctx.translate(width, 0); ctx.scale(-1,1); break;
case 3: ctx.translate(width,height); ctx.rotate(Math.PI); break;
case 4: ctx.translate(0,height); ctx.scale(1,-1); break;
case 5: ctx.rotate(0.5 * Math.PI); ctx.scale(1,-1); break;
case 6: ctx.rotate(0.5 * Math.PI); ctx.translate(0,-height); break;
case 7: ctx.rotate(0.5 * Math.PI); ctx.translate(width,-height); ctx.scale(-1,1); break;
case 8: ctx.rotate(-0.5 * Math.PI); ctx.translate(-width,0); break;
}
}
ctx.drawImage(thisImage,0,0);
ctx.restore();
var dataURL = can.toDataURL();
// at this point you can save the image away to your back-end using 'dataURL'
}
// now trigger the onload function by setting the src to your HTML5 file object (called 'file' here)
thisImage.src = URL.createObjectURL(file);
});
The orientation block (using translate and rotate) is copied from https://github.com/blueimp/JavaScript-Load-Image/blob/master/js/load-image-orientation.js and so I consider it well proven. It certainly worked perfectly for me, whereas other approaches didn't.
Solution 4
If you just want the Orientation tag, using exif.js:
EXIF.getData(file, function () {
alert(this.exifdata.Orientation);
});
In my tests, iOS camera only returns 1,3,6 or 8.
Rock Yip
I am a facebook app developer know HTML,CSS,Javascript Actionscript 3.0 some PHP,C++,objective C like to read code =]
Updated on July 05, 2022Comments
-
Rock Yip almost 2 years
I am making a simple web app in mobile which allow visitor to capture photo by using html5 input[type=file] element. Then I will display it on the web for preview, and then visitor can choose to upload the photo to my server for other purpose(ie: upload to FB)
I find a problem on the orientation of photo when I take photo using my iPhone and hold vertically.The photo is in a correct orientation in tag. However, when I try to draw it into canvas by using drawImage() method, it is drawn 90 degree rotated.
I have tried to take photo in 4 orientations, only one of them can draw a correct image in canvas, others are rotated or even flipped upside down.
Well, I am confused to get the correct orientation to fix this problem... Thanks for helping...
here is my code, mostly copy from MDN
<div class="container"> <h1>Camera API</h1> <section class="main-content"> <p>A demo of the Camera API, currently implemented in Firefox and Google Chrome on Android. Choose to take a picture with your device's camera and a preview will be shown through createObjectURL or a FileReader object (choosing local files supported too).</p> <p> <form method="post" enctype="multipart/form-data" action="index.php"> <input type="file" id="take-picture" name="image" accept="image/*"> <input type="hidden" name="action" value="submit"> <input type="submit" > </form> </p> <h2>Preview:</h2> <div style="width:100%;max-width:320px;"> <img src="about:blank" alt="" id="show-picture" width="100%"> </div> <p id="error"></p> <canvas id="c" width="640" height="480"></canvas> </section> </div> <script> (function () { var takePicture = document.querySelector("#take-picture"), showPicture = document.querySelector("#show-picture"); if (takePicture && showPicture) { // Set events takePicture.onchange = function (event) { showPicture.onload = function(){ var canvas = document.querySelector("#c"); var ctx = canvas.getContext("2d"); ctx.drawImage(showPicture,0,0,showPicture.width,showPicture.height); } // Get a reference to the taken picture or chosen file var files = event.target.files, file; if (files && files.length > 0) { file = files[0]; try { // Get window.URL object var URL = window.URL || window.webkitURL; // Create ObjectURL var imgURL = URL.createObjectURL(file); // Set img src to ObjectURL showPicture.src = imgURL; // Revoke ObjectURL URL.revokeObjectURL(imgURL); } catch (e) { try { // Fallback if createObjectURL is not supported var fileReader = new FileReader(); fileReader.onload = function (event) { showPicture.src = event.target.result; }; fileReader.readAsDataURL(file); } catch (e) { // Display error message var error = document.querySelector("#error"); if (error) { error.innerHTML = "Neither createObjectURL or FileReader are supported"; } } } } }; } })(); </script>
-
Rock Yip over 10 yearsIt work perfectly.Thanks so much. the reading method of fileReader need to change from readAsDataURL(file) to readAsBinaryString(file)
-
gburning almost 9 yearsThanks for the great answer, Ben! However, I'm not quite sure the rotations are quite correct. See my answer below.
-
Ben Wong over 8 years@gburning I do agree that your answer below is much better and covers most scenarios! Please use the answer below instead of mine.
-
Manik Mittal over 8 yearsDo the solutions proposed by Ben Wong and gburning work with all types of images viz. Portrait, Landscape... I am facing mainly the issue with Portrait images with EXIF.Orientation=6. The images are getting cut from Top or bottom. I tried these options with multiple values in the ctx.drawImage method: ctx.drawImage(img, 0,0); ctx.drawImage(img, -img.width/2,-img.length/2); ctx.drawImage(img, 0,-img.length/2); ctx.drawImage(img, -img.width/2,0); but couldnt get it right. Added the new question here: stackoverflow.com/questions/32968805/…
-
FirstVertex over 7 yearsWhat this answer lacks is a link to EXIF library!
-
Shammel Lee over 7 yearswould be a better answer if it was a complete example
-
gburning over 7 years@ShammelLee Fair enough. :) Done.
-
Christopher Grigg over 7 yearsI'm getting an error where "BinaryFile" cannot be found, where is this coming from? It's not in the exif.js
-
Arthur Menezes about 7 years@gburning Do you have a solution for cut problem?
-
gburning about 7 years@ArthurMenezes Not sure what you mean?
-
Jacob Morris about 7 years@ArthurMenezes my portrait images were being cut from top or bottom, as well. I resolved the issue by adding "canvas.height = width;" to cases 5 - 8. Thanks!
-
user1709076 almost 7 yearsI am getting EXIF undefined, do i need to import some kind of library? is there no standard HTML5 'library-less solution' ?
-
Josh Hibschman over 6 years@ChristopherGrigg github.com/jseidelin/binaryajax/blob/master/binaryajax.js
-
crazygringo about 6 yearsI'm still unable to resolve the cut problem. After much experimentation, as far as I can tell the problem is that when calling drawImage() on an EXIF-rotated image, mobile Safari reads the pixels pre-rotation, but only to a maximum dimension allowed post-rotation. The result is that you can only ever copy maximum dimensions of a square. This seems like a clear mobile Safari bug, but I can't find any reference to it anywhere online. The only workaround I can imagine is to read the image in base64, manually strip the EXIF data, and recreate an <img> from that to then rotate. But just ugh.
-
crazygringo about 6 yearsUpdate: I've re-verified the cut problem and filed a bug with Apple: bugreport.apple.com/web/?problemID=39809730
-
RafaelKr over 5 yearsThis is the best answer in my opinion. It exactly shows how you get back the correct canvas.
-
Andy Lorenz over 5 yearsthanks @RafaelKr - I always try to post complete solutions, not snippets that give you just a glimpse of how to do something but leave you with other questions.
-
Maros over 4 yearsThis worked for me github.com/koba04/canvas-exif-orientation