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

时间:2013-12-15 22:41:22

标签: javascript rotation html5-canvas exif

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

Summary of EXIF Orientations

示例图片位于4

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

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

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

处获得

12 个答案:

答案 0 :(得分:121)

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

的在线演示

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

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

答案 1 :(得分:72)

Mederr's context transform完美无缺。如果您只需要提取方向,请使用this function - 您不需要任何EXIF读取库。以下是在base64图像中重新设置方向的功能。 Here's a fiddle for it。我还准备了fiddle with orientation extraction demo

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

答案 2 :(得分:36)

如果

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);
  9. 在ctx上绘制图像之前

答案 3 :(得分:21)

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

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

  1. 因为 load-image 库不能仅从网址图片中提取exif标记(文件/ blob),我们将同时使用 exif-js load-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 - 使用window.EXIF.getData()

    阅读exif标签

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

    修复图像方向

    d - 将画布放入文档

  3. 在这里你去:)。

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

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

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

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

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

    有一个非常快速简单的黑客攻击,大多数浏览器都会以正确的方向显示图像,如果图像在新标签内直接打开而没有任何HTML(我不知道为什么),所以基本上你可以通过将iframe src属性直接作为图片网址,使用iframe显示您的图片:

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

    解决方案3:使用css(仅限ios上的firefox&amp; safari)

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

    img {
       image-orientation: from-image;
    }
    

答案 4 :(得分:7)

对于那些从输入控件中获取文件的人,不知道它的方向是什么,有点懒,并且不想在下面包含一个大型库是由@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;
});

答案 5 :(得分: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;
}

答案 6 :(得分:3)

一个班轮有人吗?

我还没有看到有人提到browser-image-compression library。它有一个完美的辅助功能。

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

这种有用的工具还可以通过许多其他方式使用。

答案 7 :(得分:2)

我创建了一个包装在ES6模块中的类,可以解决此问题。

这是103行,没有依赖关系,并且结构和文档都很好,易于修改/重用。

处理所有8种可能的方向,并且基于Promise。

您在这里,希望对您有所帮助:https://gist.github.com/vdavid/3f9b66b60f52204317a4cc0e77097913

答案 8 :(得分:0)

除了@fareed namrouti的回答,

如果必须从文件输入元素

浏览图像,则应使用此选项
<input type="file" name="file" id="file-input"><br/>
image after transform: <br/>
<div id="container"></div>

<script>
    document.getElementById('file-input').onchange = function (e) {
        var image = e.target.files[0];
        window.loadImage(image, function (img) {
            if (img.type === "error") {
                console.log("couldn't load image:", img);
            } else {
                window.EXIF.getData(image, function () {
                    console.log("load image done!");
                    var orientation = window.EXIF.getTag(this, "Orientation");
                    var canvas = window.loadImage.scale(img,
                        {orientation: orientation || 0, canvas: true, maxWidth: 200});
                    document.getElementById("container").appendChild(canvas);
                    // or using jquery $("#container").append(canvas);
                });
            }
        });
    };
</script>

答案 9 :(得分:0)

我正在使用混合解决方案(php + css)。

需要容器:

  • div.imgCont2容器需要旋转;
  • div.imgCont1容器需要zoomOut - width:150%;
  • 滚动条需要
  • div.imgCont容器,当图像为zoomOut时。

<?php
    $image_url = 'your image url.jpg';
    $exif = @exif_read_data($image_url,0,true);
    $orientation = @$exif['IFD0']['Orientation'];
?>

<style>
.imgCont{
    width:100%;
    overflow:auto;
}
.imgCont2[data-orientation="8"]{
    transform:rotate(270deg);
    margin:15% 0;
}
.imgCont2[data-orientation="6"]{
    transform:rotate(90deg);
    margin:15% 0;
}
.imgCont2[data-orientation="3"]{
    transform:rotate(180deg);
}
img{
    width:100%;
}
</style>

<div class="imgCont">
  <div class="imgCont1">
    <div class="imgCont2" data-orientation="<?php echo($orientation) ?>">
      <img src="<?php echo($image_url) ?>">
    </div>
  </div>
</div>

答案 10 :(得分:0)

我写了一个用于旋转图像的小脚本。请务必存储图像,以便只是重新计算每个请求。

<?php

header("Content-type: image/jpeg");
$img = 'IMG URL';

$exif = @exif_read_data($img,0,true);
$orientation = @$exif['IFD0']['Orientation'];
if($orientation == 7 || $orientation == 8) {
    $degrees = 90;
} elseif($orientation == 5 || $orientation == 6) {
    $degrees = 270;
} elseif($orientation == 3 || $orientation == 4) {
    $degrees = 180;
} else {
    $degrees = 0;
}
$rotate = imagerotate(imagecreatefromjpeg($img), $degrees, 0);
imagejpeg($rotate);
imagedestroy($rotate);

?>

干杯

答案 11 :(得分:0)

就我而言,exif-auto-rotate 库正是我所需要的。

我有来自后端的 base64 格式图像,我必须在使用它之前将其旋转到正确的方向。它在更改旋转后返回 base64,这对我来说也是一个加分项。

这是该库的 npm 链接:https://www.npmjs.com/package/exif-auto-rotate