图片处理
图片处理
读取图片exif中的旋转角度
背景
移动端选取手机相册中的图片写入canvas时偶尔会遇到写入的图片莫名自动旋转。
原因
手机或摄像机拍摄的图片如果是旋转设备拍摄时,图片会有一个旋转属性Orientation,该属性会写在图片的exif
内。在使用drawImage
方法绘制图片时需要手动旋转。
什么是exif
Exif 信息就是由数码相机在拍摄过程中采集一系列的信息,然后把信息放置在我们熟知的 JPEG/TIFF 文件的头部,也就是说 Exif信息是镶嵌在 JPEG/TIFF 图像文件格式内的一组拍摄参数,它就好像是傻瓜相机的日期打印功能一样,只不过 Exif信息所记录的资讯更为详尽和完备。Exif 所记录的元数据信息非常丰富,主要包含了以下几类信息:
- 拍摄日期
- 摄器材(机身、镜头、闪光灯等)
- 拍摄参数(快门速度、光圈F值、ISO速度、焦距、测光模式等)
- 图像处理参数(锐化、对比度、饱和度、白平衡等)
- 图像描述及版权信息
- GPS定位数据
- 缩略图
JPEG格式和标记
每个JPEG文件均从二进制值“ 0xFFD8”开始,以二进制值“ 0xFFD9”结束。JPEG数据中有几个二进制0xFFXX数据,它们被称为“标记”,它表示JPEG信息数据的周期。0xFFD8表示SOI(图像开始),0xFFD9表示EOI(图像结束)。这两个特殊标记后面没有数据,其他标记旁边有数据。标记的基本格式如下。
exif协议规范
标记0xFFE0〜0xFFEF被称为“应用标记”,对于解码JPEG图像不是必需的。它们由用户应用程序使用。例如,较早的olympus / canon / casio / agfa数码相机使用JFIF(JPEG文件交换格式)存储图像。JFIF使用APP0(0xFFE0)标记插入数码相机配置数据和缩略图。
此外,Exif使用应用程序标记插入数据,但Exif使用APP1(0xFFE1)标记以避免与JFIF格式冲突。每个Exif文件格式都从这种格式开始;
Exif数据(APP1)的大致结构如下所示。这是“ Intel”字节对齐的情况,它包含JPEG格式的缩略图。如上所述,Exif数据从ASCII字符“ Exif”和2个字节的0x00开始,然后是Exif数据。Exif使用TIFF格式存储数据。
TIFF标头的结构
TIFF格式的前8个字节是TIFF标头。前2个字节定义TIFF数据的字节对齐。如果它是0x4949 =“ I I”,则表示“ Intel”类型的字节对齐。如果它是0x4d4d =“ MM”,则表示“ Motorola”类型的字节对齐。例如,第十六个系统将值“ 305,419,896”标记为0x12345678。在Motrola对齐时,将其存储为0x12,0x34,0x56,0x78。如果是Intel align,则将其存储为0x78,0x56,0x34,0x12。似乎大多数数字货币使用Intel align。理光使用Motorola align。索尼使用D700以外的Intel Align。柯达DC200 / 210/240使用Motorola align,但DC220 / 260使用Intel align,尽管它们使用的是PowerPC!因此,当我们需要Exif数据的值时,我们必须每次都检查字节对齐。尽管JPEG数据仅使用Motorola对齐方式,但Exif允许两种对齐方式
接下来的2个字节始终是2个字节的长度值0x002A。如果数据使用Intel align,则接下来的2个字节为“ 0x2a00”。如果使用摩托罗拉,则为“ 0x002a”。TIFF标头的最后4个字节是第一个IFD(图像文件目录,在下一章中介绍)的偏移量。包括此偏移量,TIFF格式中使用的所有偏移量值都将从TIFF标头(“ I I”或“ MM”)的第一个字节开始偏移字节。通常,第一个IFD在紧靠TIFF头之后开始,因此此偏移量的值为’0x00000008’。
IFD结构
TIFF标头旁边是第一个IFD:Image File Directory。它包含图像信息数据。在下面的图表中,前2个字节(’00 0d’)表示此IFD中包含的目录条目数(13)。然后是目录条目(每个条目12字节)。在最后一个目录条目之后,有4个字节的数据(在图表上为“ LLLLLLLL”),这意味着到下一个IFD的偏移量。如果其值为“ 0x00000000”,则表示这是最后一个IFD,并且没有链接的IFD。
IFD条目的结构
前0-1字节为标签号,表示一种数据。2-3字节表示数据格式,4-7字节是组件数。8-11字节(4字节)包含一个数据值或数据值的偏移量(如果最后4个字节不够表示该条目的值的话,则这4个字节表示为值地址的偏移量)
数据格式
标签号对照表
这里
0x0112
标签号就是对应方向,我们取出该值即可
例子图片
实现代码
async function loadImg (url: string) {
try {
const res = await fetch(url)
return res.arrayBuffer()
} catch (error) {
return Promise.reject(error)
}
}
async function getJpegOrientation (url: string):Promise<number> {
try {
let app1Start = 0
let offset = 0
let littleEndian = false
let ifdStart = 0
let orientation = 1
const arryBuffer = await loadImg(url)
const dataView = new DataView(arryBuffer)
const length = dataView.byteLength
if (dataView.getUint8(0) === 0xff && dataView.getUint8(1) === 0xd8) {
offset = 2
while (offset < length) {
if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) {
app1Start = offset
break
}
offset++
}
}
if (app1Start) {
const exifIDCode = app1Start + 4
const tiffOffset = app1Start + 10
if (dataView.getUint32(exifIDCode) === 0x45786966) {
const endianness = dataView.getUint16(tiffOffset)
littleEndian = endianness === 0x4949
if (littleEndian || endianness === 0x4D4D /* bigEndian */) {
if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) {
const firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian)
if (firstIFDOffset >= 0x00000008) {
ifdStart = tiffOffset + firstIFDOffset
}
}
}
}
}
if (ifdStart) {
const ifdLength = dataView.getUint16(ifdStart, littleEndian)
const ifdContentStart = ifdStart + 2
let idx = 0
while (idx < ifdLength) {
offset = ifdContentStart + 12 * idx
if (dataView.getUint16(offset, littleEndian) === 0x0112) {
offset += 8
orientation = dataView.getUint16(offset, littleEndian)
break
}
idx++
}
}
return orientation
} catch (error) {
return Promise.reject(error)
}
}
评论(0条):