70

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
  • 713
  • 1
  • 6
  • 7

5 Answers5

46

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;


    }
};
Ben Wong
  • 1,987
  • 2
  • 19
  • 20
43

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;


    }
};
Community
  • 1
  • 1
gburning
  • 486
  • 6
  • 19
  • 2
    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: http://stackoverflow.com/questions/32968805/javascript-exif-based-rotation-using-canvas-element – Manik Mittal Oct 06 '15 at 11:32
  • 4
    would be a better answer if it was a complete example – Shammel Lee Sep 23 '16 at 17:04
  • 1
    @ShammelLee Fair enough. :) Done. – gburning Sep 28 '16 at 12:02
  • 1
    @gburning Do you have a solution for cut problem? – Arthur Menezes Feb 08 '17 at 12:09
  • @ArthurMenezes Not sure what you mean? – gburning Feb 13 '17 at 13:17
  • 2
    @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! – Jacob Morris Apr 13 '17 at 15:15
  • 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 from that to then rotate. But just ugh. – crazygringo Apr 27 '18 at 21:11
  • Update: I've re-verified the cut problem and filed a bug with Apple: https://bugreport.apple.com/web/?problemID=39809730 – crazygringo Apr 28 '18 at 00:12
  • This worked for me https://github.com/koba04/canvas-exif-orientation – Maros Dec 14 '19 at 21:49
23

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.

Andy Lorenz
  • 2,905
  • 1
  • 29
  • 29
  • This is the best answer in my opinion. It exactly shows how you get back the correct canvas. – RafaelKr Jan 24 '19 at 14:47
  • 1
    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. – Andy Lorenz Jan 24 '19 at 16:21
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.

Felix Turner
  • 1,345
  • 1
  • 9
  • 8
0

Based on your answers, I created a function to auto rotate iphone photo to right direction.
Just pass in an input.files[0] and an optional max width or height, it'll output a blob used for form submit.
https://github.com/gonnavis/iphone_photo_rotation_adjust

gonnavis
  • 336
  • 1
  • 7
  • 16