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

JavascriptHtmlCanvasHtml5 CanvasDrawimage

Javascript Problem Overview


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>

Javascript Solutions


Solution 1 - Javascript

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 - Javascript

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 - Javascript

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 - Javascript

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.

Solution 5 - Javascript

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRock YipView Question on Stackoverflow
Solution 1 - JavascriptBen WongView Answer on Stackoverflow
Solution 2 - JavascriptgburningView Answer on Stackoverflow
Solution 3 - JavascriptAndy LorenzView Answer on Stackoverflow
Solution 4 - JavascriptFelix TurnerView Answer on Stackoverflow
Solution 5 - JavascriptgonnavisView Answer on Stackoverflow