JS 客户端 Exif 方向:旋转和镜像 JPEG 图像

IT技术 javascript rotation html5-canvas exif
2021-01-20 18:09:31

数码相机照片通常保存为带有 EXIF“方向”标签的 JPEG。为了正确显示,需要根据设置的方向旋转/镜像图像,但浏览器会忽略渲染图像的此信息。即使在大型商业网络应用程序中,对 EXIF 方向的支持也可能参差不齐1同一来源还 对 JPEG 可以具有8 个不同方向进行了很好的总结

EXIF 方向总结

示例图像可在4 处获得

问题是如何在客户端旋转/镜像图像以使其正确显示并在必要时可以进一步处理?

有一些 JS 库可用于解析 EXIF 数据,包括方向属性2Flickr 指出在解析大图像时可能存在性能问题,需要使用网络工作者3

控制台工具可以正确地重新定向图像5解决问题的 PHP 脚本在6可用

6个回答

github 项目JavaScript-Load-Image为 EXIF 方向问题提供了完整的解决方案,正确旋转/镜像所有 8 个 exif 方向的图像。参见javascript exif 方向的在线演示

图像被绘制到 HTML5 画布上。它的正确渲染是通过canvas操作js/load-image-orientation.js中实现的。

希望这可以为其他人节省一些时间,并教搜索引擎了解这个开源 gem :)

