Exif Orientation 元数据

最近发现手机上拍的照片,通过pdf-lib.js 库绘制PDF时,图片逆时针旋转了90 度。
经过排查,实际上这个图片是按旋转的数据点阵记录每一像素的,我们在大多图片查看器中看到他是正常的,是因为有一个Orientation(方向)元数据信息,这些图像查看器会根据这个Orientation 的值来决定是否要旋转及怎么旋转展示,但有一些程序并没有这么做。

我的这张图片,他的元数据 Orientation 是 6,用一般的图像查看器都是正常显示,但用 pdf-lib.js JavaScript 库绘制 PDF 是逆时针旋转了 90 度。

Exif Orientation是一种用于指示图像方向的元数据字段,最初由日本电子工业发展协会(JEIDA)在1998年引入到 Exif 规范中。它被嵌入在 JPEG、TIFF、RAW 等图像格式中,用于存储与图像拍摄相关的信息,例如相机型号、拍摄日期和时间、曝光时间等。其中,Exif Orientation 用于指示图像的方向,以便在图像浏览器和编辑器中正确显示图像。

Orientation 有 8 种值,如下图所示 8 种值以及它所代表的方向。

这是在iPhone 下以不同方向握手机拍照生成的照片,有着不同的 Orientation 值的。

智能手机通常使用内置的陀螺仪和加速度计等传感器来检测其方向。这些传感器可以检测设备的重力方向、倾斜角度和方向,并将这些数据传递给设备的操作系统。根据这些数据,操作系统可以确定设备的方向,并在需要时将图像的 Exif Orientation 标记设置为相应的值。例如,当您拍摄一张横向的照片时,设备会检测到其方向为横向,并将 Exif Orientation 标记设置为6。这样,当您在图库中查看照片时,设备就会自动将其旋转为正确的方向。

在拍照时,无论您是横向还是竖向拿设备,图像感光器(Image sensor)总是按照固定的方向来捕捉数据,最终的照片数据通常都是按照固定的方向保存,而不是根据Orientation值转化数据保存相应的旋转后图像。这是为了节省计算资源和处理时间

因此,许多设备采用 Exif Orientation 标记来标记图像的方向信息,并且图像本身并没有被旋转或转换。这可以避免不必要的处理和降低存储成本。此外,通过保存 Exif Orientation 标记,图像的原始方向信息可以被保留下来,这对于后续处理和编辑图像非常有用。因此,采用 Exif Orientation 标记是一种更加普遍和高效的方式来保存图像方向信息。

知道了问题所在,就要提取 Orientation 元数据信息进行适配。

提取EXIF Orientation Tag 数据

0x0112(274) is the tag for orientation

https://www.awaresystems.be/imaging/tiff/tifftags/baseline.html
function getOrientation(buffer) {
  return new Promise((resolve, reject) => {
      const dataView = new DataView(buffer);
      let offset = 0;
      let marker = dataView.getUint16(offset, false);
      if (marker === 0xffd8) {
        offset += 2;
        while (offset < dataView.byteLength) {
          marker = dataView.getUint16(offset, false);
          offset += 2;
          if (marker === 0xffe1) {
            offset += 2;
            if (dataView.getUint32(offset, false) !== 0x45786966) { // Exif
              reject(new Error('Invalid Exif header'));
              return;
            }
            const littleEndian = dataView.getUint16(offset += 6, false) === 0x4949;
            offset += dataView.getUint32(offset + 4, littleEndian);
            const tags = dataView.getUint16(offset, littleEndian);
            offset += 2;
            for (let i = 0; i < tags; i++) {
              if (dataView.getUint16(offset, littleEndian) === 0x0112) {
                // 0x0112(274) is the tag for orientation
                resolve(dataView.getUint16(offset + 8, littleEndian));
                return;
              }
              offset += 12;
            }
            reject(new Error('Orientation tag not found'));
            return;
          } else if ((marker & 0xff00) !== 0xff00) {
            break;
          } else {
            offset += dataView.getUint16(offset, false);
          }
        }
        reject(new Error('Invalid JPEG format'));
        return;
      }
      reject(new Error('Not a valid JPEG file'));
  });
}


Leave a Reply

Your email address will not be published. Required fields are marked *