ChatGPT解决这个技术问题 Extra ChatGPT

在客户端的 JavaScript 中访问 JPEG EXIF 旋转数据

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

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

服务器端的答案不行;我正在寻找客户端解决方案。


J
Jamie

如果您只想要方向标签而不想要其他任何东西并且不喜欢包含另一个巨大的 JavaScript 库,我编写了一个尽可能快地提取方向标签的小代码(它使用 IE10+ 中可用的 DataView 和 readAsArrayBuffer,但是您可以为旧版浏览器编写自己的数据阅读器):

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) { 返回回调(-2); } var 长度 = view.byteLength,偏移量 = 2; while (offset < length) { if (view.getUint16(offset+2, false) <= 8) return callback(-1); var marker = view.getUint16(offset, false);偏移 += 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);偏移 += 2; for (var i = 0; i < tags; i++) { if (view.getUint16(offset + (i * 12), little) == 0x0112) { return callback(view.getUint16(offset + (i * 12) + 8、小)); } } } else if ((marker & 0xFF00) != 0xFF00) { break; } else { offset += view.getUint16(offset, false); } } 返回回调(-1); }; reader.readAsArrayBuffer(file); } // 用法: var input = document.getElementById('input'); input.onchange = function(e) { getOrientation(input.files[0], function(orientation) { alert('orientation: ' +orientation); }); }

价值观:

-2: not jpeg
-1: not defined

https://i.stack.imgur.com/VGsAj.gif

对于那些使用 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?
@Mick PNG 或 GIF 没有任何标准格式来存储图像方向stackoverflow.com/questions/9542359/…
为我工作,但我需要将最后一行更改为 reader.readAsArrayBuffer(file);没有切片,因为我打算将缓冲区用于我的 base64 图像,否则,您只会看到图像的第一个切片。顺便说一句,如果您只需要方向信息,则不需要这样做。谢谢
@DaraJava我删除了切片部分,因为有时标签在限制之后进入,但是如果永远找不到标签,它会减慢操作。无论如何,与方向标签不同,Flash 标签不在 IFD0 目录中,我的代码只搜索这部分。要获取 Flash 标签,您必须搜索 SubIFD 目录。您可以在此处找到有关 EXIF 的优秀教程:media.mit.edu/pia/Research/deepview/exif.html
T
Tetramputechture

您可以将 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 库看起来有点过时,但可能会起作用。
另请参阅我刚刚编写的文件上传小部件的演示。它使用上面提到的 EXIF.js 库来读取图像文件元数据中的 EXIF 方向标志。根据信息,它使用画布元素应用旋转... sandbox.juurlink.org/html5imageuploader
尝试在我的项目中甚至包含 binaryajax.js 会导致访问被拒绝错误。
EXIF 对象从何而来? BinaryFile 脚本似乎不包含它,据我所知,它不是 jquery 或我经常使用的任何其他脚本的一部分......
图书馆网站似乎关闭了,我发现的唯一其他 ExifReader 库在浏览器支持方面受到限制。有什么好的选择吗?
r
robocat

Firefox 26 支持 image-orientation: from-image:图像显示为纵向或横向,具体取决于 EXIF 数据。 (见sethfowler.org/blog/2013/09/13/new-in-firefox-26-css-image-orientation。)

还有a bug to implement this in Chrome

请注意,此属性仅受 Firefox 支持并且是 likely to be deprecated


感谢您提供错误报告的链接。我为它加星是为了让 Chrome 团队知道更多人想要这个。
根据 Chromium 项目成员的评论 bugs.chromium.org/p/chromium/issues/detail?id=158753#c104:“变化发生在 Chrome 81 中。这将在 8-10 周内作为稳定版向公众推出”
从 81 开始在 Chrome 上实施 🎉 不过,人们更新浏览器需要一段时间 - 请留意 caniuse
C
Community

https://github.com/blueimp/JavaScript-Load-Image 是一个现代 javascript 库,它不仅可以提取 exif 方向标志 - 它还可以在客户端正确镜像/旋转 JPEG 图像。

我刚刚用这个库解决了同样的问题:JS Client-Side Exif Orientation: Rotate and Mirror JPEG Images


W
Wonhyuk Cho

我上传了扩展代码,以便在某些具有右旋转的 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]);

});

C
Community

如果你想要它跨浏览器,你最好的选择是在服务器上做。你可以有一个 API 接受文件 URL 并返回 EXIF 数据; PHP has a module for that

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


@MikkoOhtamaa:您需要了解 Stack Overflow 会为每个人回答问题,而只是最初提出问题的人。下一个与您有相同目标的人可能是 PHP 开发人员——您为什么要拒绝他们 Xeon06 包含的信息?只是因为您不想要 PHP 解决方案,所以编辑它是不合适的。
问题说“在 Javascript 中”,所以这部分无关紧要。网站上已经有许多其他类似的 PHP 问题和答案,关于这个问题是不必要的噪音。
如果人们要求 Javascript 解决方案,他们不希望将 PHP 解决方案视为第一篇文章。
@MikkoOhtamaa 似乎大多数人不同意你meta.stackexchange.com/questions/157338/…你似乎对你的问题的答案有一些错误的主人翁意识。
我编辑了答案,以便在一开始就有正确的答案。对不起,毛茸茸的。
g
grayswan

改进/向 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 });
  });
};

z
zavr

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

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


不错的模块!但是,它首先是如何从 JPEG 中获取 EXIF 信息的呢?
@MikkoOhtamaa 谢谢,不,你必须用 exif-js 或 exiftool 服务器端来做
这很有用。但在我看来,它只适用于肖像照片,而不适用于风景照片。