在客户端使用JavaScript访问JPEG EXIF旋转数据


125

我想根据相机在JPEG EXIF图像数据中设置的原始旋转角度旋转照片。诀窍是所有这些操作都应该在浏览器中使用JavaScript和进行<canvas>

JavaScript如何访问JPEG,本地文件API对象,本地<img>或远程<img>EXIF数据以读取旋转信息?

服务器端的答案不好。我正在寻找一种客户端解决方案。

Answers:


261

如果您只想要方向标记,而又不想包含另一个庞大的javascript库,我写了一些代码,以尽可能快的速度提取方向标记(它使用DataView,readAsArrayBuffer并且在IE10 +中可用,但是您可以编写您自己的旧浏览器的数据读取器):

function getOrientation(file, callback) {
    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) 
        {
            if (view.getUint16(offset+2, false) <= 8) return callback(-1);
            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);
}

// usage:
var input = document.getElementById('input');
input.onchange = function(e) {
    getOrientation(input.files[0], function(orientation) {
        alert('orientation: ' + orientation);
    });
}
<input id='input' type='file' />

值:

-2: not jpeg
-1: not defined

在此处输入图片说明

对于使用Typescript的用户,可以使用以下代码:

export const getOrientation = (file: File, callback: Function) => {
  var reader = new FileReader();

  reader.onload = (event: ProgressEvent) => {

    if (! event.target) {
      return;
    }

    const file = event.target as FileReader;
    const view = new DataView(file.result as ArrayBuffer);

    if (view.getUint16(0, false) != 0xFFD8) {
        return callback(-2);
    }

    const length = view.byteLength
    let offset = 2;

    while (offset < length)
    {
        if (view.getUint16(offset+2, false) <= 8) return callback(-1);
        let marker = view.getUint16(offset, false);
        offset += 2;

        if (marker == 0xFFE1) {
          if (view.getUint32(offset += 2, false) != 0x45786966) {
            return callback(-1);
          }

          let little = view.getUint16(offset += 6, false) == 0x4949;
          offset += view.getUint32(offset + 4, little);
          let tags = view.getUint16(offset, little);
          offset += 2;
          for (let 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);
}

2,4,5,7以获得正确的图像,您需要旋转和翻转,对不对?
穆罕默德·乌默尔

我的图像方向是3 ..如何将方向设置为1?
露西

3
@Mick PNG或GIF没有任何标准格式来存储图像方向stackoverflow.com/questions/9542359/…–
阿里

2
为我工作,但我需要将最后一行更改为reader.readAsArrayBuffer(file); 没有切片,因为我打算将缓冲区用于我的base64图像,否则,您只会看到图像的第一个切片。顺便说一句,如果您只需要方向信息,则不需要这样做。谢谢
菲利普·墨菲

2
@DaraJava我删除了slice部分,因为有时标签会在限制之后进入,但是如果从未找到标签,则会减慢操作速度。无论如何,与方向标记不同,Flash标记不在IFD0目录中,而我的代码仅搜索此部分。要获取Flash标签,您必须搜索SubIFD目录。您可以在此处找到有关EXIF的好教程:media.mit.edu/pia/Research/deepview/exif.html
阿里(Ali)

22

您可以将exif-js库与HTML5 File API结合使用:http : //jsfiddle.net/xQnMd/1/

$("input").change(function() {
    var file = this.files[0];  // file
        fr   = new FileReader; // to read file contents

    fr.onloadend = function() {
        // get EXIF data
        var exif = EXIF.readFromBinaryFile(new BinaryFile(this.result));

        // alert a value
        alert(exif.Make);
    };

    fr.readAsBinaryString(file); // read the file
});

谢谢。这个问题中的JS库看起来有些过时了,但可能会起作用。
Mikko Ohtamaa 2011年

另请参阅我刚才编写的文件上传小部件的演示。它使用上面提到的EXIF.js库来读取图像文件的元数据中的EXIF方向标志。根据这些信息,它适用于使用canvas元素的旋转... sandbox.juurlink.org/html5imageuploader
罗布Juurlink

尝试甚至在我的项目中包含binaryajax.js都会导致拒绝访问错误。
欧比

EXIF对象来自哪里?BinaryFile脚本似乎不包含它,据我所知,它不是jquery或我经常使用的任何其他脚本的一部分...
jrista

6
图书馆的网站似乎停滞了,我发现的其他ExifReader唯一的图书馆在浏览器支持方面受到限制。有什么好的选择吗?
Praxis Ashelin 2014年

19

Firefox 26支持image-orientation: from-image:根据EXIF数据,图像以纵向或横向显示。(请参阅sethfowler.org/blog/2013/09/13/new-in-firefox-26-css-image-orientation。)

在Chrome中有一个错误可以实现

注意该属性仅受Firefox支持,并且可能不推荐使用


5
感谢您提供的错误报告链接。我为它加注了星标,以便Chrome小组了解更多人想要这个。
DemiImp 2014年

根据此评论,Chromium项目成员的bugs.chromium.org/p/chromium/issues/detail?id=158753#c104:“ Chrome 81进行了更改。它将作为稳定版本在8中向公众推出-10周的时间”
杰夫·福斯特

1
在Chrome中实现先从81🎉这将需要一段时间的人,虽然更新他们的浏览器-保持眼睛上的caniuse
罗宾梅特拉尔


4

如果您希望跨浏览器,最好的选择是在服务器上进行。您可能有一个使用文件URL并返回EXIF数据的API。PHP为此提供了一个模块

这可以使用Ajax完成,因此对用户是无缝的。如果您不关心跨浏览器的兼容性,并且可以依靠HTML5文件功能,请查看库JsJPEGmeta,该库将允许您使用本机JavaScript获取该数据。


21
@MikkoOhtamaa:您需要了解Stack Overflow会回答每个人的问题,而仅仅是原始人在问。下一个与您有相同目标的人可能是PHP开发人员-您为什么要拒​​绝他们Xeon06包含的信息?仅由于不需要PHP解决方案而将其编辑掉是不合适的。
乔恩·斯基特

5
这个问题说“用Java语言编写”,所以该部分无关紧要。该站点上已经有许多其他类似的PHP问题和答案,这对这个问题是不必要的。
Mikko Ohtamaa

2
如果人们要求Javascript解决方案,那么他们不希望将PHP解决方案视为第一篇文章。
Mikko Ohtamaa

1
@MikkoOhtamaa似乎最不同意您meta.stackexchange.com/questions/157338/… 您似乎对问题的答案有错误的主人翁感。
亚历克斯·图尔平

1
我对答案进行了编辑,以使其在开始时具有正确的答案。抱歉!
Mikko Ohtamaa

3

检出我编写的模块(可以在浏览器中使用),它将exif方向转换为CSS转换:https : //github.com/Sobesednik/exif2css

还有一个此节点程序可以生成所有方向的JPEG固定装置:https : //github.com/Sobesednik/generate-exif-fixtures


1
不错的模块!但是,它如何首先从JPEG中获取EXIF信息?
Mikko Ohtamaa

@MikkoOhtamaa谢谢,不是吗,您必须使用exif-js或exiftool服务器端
zavr

这很有用。但是在我看来,它仅适用于人像照片,而不适用于风景照。
Sridhar Sarnobat

3

我上传扩展代码以正常显示在带有右旋的某些img标签上的html上的android相机上的照片,尤其是宽度大于高度的img标签。我知道这段代码很丑陋,但是您不需要安装任何其他软件包。(我使用上面的代码来获取exif旋转值,谢谢。)

function getOrientation(file, callback) {
  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);
}

var isChanged = false;
function rotate(elem, orientation) {
    if (isIPhone()) return;

    var degree = 0;
    switch (orientation) {
        case 1:
            degree = 0;
            break;
        case 2:
            degree = 0;
            break;
        case 3:
            degree = 180;
            break;
        case 4:
            degree = 180;
            break;
        case 5:
            degree = 90;
            break;
        case 6:
            degree = 90;
            break;
        case 7:
            degree = 270;
            break;
        case 8:
            degree = 270;
            break;
    }
    $(elem).css('transform', 'rotate('+ degree +'deg)')
    if(degree == 90 || degree == 270) {
        if (!isChanged) {
            changeWidthAndHeight(elem)
            isChanged = true
        }
    } else if ($(elem).css('height') > $(elem).css('width')) {
        if (!isChanged) {
            changeWidthAndHeightWithOutMargin(elem)
            isChanged = true
        } else if(degree == 180 || degree == 0) {
            changeWidthAndHeightWithOutMargin(elem)
            if (!isChanged)
                isChanged = true
            else
                isChanged = false
        }
    }
}


function changeWidthAndHeight(elem){
    var e = $(elem)
    var width = e.css('width')
    var height = e.css('height')
    e.css('width', height)
    e.css('height', width)
    e.css('margin-top', ((getPxInt(height) - getPxInt(width))/2).toString() + 'px')
    e.css('margin-left', ((getPxInt(width) - getPxInt(height))/2).toString() + 'px')
}

function changeWidthAndHeightWithOutMargin(elem){
    var e = $(elem)
    var width = e.css('width')
    var height = e.css('height')
    e.css('width', height)
    e.css('height', width)
    e.css('margin-top', '0')
    e.css('margin-left', '0')
}

function getPxInt(pxValue) {
    return parseInt(pxValue.trim("px"))
}

function isIPhone(){
    return (
        (navigator.platform.indexOf("iPhone") != -1) ||
        (navigator.platform.indexOf("iPod") != -1)
    );
}

然后使用如

$("#banner-img").change(function () {
    var reader = new FileReader();
    getOrientation(this.files[0], function(orientation) {
        rotate($('#banner-img-preview'), orientation, 1)
    });

    reader.onload = function (e) {
        $('#banner-img-preview').attr('src', e.target.result)
        $('#banner-img-preview').css('display', 'inherit')

    };

    // read the image file as a data URL.
    reader.readAsDataURL(this.files[0]);

});

2

改进/添加更多功能来解决Ali的问题,我在Typescript中创建了一个util方法,该方法适合我对此问题的需求。此版本返回您的项目可能也需要的旋转角度。

ImageUtils.ts

/**
 * Based on StackOverflow answer: https://stackoverflow.com/a/32490603
 *
 * @param imageFile The image file to inspect
 * @param onRotationFound callback when the rotation is discovered. Will return 0 if if it fails, otherwise 0, 90, 180, or 270
 */
export function getOrientation(imageFile: File, onRotationFound: (rotationInDegrees: number) => void) {
  const reader = new FileReader();
  reader.onload = (event: ProgressEvent) => {
    if (!event.target) {
      return;
    }

    const innerFile = event.target as FileReader;
    const view = new DataView(innerFile.result as ArrayBuffer);

    if (view.getUint16(0, false) !== 0xffd8) {
      return onRotationFound(convertRotationToDegrees(-2));
    }

    const length = view.byteLength;
    let offset = 2;

    while (offset < length) {
      if (view.getUint16(offset + 2, false) <= 8) {
        return onRotationFound(convertRotationToDegrees(-1));
      }
      const marker = view.getUint16(offset, false);
      offset += 2;

      if (marker === 0xffe1) {
        if (view.getUint32((offset += 2), false) !== 0x45786966) {
          return onRotationFound(convertRotationToDegrees(-1));
        }

        const little = view.getUint16((offset += 6), false) === 0x4949;
        offset += view.getUint32(offset + 4, little);
        const tags = view.getUint16(offset, little);
        offset += 2;
        for (let i = 0; i < tags; i++) {
          if (view.getUint16(offset + i * 12, little) === 0x0112) {
            return onRotationFound(convertRotationToDegrees(view.getUint16(offset + i * 12 + 8, little)));
          }
        }
        // tslint:disable-next-line:no-bitwise
      } else if ((marker & 0xff00) !== 0xff00) {
        break;
      } else {
        offset += view.getUint16(offset, false);
      }
    }
    return onRotationFound(convertRotationToDegrees(-1));
  };
  reader.readAsArrayBuffer(imageFile);
}

/**
 * Based off snippet here: https://github.com/mosch/react-avatar-editor/issues/123#issuecomment-354896008
 * @param rotation converts the int into a degrees rotation.
 */
function convertRotationToDegrees(rotation: number): number {
  let rotationInDegrees = 0;
  switch (rotation) {
    case 8:
      rotationInDegrees = 270;
      break;
    case 6:
      rotationInDegrees = 90;
      break;
    case 3:
      rotationInDegrees = 180;
      break;
    default:
      rotationInDegrees = 0;
  }
  return rotationInDegrees;
}

用法:

import { getOrientation } from './ImageUtils';
...
onDrop = (pics: any) => {
  getOrientation(pics[0], rotationInDegrees => {
    this.setState({ image: pics[0], rotate: rotationInDegrees });
  });
};
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.