@igneosaur:您可以将 base64 编码的数据发送到服务器,canvas.toDataURL()并在服务器端解码并保存。
2021-03-27 18:09:31
我一直在使用这个库,但最近它在 iOS 8.3 上崩溃了,这是我最需要它工作的地方:(
2021-04-01 18:09:31
我同意@igneosaur,在如何使用这个库从 url 中提取 jpeg 图像并正确定位方面真的很挣扎。我没有本地文件。
2021-04-04 18:09:31
我真的很想知道这有什么帮助。我正在尝试学习它,因此我可以在需要旋转时解析页面并旋转图像,或者在实际上传之前检测方向并旋转文件(以某种方式)。演示两者都没有。它只是从文件输入中获取一个文件并以正确的方式显示它,这在现实世界中何时有用?当我解析我的页面并将图像标签中的 URL 提供给 loadImage 库时,没有 exif 数据所以不能这样做。对于上传,它返回一个画布对象,因此我无法将其发送到服务器或任何东西。
2021-04-06 18:09:31
您可以使用它的一些代码库来烘焙您自己的项目,而不是使用 load-image 项目 - 只需查看github.com/blueimp/JavaScript-Load-Image/blob/master/js/...
2021-04-10 18:09:31

Mederr 的上下文转换工作完美。如果您只需要提取方向,只需使用此功能- 您不需要任何 EXIF 读取库。下面是在 base64 图像中重新设置方向的函数。 这是一个小提琴我还准备了一个方向提取演示小提琴

function resetOrientation(srcBase64, srcOrientation, callback) {
  var img = new Image();    

  img.onload = function() {
    var width = img.width,
        height = img.height,
        canvas = document.createElement('canvas'),
        ctx = canvas.getContext("2d");

    // set proper canvas dimensions before transform & export
    if (4 < srcOrientation && srcOrientation < 9) {
      canvas.width = height;
      canvas.height = width;
    } else {
      canvas.width = width;
      canvas.height = height;
    }

    // transform context before drawing image
    switch (srcOrientation) {
      case 2: ctx.transform(-1, 0, 0, 1, width, 0); break;
      case 3: ctx.transform(-1, 0, 0, -1, width, height); break;
      case 4: ctx.transform(1, 0, 0, -1, 0, height); break;
      case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;
      case 6: ctx.transform(0, 1, -1, 0, height, 0); break;
      case 7: ctx.transform(0, -1, -1, 0, height, width); break;
      case 8: ctx.transform(0, -1, 1, 0, 0, width); break;
      default: break;
    }

    // draw image
    ctx.drawImage(img, 0, 0);

    // export base64
    callback(canvas.toDataURL());
  };

  img.src = srcBase64;
};
我在 Android WebView 中使用了这个答案,结果发现有些 Android 设备在 WebView 中不支持 WebGL(请参阅bugs.chromium.org/p/chromium/issues/detail?id=555116)根据图像的大小,在此类设备上旋转可能需要很长时间。
2021-03-15 18:09:31
switch不需要方向中的默认情况,因为该转换不执行任何操作。此外,请考虑使用srcOrientation > 4 && srcOrientation < 9而不是[5,6,7,8].indexOf(srcOrientation) > -1,因为它速度更快且资源占用更少(RAM 和 CPU)。没有必要在那里有一个数组。这在批处理大量图像时很重要,其中每一位都很重要。否则,很好的答案。点赞!
2021-03-24 18:09:31
@RyanCasas 我不知道与indexOf您提出的建议相比有多么沉重我运行了一个包含 10M 次迭代的简单循环,速度提高了 1400%。很好 :D 感谢一堆!
2021-03-24 18:09:31
另外,对于已经尊重方向的浏览器怎么办?我相信 iOS Safari 就是这种情况,并且在image-orientation: from-image使用CSS支持 CSS 的浏览器也是如此。
2021-03-24 18:09:31
当与引用的 一起使用时getOrientation,我很好奇这在性能方面是否有效。getOrientation调用fileReader.readAsArrayBuffer,然后我们调用URL.createObjectURL并将结果传递给resetOrientation,它将此 URL 作为图像加载。这是否意味着图像文件将被浏览器“加载”/读取不是一次而是两次,还是我误解了?
2021-04-01 18:09:31

如果

width = img.width;
height = img.height;
var ctx = canvas.getContext('2d');

然后您可以使用这些转换将图像转换为方向 1

从方向:

  1. ctx.transform(1, 0, 0, 1, 0, 0);
  2. ctx.transform(-1, 0, 0, 1, width, 0);
  3. ctx.transform(-1, 0, 0, -1, width, height);
  4. ctx.transform(1, 0, 0, -1, 0, height);
  5. ctx.transform(0, 1, 1, 0, 0, 0);
  6. ctx.transform(0, 1, -1, 0, height, 0);
  7. ctx.transform(0, -1, -1, 0, height, width);
  8. ctx.transform(0, -1, 1, 0, 0, width);

在ctx上绘制图像之前

这里到底发生了什么?
2021-03-12 18:09:31
为什么在 ctx 上绘制图像很重要?在画布上绘制图像后无法旋转画布?
2021-03-12 18:09:31
方向 8 的旋转对我来说是这样工作的: ctx.transform(0, -1, 1, 0, 0, height);
2021-03-19 18:09:31
有了这个,您只需要知道方向(例如通过 EXIF),然后根据需要旋转。我正在寻找的一半。
2021-04-01 18:09:31
这些转换对我不起作用 - 相反,我使用了github.com/blueimp/JavaScript-Load-Image/blob/master/js/的 load-image 项目中的代码来获得我认为经过充分验证的代码它同时使用画布上下文上的平移、旋转操作以及宽度/高度交换。经过数小时的其他转换尝试实验后,我得到了 100% 的结果
2021-04-09 18:09:31

好的,除了@user3096626 回答我认为如果有人提供代码示例会更有帮助,以下示例将向您展示如何修复来自 url(远程图像)的图像方向:


解决方案 1:使用 javascript(推荐)

  1. 因为load-image库不会仅从 url 图像(文件/blob)中提取 exif 标签,我们将同时使用exif-jsload-image javascript 库,因此首先将这些库添加到您的页面中,如下所示:

    <script src="https://cdnjs.cloudflare.com/ajax/libs/exif-js/2.1.0/exif.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-load-image/2.12.2/load-image.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-load-image/2.12.2/load-image-scale.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-load-image/2.12.2/load-image-orientation.min.js"></script>
    

    注意exif-js 的 2.2 版本似乎有问题,所以我们使用 2.1

  2. 那么基本上我们要做的是

    a - 使用加载图像 window.loadImage()

    b - 使用读取 exif 标签 window.EXIF.getData()

    c - 将图像转换为画布并使用固定图像方向 window.loadImage.scale()

    d - 将画布放入文档中

干得好 :)

window.loadImage("/your-image.jpg", function (img) {
  if (img.type === "error") {
    console.log("couldn't load image:", img);
  } else {
    window.EXIF.getData(img, function () {
        var orientation = EXIF.getTag(this, "Orientation");
        var canvas = window.loadImage.scale(img, {orientation: orientation || 0, canvas: true});
        document.getElementById("container").appendChild(canvas); 
        // or using jquery $("#container").append(canvas);

    });
  }
});

当然,您也可以从画布对象中获取 base64 格式的图像并将其放置在 img src 属性中,因此您可以使用 jQuery ;)

$("#my-image").attr("src",canvas.toDataURL());

这里是完整的代码:github:https : //github.com/digital-flowers/loadimage-exif-example


解决方案2:使用html(浏览器黑客)

有一个非常快速和简单的 hack,如果图像在没有任何 html 的新选项卡中直接打开,大多数浏览器会以正确的方向显示图像(哈哈,我不知道为什么),所以基本上你可以使用 iframe 显示你的图像通过将 iframe src 属性直接作为图像 url:

