html5图片在android端旋转

June 5, 2020 ... ☕️ 7 min read

在开发手机端app内嵌html5的页面,刚把软键盘的坑填完,测试同学报告说图片好像转圈了?样子如下:

rotated-image android手机内嵌app内显示的图片

rotated-image 原始图片

搜索一下发现,是文件的exif信息导致的。

可交换图像文件格式(英语:Exchangeable image file format,官方简称Exif),是专门为数码相机的照片设定的文件格式,可以记录数码照片的属性信息和拍摄数据。 支持的扩展名.JPG、.TIF、.WAV,png或者gif不包含exif信息。

获取EXIF的方法

1、从文件流获取

该方法来自:https://stackoverflow.com/a/32490603

EXIF可以保存旋转方向,以Orientation记录。要获取到这个参数,先要了解EXIF的编码格式。

而图像中,只有jpg/jpeg带有EXIF信息,所以从jpeg编码开始。

jpeg的编码格式

如果用命令行读取上面jpg文件的源文件,能看到

jpg-info jpg信息

Marker名称 Marker内容 说明
SOI 0xFFD8 Start Of Image
SOF0 0xFFC0 Start Of Frame 0
SOF2 0xFFC2 Start of Frame 2
DHT 0xFFC4 Define Huffman Table(s)
DQT 0xFFDB Define Quantization Table(s)
DRI 0xFFDD Define Restart Interval
SOS 0xFFDA Start of Scan
RST0~RST7 0xFFD0 ~ 0xFFD7 Restart
APP0~APP15 0xFFE0 ~ 0xFFEF Application-sepcific
COM 0xFFFE Comment
EOI 0xFFD9 End of Image

在jpeg数据中有格式为0xFF**这样的数据,称作标志位。

jpeg文件以16进制0xFFD8(Start Of Image-图像开始)为首,0xFFD9(End Of Image-图像结束)为尾。

这两个特殊的标志位不附带数据,其他的标志位都附带数据。基本格式为:

OxFF+标志数字(1字节)+数据大小(2字节)+数据(n字节)

从0xFFE0 ~ 0xFFEF 的标志是“应用程序标志(APP0~APP15)”。

APP marker format
APP0 JFIF, JFXX
APP1 EXIF
APP2 ICC profile

EXIF数据存储在APP1中,我们要找的Orientation包含在IFD0(main image)里。

APP1 APP1结构

然后是IFD0的结构描述 | Tag No. | Tag Name | Format | CompoNo | Desc.| |--------------------------------|-----------------------|-------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 0x010e | ImageDescription | ascii string | | Describes image | | 0x010f | Make | ascii string | | Shows manufacturer of digicam | | 0x0110 | Model | ascii string | | Shows model number of digicam | | 0x0112 | Orientation | unsigned short | 1 | The orientation of the camera relative to the scene, when the image was captured. The start point of stored data is, ‘1’ means upper left, ‘3’ lower right, ‘6’ upper right, ‘8’ lower left, ‘9’ undefined. | | 0x011a | XResolution | unsigned rational | 1 | Display/Print resolution of image. Large number of digicam uses 1/72inch, but it has no mean because personal computer doesn’t use this value to display/print out. | | 0x011b | YResolution | unsigned rational | 1 | | | 0x0128 | ResolutionUnit | unsigned short | 1 | Unit of XResolution(0x011a)/YResolution(0x011b). ‘1’ means no-unit, ‘2’ means inch, ‘3’ means centimeter. | | 0x0131 | Software | ascii string | | Shows firmware(internal software of digicam) version number. | | 0x0132 | DateTime | ascii string | 20 | Date/Time of image was last modified. Data format is “YYYY:MM:DD HH:MM:SS”+0x00, total 20bytes. In usual, it has the same value of DateTimeOriginal(0x9003) | | 0x013e | WhitePoint | unsigned rational | 2 | Defines chromaticity of white point of the image. If the image uses CIE Standard Illumination D65(known as international standard of ‘daylight’), the values are ‘3127/10000,3290/10000’. | | 0x013f | PrimaryChromaticities | unsigned rational | 6 | Defines chromaticity of the primaries of the image. If the image uses CCIR Recommendation 709 primearies, values are ‘640/1000,330/1000,300/1000,600/1000,150/1000,0/1000’. | | 0x0211 | YCbCrCoefficients | unsigned rational | 3 | When image format is YCbCr, this value shows a constant to translate it to RGB format. In usual, values are ‘0.299/0.587/0.114’. | | 0x0213 | YCbCrPositioning | unsigned short | 1 | When image format is YCbCr and uses ‘Subsampling’(cropping of chroma data, all the digicam do that), defines the chroma sample point of subsampling pixel array. ‘1’ means the center of pixel array, ‘2’ means the datum point. | | 0x0214 | ReferenceBlackWhite | unsigned rational | 6 | Shows reference value of black point/white point. In case of YCbCr format, first 2 show black/white of Y, next 2 are Cb, last 2 are Cr. In case of RGB format, first 2 show black/white of R, next 2 are G, last 2 are B. | | 0x8298 | Copyright | ascii string | | Shows copyright information | | 0x8769 | ExifOffset | unsigned long | 1 | Offset to Exif Sub IFD |

按照这个获取步骤去看stackoverflow回答中提供的示例代码,就不难看懂了。

使用方法

loadXHR(src).then((blob) => {
  const file = blobToFile(blob, 'image');
  // 调用上面stackoverflow里的方法
  getOrientation(file, (orientation) => {
    console.log(orientation);
    // 根据旋转信息处理文件
    if (orientation === 6) {
      // 旋转90
      // avatar是图片标签,这里让他的父元素进行的旋转
      avatar.parentNode.style = `transform: rotate(90deg)`;
    } else if (orientation === 8) {
      // 旋转-90
      avatar.parentNode.style = `transform: rotate(-90deg)`;
    }
  });
});

// 获取图片的blob
const loadXHR = (url) => {
  return new Promise((resolve, reject) => {
      try {
          const xhr = new XMLHttpRequest();
          xhr.open("GET", url);
          xhr.responseType = "blob";
          xhr.onerror = () => {reject("Network error.")};
          xhr.onload = () => {
              if (xhr.status === 200) {resolve(xhr.response)}
              else {reject("Loading error:" + xhr.statusText)}
          };
          xhr.send();
      }
      catch(err) {reject(err.message)}
  });
}

// blob转图片
blobToFile = (theBlob, fileName) => {
  //A Blob() is almost a File() - it's just missing the two properties below which we will add
  theBlob.lastModifiedDate = new Date();
  theBlob.name = fileName;
  return theBlob;
}

2、通过插件exif-js获取

和上面的流程基本一样,通过转化成文件流,来读取各个标志位。

使用方法

const loadAvatar = (src) => {
  const img = new Image();
  img.src = src;
  img.crossOrigin = "";
  img.onload = () => {
    getExifData(img, function() {
      const orientation = getExifTag(this, "Orientation"); // 获取该Orientation属性
      // 和上面操作一样
    }
  }
}

参考网址:

#exif