【问题标题】:Accessing JPEG EXIF rotation data in JavaScript on the client side在客户端的 JavaScript 中访问 JPEG EXIF 旋转数据
【发布时间】:2023-03-17 12:16:01
【问题描述】:

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

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

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

【问题讨论】:

    标签: javascript jpeg exif html5-canvas


    【解决方案1】:

    如果您想要跨浏览器,最好的办法是在服务器上进行。你可以有一个 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/… 你似乎对你的问题的答案有一些错误的主人翁意识。
    • 我在开始时编辑了答案以获得正确答案。对不起,模糊不清。
    【解决方案2】:

    您可以将 exif-js 库与 HTML5 文件 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 库在浏览器支持方面受到限制。有什么好的选择吗?
    【解决方案3】:

    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
    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

      如果您只想要方向标记而不想要其他任何东西并且不喜欢包含另一个巨大的 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);
          });
      }
      &lt;input id='input' type='file' /&gt;

      价值观:

      -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??
      • @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
      【解决方案6】:

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

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

      【讨论】:

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

      我上传扩展代码在一些具有正确旋转的img标签上正常显示android相机在html上的照片,特别是对于宽度大于高度的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]);
      
      });
      

      【讨论】:

        【解决方案8】:

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

        【讨论】:

          猜你喜欢
          • 2014-01-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-04-21
          • 1970-01-01
          • 2016-04-30
          • 1970-01-01
          相关资源
          最近更新 更多