Exif file format
JPEG format and Marker
JPEG 文件以十六进制 0xFFD8 开始,以 0xFFD9 结束。在 JPEG 数据中有像 0xFF 这样的数据,这些被称为「标志」(Marker),它表示 JPEG 信息数据段。0xFFD8 表示 SOI(Start of image 图像开始,0xFFD9 表示 EOI(End of image 图像结束)。这两个特殊的标志没有附加的数据,而其他的标志在标志后都带有附加的数据。
Marker 的基本格式如下:
0xFF + Marker Number(1 byte) + Data size(2 bytes) + Data(n bytes)
0xFF + 标志数字(1字节) + 数据大小(2字节) + 数据(n字节)
数据大小(Data size)使用「Motorola」方式表示 byte 的顺序,起始从高字节开始。(也就是 big-endian format 参考 Wikipedia)。需要注意的是,数据中包含着有关于数据大小的描述。
如果一个 Marker 为:FF xx - 00 0C - 01 02 03 04 05 06 08 09 0A
:
它代表的含义是:Marker(OxFFC1
) 这个标记对应的数据的长度是 0x000C
(12 字节),但这个数据长度「12 字节」也包含「对于数据的描述」的字节,所以「真实数据」在这里应该是 10 个字节。所以实际的数据是:00 0C - 01 02 03 04 05 06 08 09 0A
这 12 个字节。
在 JPEG 格式中,在一些 Marker 描述数据后就是SOS(Start of stream)Marker,紧随 SOS Mark 后的就是图片本身的流,直到 EOI Marker 终结。
Marker used by Exif(Exif 中使用到的标记)
The marker 0xFFE0~3 被称为「Application Marker」(应用程序标记),这一系列标记对于 JPEG 文件的解码并不必须。更多的,他们是被一些设备的图像格式 JFIF(JPEG File Interchange Format)用来记录数码相机的配置信息和缩略图像,比如:老款的 olympus/canon/casio/agfa 数码相机。
Exif 同时也使用「Application Marker」(应用程序标记)来插入数据,但是会使用 APP1(0xFFE1)Marker 来避免与 JFIF 格式之间的冲突。所有 Exif 文件格式都以一下格式开始:
SOI marker | APP1 marker | APP1 Data | Other Marker |
---|---|---|---|
FFD8 | FFE1 | SSSS 4578660000 TTTT… | FFXX SSSS DDDD… |
Exif 文件最开始的码位是 FFD8,所以它是一个 JPEG 文件,之后是 APP1 Marker 紧随其后。Exif 里所有的数据都是在 APP1 对应的数据区内。上面表格中「SSSS」代表的是 APP1 数据区(Exif 的数据区)内的数据大小。
在「SSSS」之后,是 APP1 的数据,这段数据的第一部分用来判定 Exif 是否存在,使用的是 ASCII 码对应的「Exif」和 2个字节的 0x00
。
APP1 Marker 对应的数据区结束之后,其它 JPEG 的数据紧随其后。
Exif 数据结构:
Exif(APP1)基本结构如下面的表格所示,采用了 Intel 的 little-endian 的字节顺序,并且包含了 JPEG 格式的缩略图。如上文说,Exif 数据的开始是 ASCII 字符的 「Exif」和 2 个字节的 0x00,接着就是 Exif 具体数据。Exif 使用「TIFF」格式来存储数据。更多关于「TIFF」格式的信息,可以参考 TIFF6.0 specification。
TIFF Header:
TiFF header 指的是 TIFF 格式的前 8 个字节,前两个定义了 TIFF 数据采用何种字节顺序:
- 如果是
0x4949
= II,表示采用 Inter 的小端字节顺序(49 49:01001001 01001001
); - 如果为
0x4d4d
= MM,表示采用 Motorola 的大端字节顺序(4d 4d:01001101 01001101
);
例如:十进制值 305419896
的十六进制表示是:12345678
,如果以 Motroal 式的 big-endian 进行存储,对应的 code unit 就是 0x12 0x34 0x56 0x78
;而如果是 Intel 式的 little-endian 进行存储,则对应 0x78 0x56 0x34 0x12
。
不过市面上大多数的数码相机似乎都是使用的 intel 的 little-endian:
- Ricoh 使用 big-endian;
- Sony 旗下的除了 D700 都是使用 little-endian;
- Kodak 的 DC200/210/240 使用 big-edian,而 DC220/260 使用 PowerPC 却仍旧是 little-endian。
因此,每次我们想从图像文件获取 Exif 信息的时候,都必须确认其自己的对齐方式。虽然 JPEG 只采用了 Motorola 的 big-endian 字节顺序,但 little-edian 和 big-endian 在 Exif 中都可以使用。完全不明白为什么 Exif 为什么不将字节的对齐方法修正为 Motorola 的 big-endian 方式。
接下来的 2 个字节是 2 个字节长的 0x002A
,如果是 intel 的 little-endian,则为 0x2A
, 0x00
;如果是 Motorola 的 big-endian,则是 0x00
,0x2A
。
TiFF header 中包含 8 个字节,除去上面提到四个字节,剩下的四个字节是相对于第一个 IFD(Image File Directory 图像文件目录) 的偏移量。这个偏移量的计算包含了从 TIFF header 中的第一个字节,即从「II」或者「MM」,而该偏移量结束之后往往紧跟着的就是 IFD ,所以这个偏移量的值(一般情况下)是 0x00000008
。
Byte Align | Tag Marker | Offset to first IFD |
---|---|---|
「II」或者「MM」 | 2A00 |
0x00000008 |
IFD:Image file directory
在 TIFF header 之后的是包含了图像信息的 IFD,image file directory(可以大致翻译为:图像文件目录)。
对照下表,可以对其结构有个大概的认识。最前面的两个字节 EEEE
表示了当前「图像文件目录」中所包含的目录实体的数量。之后是图像目录实体本身,每一个目录的长度是 12 个字节。在最后一个目录实体中,有长度为 4 个字节的数据(表中的 LLLLLLLL
),它表示相对于相对于下一个目录试题的字节偏移量。而如果这个偏移量是 0x00000000
,则意味着这是 IFD 中的最后一个目录实体。
以上表格中:
TTTT
(2 个字节)表示的是 Tag number,表示一种类型的数据;ffff
(2 个字节)表示数据的格式;NNNNNNNN
(4 个字节)表示所组成元素的数量;DDDDDDDD
(4 个字节)表示所包含数据的长度,或者是数据存储地址的偏移量。
数据格式(Data format) 上表中 ffff 对应数据的格式如下表所示。rational 表示一个分数,它包含两个signed/unsigned long integer值并且第一个为分子,第二个为分母。
将 Bytes/component
的数值乘以 NNNNNNNN
可以得到全部数据的字节长度。( a ‘bytes/components’ value * number of component stored ‘NNNNNNNN’ = total data byte length)
- 如果全部数据的字节长度小于 4 个字节,
DDDDDDDD
区域就包含着当个标签对应的值; - 如果数据的字节长度大于于 4 个字节,
DDDDDDDD
中的值指的是数据地址的偏移量。
IFD data structure
在 Exif 格式中,第一个图像文件目录(IFD)是 IFD0(主图像的 IFD),之后是 IFD1(缩略图的 IFD),接着图像文件目录链(IFD link)结束。IFD0/IFD1 中没有快门速度(shutter speed),焦距(focal length)等任何关于数码相机的信息。IFD0 有一个特别的标签(Tag)Exif Offset(0x8769),在这个标签中是 Exif SubIFD 的偏移量。Exif SubIFD 的数据格式也是 IFD 的数据格式,它包含了数码相机的相关信息。
例,如果 TIFF 的第一部分的数据如下:
0000: 49 49 2A 00 08 00 00 00-02 00 1A 01 05 00 01 00
0010: 00 00 26 00 00 00 69 87-04 00 01 00 00 00 11 02
0020: 00 00 40 00 00 00 48 00-00 00 01 00 00 00
则其中的数据可以按照以下步骤(方法)来读取:
0x0000 ~ 0x0001
是49 49
,相当于「ii」,可以判断为 Intel 的 little-endian 对齐方式;0x0004 ~ 0x0007
是0x08 00 00 00
,IFD0 就是从0x00000008
开始;0x0008 ~ 0x0009
是0x02 00
,那么 IFD0 种图像目录实体的数量就是 2;0x000a ~ 0x000b
是0x1A 01
,它代表 XResolution 标签,0x001A
,包含了图像文件水平像素值(horizontal resolution of image)0x000c ~ 0x000d
是0x05 00
,数值表现的值 unsigned rational。0x000e ~ 0x0011
是0x01 00 00 00
,包含的元素数量是 1,unsigned relational 的数据长度是每个元素 8 个字节(8 bytee/components),所以所有数据的总长度是 1 * 8 = 8 bytes。数据总长度大于 4 个字节,所以接下来的 4 个字节是数据的偏移量。0x0012 ~ 0x0015
是0x26 00 00 00
,即水平分辨率(XResolutation)的数据存储在 0x0026;0x0026 ~ 0x0029
是0x48 00 00 00
,分子是 72;0x00 2a ~ 0x00 2d
是0x01 00 00 00
,分母是 1;所以水平分辨率(XResoultion)是 72/1。0x0016 ~ 0x0017
是0x69 87
,下一个标签是 Exif 偏移量(ExifOffset,0x8769),值是 Exif SubIFD 的偏移量。 数据结构是 0x0004,unsigned long integer;- 这个 tag 只有一个 component,unsigned long integer 的 4 bytes/components,所以数据的总长度是 4 个字节,等于四个字节的情况,那么之后的四个字节就是 ExifSubIFD 的偏移量;
0x001e~0x0021
是0x11020000
,所以 ExifSubIFD 从0x0211
开始;- 这是当前 IFD 的最后一个 entry,之后的四个字节代表了「到下一个 IFD」的偏移量;
0x0022~0x0025
是0x40000000
, 所以下一个 IFD 开始的位置是0x0040
。
技术发展迭代很快,所以这些笔记内容也有类似新闻的时效性,不免有过时、或者错误的地方,欢迎指正 ^_^。
BEST
Lien(A.K.A 胡椒)