How to draw photo with correct orientation in canvas after capture photo by using input[type='file'] in mobile web browser?

53,868

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.

Share:
53,868
Rock Yip
Author by

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, 2022

Comments

  • Rock Yip
    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
    Rock Yip over 10 years
    It work perfectly.Thanks so much. the reading method of fileReader need to change from readAsDataURL(file) to readAsBinaryString(file)
  • gburning
    gburning almost 9 years
    Thanks for the great answer, Ben! However, I'm not quite sure the rotations are quite correct. See my answer below.
  • Ben Wong
    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
    Manik Mittal over 8 years
    Do 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
    FirstVertex over 7 years
    What this answer lacks is a link to EXIF library!
  • Shammel Lee
    Shammel Lee over 7 years
    would be a better answer if it was a complete example
  • gburning
    gburning over 7 years
    @ShammelLee Fair enough. :) Done.
  • Christopher Grigg
    Christopher Grigg over 7 years
    I'm getting an error where "BinaryFile" cannot be found, where is this coming from? It's not in the exif.js
  • Arthur Menezes
    Arthur Menezes about 7 years
    @gburning Do you have a solution for cut problem?
  • gburning
    gburning about 7 years
    @ArthurMenezes Not sure what you mean?
  • Jacob Morris
    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
    user1709076 almost 7 years
    I am getting EXIF undefined, do i need to import some kind of library? is there no standard HTML5 'library-less solution' ?
  • Josh Hibschman
    Josh Hibschman over 6 years
  • crazygringo
    crazygringo about 6 years
    I'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
    crazygringo about 6 years
    Update: I've re-verified the cut problem and filed a bug with Apple: bugreport.apple.com/web/?problemID=39809730
  • RafaelKr
    RafaelKr over 5 years
    This is the best answer in my opinion. It exactly shows how you get back the correct canvas.
  • Andy Lorenz
    Andy Lorenz over 5 years
    thanks @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
    Maros over 4 years