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

fle*_*ive 131 javascript exif rotation html5-canvas

数码相机照片通常以JPEG格式保存,并带有EXIF"方向"标签.要正确显示,需要根据设置的方向旋转/镜像图像,但浏览器会忽略渲染图像的信息.即使在大型商业网络应用程序中,对EXIF方向的支持也可能不稳定1.相同的源代码还提供 了JPEG可以具有的8种不同方向的精彩摘要:

EXIF方向摘要

样本图像可在4处获得.

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

有JS库可用于解析EXIF数据,包括orientation属性2.Flickr在解析大图像时注意到了可能的性能问题,需要使用Web工作者3.

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

fle*_*ive 132

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

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

希望这能节省一些时间,并教导搜索引擎关于这个开源宝石:)

  • 我一直在使用这个库,但最近它在 iOS 8.3 上崩溃了,这是我最需要它工作的地方:( (2认同)
  • 我真的很想知道这有什么帮助。我正在尝试学习它,所以我可以解析页面并在需要旋转图像时旋转图像,或者在实际上传之前检测方向并旋转文件(以某种方式)。该演示都没有。它只是从文件输入中提取一个文件并以正确的方式显示它,这在现实世界中什么时候有用?当我解析页面并将图像标签中的URL馈入loadImage库时,没有exif数据,因此无法做到这一点。对于上传,它返回一个canvas对象,因此我无法将其发送到服务器或其他任何对象。 (2认同)
  • @igneosaur:您可以使用`canvas.toDataURL()`将base64编码数据发送到服务器并解码并保存到服务器端. (2认同)

Wun*_*art 87

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;
};
Run Code Online (Sandbox Code Playgroud)

  • 不需要方向"switch"中的默认情况,因为该转换不起作用.另外,考虑使用`srcOrientation> 4 && srcOrientation <9`而不是`[5,6,7,8] .indexOf(srcOrientation)> -1`,因为它更快,资源更少(RAM和CPU).那里没有阵列.在批量处理大量图像时,这很重要.否则,相当不错的答案.Upvoted! (6认同)
  • @RyanCasas我不知道`indexOf'与你提出的建议相比有多重.我运行了一个10M迭代的简单循环,速度提高了1400%.尼斯:D谢谢你! (4认同)
  • 我在 Android WebView 中使用了这个答案,结果发现有些 Android 设备在 WebView 中不支持 WebGL(请参阅 https://bugs.chromium.org/p/chromium/issues/detail?id =555116) 根据图像的大小,在此类设备上旋转可能需要很长时间。 (2认同)
  • 另外,对于已经尊重方向的浏览器怎么办?我相信 iOS Safari 就是这种情况,并且在使用 CSS `image-orientation: from-image` 的浏览器中。 (2认同)

Med*_*err 38

如果

width = img.width;
height = img.height;
var ctx = canvas.getContext('2d');
Run Code Online (Sandbox Code Playgroud)

然后,您可以使用这些转换将图像转换为方向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上绘制图像之前

  • 甚至在这里发生了什么? (46认同)
  • 这些转换对我来说不起作用 - 而是使用了https://github.com/blueimp/JavaScript-Load-Image/blob/master/js/load-image-orientation.js中的load-image项目中的代码.得到我认为是经过充分验证的代码,它在画布上下文中使用平移,旋转操作以及宽度/高度交换.经过数小时的实验以及其他尝试改造等,给了我100%的结果 (2认同)

Far*_*uti 22

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


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

  1. 因为加载图像库不会仅从url图像中提取exif标记(文件/ blob),所以我们将使用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>
    
    Run Code Online (Sandbox Code Playgroud)

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

    });
  }
});
Run Code Online (Sandbox Code Playgroud)

当然你也可以从canvas对象获取base64的图像并将其放在img src属性中,所以使用jQuery你可以做;)

$("#my-image").attr("src",canvas.toDataURL());
Run Code Online (Sandbox Code Playgroud)

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


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

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

<iframe src="/my-image.jpg"></iframe>
Run Code Online (Sandbox Code Playgroud)

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

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

img {
   image-orientation: from-image;
}
Run Code Online (Sandbox Code Playgroud)


run*_*ier 9

对于那些有输入控件文件的人,不知道它的方向是什么,有点懒,不想在下面包含一个大型库,@ WunderBart提供的代码与他链接的答案融合在一起(/sf/answers/2274342241/)找到方向.

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);
    }
Run Code Online (Sandbox Code Playgroud)

这很容易被称为

getDataUrl(input.files[0], function (imgBase64) {
      vm.user.BioPhoto = imgBase64;
});
Run Code Online (Sandbox Code Playgroud)

  • [移植到Typescript并进行了优化](https://gist.github.com/mindplay-dk/72f47c1a570e870a375bd3dbcb9328fb)从大约4秒到大约20毫秒。Base64编码是瓶颈-使用`image / jpeg`而不是默认的`image / png`,并将输出图像的大小调整为最大宽度(在我的情况下为400px)产生了很大的差异。(这对于缩略图来说效果很好,但是如果您必须显示完整图像,我建议避免使用base64并将简单的canvas元素直接注入页面中。) (4认同)
  • 经过2个多小时的Google搜索后,谢谢,我找到了正确的解决方案。 (2认同)
  • 为什么有人会懒得更喜欢更轻松的解决方案呢? (2认同)

gil*_*t-v 8

一个班轮任何人?

我还没有看到有人提到browser-image-compression图书馆。它有一个完美的辅助函数。

用法: const orientation = await imageCompression.getExifOrientation(file)

在许多其他方面也是如此有用的工具。

  • 不,你可以从 Blob 创建一个 File 对象就可以了。文件 File( 数组部分、字符串文件名、BlobPropertyBag 属性 ); https://developer.mozilla.org/de/docs/Web/API/File (2认同)
  • 金色贴纸!@masterxilo (2认同)
  • 这对我来说非常有用!在过去的两天里,我一直在为方向而苦苦挣扎!由于某种原因,发布的所有转换开关语句都无法处理我手机中的肖像图像,会有黑条。可能是因为我试图同时压缩和旋转。 (2认同)

sta*_*ler 7

WunderBart的答案对我来说是最好的.请注意,如果您的图像通常是正确的方式,您可以加快速度,只需首先测试方向,如果不需要旋转则绕过其余代码.

把所有来自wunderbart的信息放在一起,就像这样;

var handleTakePhoto = function () {
    let fileInput: HTMLInputElement = <HTMLInputElement>document.getElementById('photoInput');
    fileInput.addEventListener('change', (e: any) => handleInputUpdated(fileInput, e.target.files));
    fileInput.click();
}

var handleInputUpdated = function (fileInput: HTMLInputElement, fileList) {
    let file = null;

    if (fileList.length > 0 && fileList[0].type.match(/^image\//)) {
        isLoading(true);
        file = fileList[0];
        getOrientation(file, function (orientation) {
            if (orientation == 1) {
                imageBinary(URL.createObjectURL(file));
                isLoading(false);
            }
            else 
            {
                resetOrientation(URL.createObjectURL(file), orientation, function (resetBase64Image) {
                    imageBinary(resetBase64Image);
                    isLoading(false);
                });
            }
        });
    }

    fileInput.removeEventListener('change');
}


// from http://stackoverflow.com/a/32490603
export function getOrientation(file, callback) {
    var reader = new FileReader();

    reader.onload = function (event: any) {
        var view = new DataView(event.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.slice(0, 64 * 1024));
};

export 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;
}
Run Code Online (Sandbox Code Playgroud)