<iframe src="/my-image.jpg"></iframe>

解决方案 3:使用 css(仅 ios 上的 firefox 和 safari)

有 css3 属性可以修复图像方向,但它仅适用于 firefox 和 safari/ios 的问题仍然值得一提,因为很快它将可用于所有浏览器(来自caniuse 的浏览器支持信息

img {
   image-orientation: from-image;
}
ok checkout the github project有一个新文件“upload.html”,欢迎你:)
2021-03-12 18:09:31
似乎 exif-js 已损坏,请检查我的编辑我还在 github 上添加了完整代码:github.com/digital-flowers/loadimage-exif-example
2021-03-17 18:09:31
解决方案 1 对我不起作用。它正在返回 window.loadImage 未定义
2021-03-23 18:09:31
我包括了图书馆。我喜欢您示例的简单性,但我一直收到该错误消息。
2021-04-04 18:09:31
如果您没有包含我在第 1 步中提到的库,就会发生这种情况
2021-04-10 18:09:31

对于那些拥有来自输入控件的文件,不知道它的方向是什么,有点懒惰并且不想在下面包含大型库的人来说,@WunderBart 提供的代码与他链接到的答案融合在一起(https://stackoverflow.com/a/32490603 ) 找到方向。

function getDataUrl(file, callback2) {
        var callback = function (srcOrientation) {
            var reader2 = new FileReader();
            reader2.onload = function (e) {
                var srcBase64 = e.target.result;
                var img = new Image();

                img.onload = function () {
                    var width = img.width,
                        height = img.height,
                        canvas = document.createElement('canvas'),
                        ctx = canvas.getContext("2d");

                    // set proper canvas dimensions before transform & export
                    if (4 < srcOrientation && srcOrientation < 9) {
                        canvas.width = height;
                        canvas.height = width;
                    } else {
                        canvas.width = width;
                        canvas.height = height;
                    }

                    // transform context before drawing image
                    switch (srcOrientation) {
                        case 2: ctx.transform(-1, 0, 0, 1, width, 0); break;
                        case 3: ctx.transform(-1, 0, 0, -1, width, height); break;
                        case 4: ctx.transform(1, 0, 0, -1, 0, height); break;
                        case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;
                        case 6: ctx.transform(0, 1, -1, 0, height, 0); break;
                        case 7: ctx.transform(0, -1, -1, 0, height, width); break;
                        case 8: ctx.transform(0, -1, 1, 0, 0, width); break;
                        default: break;
                    }

                    // draw image
                    ctx.drawImage(img, 0, 0);

                    // export base64
                    callback2(canvas.toDataURL());
                };

                img.src = srcBase64;
            }

            reader2.readAsDataURL(file);
        }

        var reader = new FileReader();
        reader.onload = function (e) {

            var view = new DataView(e.target.result);
            if (view.getUint16(0, false) != 0xFFD8) return callback(-2);
            var length = view.byteLength, offset = 2;
            while (offset < length) {
                var marker = view.getUint16(offset, false);
                offset += 2;
                if (marker == 0xFFE1) {
                    if (view.getUint32(offset += 2, false) != 0x45786966) return callback(-1);
                    var little = view.getUint16(offset += 6, false) == 0x4949;
                    offset += view.getUint32(offset + 4, little);
                    var tags = view.getUint16(offset, little);
                    offset += 2;
                    for (var i = 0; i < tags; i++)
                        if (view.getUint16(offset + (i * 12), little) == 0x0112)
                            return callback(view.getUint16(offset + (i * 12) + 8, little));
                }
                else if ((marker & 0xFF00) != 0xFF00) break;
                else offset += view.getUint16(offset, false);
            }
            return callback(-1);
        };
        reader.readAsArrayBuffer(file);
    }

可以很容易地像这样调用

getDataUrl(input.files[0], function (imgBase64) {
      vm.user.BioPhoto = imgBase64;
});
为什么有人会懒惰地喜欢更轻的解决方案?
2021-03-11 18:09:31
感谢谷歌搜索超过 2 小时后,我找到了正确的解决方案。
2021-03-19 18:09:31
移植到 Typescript 并从大约 4 秒优化到 ~20 毫秒。Base64 编码是瓶颈 - 使用image/jpeg而不是默认值image/png,并将输出图像的大小调整为最大宽度(在我的情况下为 400px)产生了巨大的差异。(这适用于缩略图,但如果您必须显示完整图像,我建议避免使用 base64,只需将画布元素直接注入页面......)
2021-03-29 18:09:31