在移动网络浏览器中使用input[type='file']拍摄照片后,在画布上正确地绘制照片的方向是什么?

71

我正在制作一个简单的移动端 Web 应用程序,让访问者可以使用 html5 input[type=file] 元素捕获照片。然后我将在网页上显示预览,访问者可以选择将照片上传到我的服务器用于其他目的(如上传到 FB)。

我发现一个问题是当我使用 iPhone 拍摄并垂直拿着手机时,照片在标签中的方向是正确的。 然而,当我尝试使用 drawImage() 方法将其绘制到画布中时,它会被旋转 90 度。

我已经尝试了四个方向拍摄照片,只有其中一个方向可以正确地绘制图像到画布中,其他方向都会被旋转或甚至翻转。

我很困惑,不知道如何获取正确的方向来解决这个问题... 谢谢帮忙...

以下是我的代码,大部分从 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>
5个回答

46
您需要阅读exif数据并检查exif.Orientation是否为以下之一:
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;


    }
};

1
它完美地工作了。非常感谢。 需要将fileReader的读取方法从readAsDataURL(file)更改为readAsBinaryString(file)。 - Rock Yip
2
感谢您的出色答案,Ben!但我不太确定旋转是否完全正确。请看下面我的答案。 - gburning
1
@gburning,我确实同意你下面的答案更好,覆盖了大多数情况!请使用下面的答案而不是我的。 - Ben Wong
27
这个答案缺少一个EXIF库的链接! - FirstVertex
10
我遇到了一个错误,提示“BinaryFile”找不到,这是什么原因?它不在exif.js文件中吗? - Christopher Grigg
显示剩余2条评论

43

Ben的精彩答案指引了我方向,但据我所知实际旋转是不正确的(至少对于我来说是这样),并且不能覆盖所有可能的情况。下面的解决方案适合我。它基于在JavaScript-Load-Image库中找到的一个解决方案(我通过这个伟大的SO问题找到了它)。请注意,我还必须将Canvas上下文平移至中心,因为它在旋转时起点是左上角。

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;


    }
};

2
Ben Wong和gburning提出的解决方案是否适用于所有类型的图像,例如纵向或横向?我主要遇到了EXIF.Orientation = 6的纵向图像问题。图像从顶部或底部被裁剪。我尝试了ctx.drawImage方法中多个值的这些选项: 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); 但是无法得到正确的结果。在此添加了新问题:http://stackoverflow.com/questions/32968805/javascript-exif-based-rotation-using-canvas-element - Manik Mittal
4
如果提供一个完整的例子,答案将更好。 - Shammel Lee
1
@ShammelLee 好的,没问题。 :) 完成了。 - gburning
1
@gburning,你有解决切割问题的方案吗? - Arthur Menezes
2
@ArthurMenezes 我的肖像图片也被从顶部或底部裁剪了。我通过在5-8个案例中添加“canvas.height = width;”来解决了这个问题。谢谢! - Jacob Morris
显示剩余4条评论

23

exif.js添加到您的项目中,然后:

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);

});

方向块(使用翻译和旋转)是从https://github.com/blueimp/JavaScript-Load-Image/blob/master/js/load-image-orientation.js复制的,因此我认为它已经得到了很好的证明。对我来说,它肯定完美地工作了,而其他方法则没有。


在我看来,这是最好的答案。它准确地展示了如何获取正确的画布。 - RafaelKr
1
感谢 @RafaelKr - 我总是尽力发布完整的解决方案,而不仅仅是一些片段,这些片段只能让你对如何做某事有一瞥,却留下其他问题。 - Andy Lorenz

4

如果你只想要方向标签,可以使用exif.js

EXIF.getData(file, function () {
    alert(this.exifdata.Orientation);
});

在我的测试中,iOS相机只返回1、3、6或8。

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接