在移动网络浏览器中使用 input[type='file'] 捕获照片后,如何在画布中以正确的方向绘制照片?

Posted

技术标签:

【中文标题】在移动网络浏览器中使用 input[type=\'file\'] 捕获照片后,如何在画布中以正确的方向绘制照片?【英文标题】:How to draw photo with correct orientation in canvas after capture photo by using input[type='file'] in mobile web browser?在移动网络浏览器中使用 input[type='file'] 捕获照片后,如何在画布中以正确的方向绘制照片? 【发布时间】:2013-10-28 02:12:30 【问题描述】:

我正在制作一个简单的移动网络应用程序,允许访问者使用 html5 input[type=file] 元素捕获照片。然后我会把它展示在网上预览,然后访客可以选择将照片上传到我的服务器以用于其他目的(即:上传到FB)

当我使用 iPhone 拍照并垂直握住时,我发现照片的方向有问题。照片在标签中的方向正确。 但是,当我尝试使用 drawImage() 方法将其绘制到画布中时,它被旋转了 90 度。

我尝试从 4 个方向拍照,其中只有一个可以在画布上绘制正确的图像,其他的可以旋转甚至倒置。

嗯,我很困惑获得正确的方向来解决这个问题...... 感谢您的帮助...

这是我的代码,主要是从 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"  id="show-picture" >
                </div>

                <p id="error"></p>
                <canvas id="c"  ></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>

【问题讨论】:

【参考方案1】:

您需要读取 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;


    
;

【讨论】:

完美运行。非常感谢。 fileReader 的读取方法需要从 readAsDataURL(file) 改为 readAsBinaryString(file) 感谢您的出色回答,本!但是,我不太确定旋转是否完全正确。请参阅下面的答案。 @gburning 我同意您在下面的回答要好得多,并且涵盖了大多数情况!请使用下面的答案而不是我的答案。 这个答案缺少的是 EXIF 库的链接! 我收到一个错误,找不到“BinaryFile”,这是从哪里来的?它不在 exif.js 中【参考方案2】:

Ben 的出色回答为我指明了正确的方向,但据我所知,实际旋转是不正确的(至少它们对我来说是正确的)并且没有涵盖所有可能的情况。下面的解决方案对我有用。它基于在javascript-Load-Image library(我通过this great SO question 找到)中找到的那个。请注意,我还必须将 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;


    
;

【讨论】:

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);但无法正确处理。在此处添加了新问题:***.com/questions/32968805/… 如果它是一个完整的例子会是一个更好的答案 @ShammelLee 够公平的。 :) 完成。 @gburning 你有解决切割问题的方法吗? @ArthurMenezes 我的肖像图像也被从顶部或底部剪切。我通过添加“canvas.height = width;”解决了这个问题案例 5 - 8。谢谢!【参考方案3】:

将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 - 我总是尝试发布完整的解决方案,而不是让您瞥见如何做某事但给您留下其他问题的 sn-ps。【参考方案4】:

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

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

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

【讨论】:

【参考方案5】:

根据您的回答,我创建了一个自动将 iphone 照片旋转到正确方向的功能。 只需传入一个 input.files[0] 和一个可选的最大宽度或高度,它就会输出一个用于表单提交的 blob。https://github.com/gonnavis/iphone_photo_rotation_adjust

【讨论】:

以上是关于在移动网络浏览器中使用 input[type='file'] 捕获照片后,如何在画布中以正确的方向绘制照片?的主要内容,如果未能解决你的问题,请参考以下文章

使 Input Type="Password" 在移动设备上使用数字键盘

在 Rails 中使用“f.input 集合”的 onChange 方法

笔记移动端H5数字键盘input type=number的处理(IOS和Android)

如何在移动网络浏览器上播放 flv 视频? [复制]

html input type=file -- 从 iCloud 中选择一个文件

input type=file上传控件老问题