image文件格式解析

1. JPEG

JPEG(jay-peg)是一种针对照片视频而广泛使用的一种有损压缩标准方法,其本身只有描述如何将一个视频转换为字节的数据流。但并没有说明这些字节如何在任何特定的存储媒体上被封存起来。JPEG的压缩方式通常是破坏性数据压缩,意即在压缩过程中图像的质量会遭受到可见的破坏,有一种以JPEG为基础的标准Lossless JPEG是采用无损的压缩方式,但Lossless JPEG并没有受到广泛的支持。

一个由C-Cube Microsystems等公司所创建的额外标准,称为JFIF(JPEG File Interchange Format,JPEG文件交换格式)详细说明如何从一个JPEG流,产出一个适合于电脑存储和传输(像是在互联网上)的文件。

JPEG/JFIF是万维网(World Wide Web)上最普遍的被用来存储和传输照片的格式。它并不适合于线条绘图(drawing)和其他文字或图标(iconic)的图形,因为它的压缩方法用在这些类型的图形上,得到的结果并不好(PNG和GIF格式通常是用来存储这类的图形;GIF每个像素只有8比特,并不很适合于存储彩色照片,PNG可以无损地存储照片,但是文件太大的缺点让它不太适合在网络上传输)。

简单概括下jpeg文件格式:

  • 整个内容由标记信息和数据组成,标记信息包括标记代码和具体字段组成,记录了图片的某些属性,数据为图像的具体数据。
  • FF为特殊值,如果后边跟的是00,即FF 00表示图像数据内容中的FF值,若后边不为0,类似FF D8为某个具体标记码的值,代表标记信息的开始。
  • 常用的标记有SOI、APP0、DQT、SOF0、DHT、DRI、SOS、EOI

JFIF文件格式这里推荐一篇文章,讲得比较详细,在这里就不再赘述了。

这里推荐打开一张图片,然后对照着去看每一个标记代码和字段,会理解的更好一些,放上我对照的图片。

xdd-jpeg

下面列出了一些常见标记内容。

SOI, Start of Image,图像开始

标记代码   2字节   固定值0xFFD8

APP0, Application, 应用程序保留标记0

标记代码   2字节   固定值0xFFE0
包含9个字段:

字段名称 字段长度 字段含义
数据长度 2字节 ①~⑨ 9个字段的总长度,即不包括标记代码,但包括本字段
标识符 5字节 固定值0x 4A46 4946 00,即字符串JFIF0
版本号 2字节 一般是0x0102,表示JFIF的版本号1.2,可能会有其他数值代表其他版本
X和Y的密度单位 1字节 0:无单位;1:点数/英寸;2:点数/厘米
X方向像素密度 2字节 取值范围未知
Y方向像素密度 2字节 取值范围未知
缩略图水平像素数目 1字节 取值范围未知
缩略图垂直像素数目 1字节 取值范围未知
缩略图RGB位图 长度可能是3的倍数 缩略图RGB位图数据

本标记段可以包含图像的一个微缩版本,存为24位的RGB像素。如果没有微缩图像(这种情况更常见),则字段缩略图水平像素数目和字段缩略图垂直像素数目的值均为0。

APPn,Application,应用程序保留标记n,其中n=1~15(任选)

标记代码   2字节   固定值0xFFE1~0xFFEF
包含2个字段:

字段名称 字段长度 字段含义
数据长度 2字节 所有字段的总长度,即不包括标记代码,但包括本字段
详细信息 5字节 内容不定

Adobe Photoshop生成的JPEG图像中就用了APP1和APP13两个标记段分别存储了一幅图像的副本。

DQT,Define Quantization Table,定义量化表

标记代码   2字节   固定值0xFFDB
包含2个具体字段:

字段名称 字段长度 字段含义
数据长度 2字节 所有字段的总长度,即不包括标记代码,但包括本字段
量化表
精度及量化表ID 1字节 高4位:精度,只有两个可选值:
0:8位;
1:16位,低4位:量化表ID,取值范围为0~3
表项 (64×(精度+1))字节 例如8位精度的量化表,其表项长度为64×(0+1)=64字节

本标记段中,字段②可以重复出现,表示多个量化表,但最多只能出现4次。

SOF0,Start of Frame,帧图像开始

标记代码   2字节   固定值0xFFC0
包含9个具体字段:

字段名称 字段长度 字段含义
数据长度 2字节 ①~⑨ 9个字段的总长度,即不包括标记代码,但包括本字段
图像高度 2字节 图像高度(单位:像素),如果不支持 DNL 就必须 >0
图像宽度 2字节 图像宽度(单位:像素),如果不支持 DNL 就必须 >0
颜色分量数 1字节 只有3个数值可选
1:灰度图;
3:YCrCb或YIQ;
4:CMYK,而JFIF中使用YCrCb,故这里颜色分量数恒为3
颜色分量信息 颜色分量数×3字节(通常为9字节)
颜色分量ID 1字节 取值范围未知
水平/垂直采样因子 1字节 高4位:水平采样因子 低4位:垂直采样因子
量化表 1字节 当前分量使用的量化表的ID

本标记段中,字段⑥应该重复出现,有多少个颜色分量,就出现多少次(一般为3次)。

DHT,Difine Huffman Table,定义哈夫曼表

标记代码   2字节   固定值0xFFC4
包含2个具体字段:

字段名称 字段长度 字段含义
数据长度 2字节 所有字段的总长度,即不包括标记代码,但包括本字段
哈夫曼表
表ID和表类型 1字节 高4位:类型,只有两个值可选
0:DC直流;
1:AC交流 低4位:哈夫曼表ID,注意,DC表和AC表分开编码
不同位数的码字数量 16字节
编码内容 16个不同位数的码字数量之和(字节)

本标记段中,字段②可以重复出现(一般4次),也可以致出现1次。例如,Adobe Photoshop 生成的JPEG图片文件中只有1个DHT标记段,里边包含了4个哈夫曼表;而Macromedia Fireworks生成的JPEG图片文件则有4个DHT标记段,每个DHT标记段只有一个哈夫曼表。

DRI,Define Restart Interval,定义差分编码累计复位的间隔

标记代码   2字节   固定值0xFFDD
包含2个具体字段:

字段名称 字段长度 字段含义
数据长度 2字节 所有字段的总长度,即不包括标记代码,但包括本字段
MCU块的单元中的重新开始间隔 2字节 设其值为n,则表示每n个MCU块就有一个RSTn标记。第一个标记是RST0,第二个是 RST1等,RST7后再从RST0重复

如果没有本标记段,或间隔值为0时,就表示不存在重开始间隔和标记RST

SOS,Start of Scan,扫描开始 12字节

标记代码   2字节   固定值0xFFDA
包含2个具体字段:

字段名称 字段长度 字段含义
数据长度 2字节 所有字段的总长度,即不包括标记代码,但包括本字段
颜色分量数 1字节 应该和SOF中的字段⑤的值相同,即:
1:灰度图是;
3: YCrCb或YIQ;
4:CMYK,而JFIF中使用YCrCb,故这里颜色分量数恒为3
颜色分量信息
颜色分量ID 1字节
直流/交流系数表号 1字节 高4位:直流分量使用的哈夫曼树编号
低4位:交流分量使用的哈夫曼树编号
压缩图像数据
谱选择开始 1字节 固定值0x00
谱选择结束 1字节 固定值0x3F
谱选择 1字节 在基本JPEG中总为00

本标记段中,字段③应该重复出现,有多少个颜色分量(字段②),就出现多少次(一般为3次)。本段结束后,紧接着就是真正的图像信息了。图像信息直至遇到一个标记代码就自动结束,一般就是以EOI标记表示结束。

EOI,End of Image,图像结束 2字节

标记代码   2字节   固定值0xFFD9

2. PNG

便携式网络图形(Portable Network Graphics,PNG)是一种无损压缩的位图图形格式,支持索引、灰度、RGB三种颜色方案以及Alpha通道等特性。PNG的开发目标是改善并取代GIF作为适合网络传输的格式而不需专利许可,所以被广泛应用于互联网及其他方面上。

PNG图像格式文件由一个8字节的PNG文件标识(file signature)域和3个以上的后续数据块(IHDR、IDAT、IEND)组成。PNG文件包括8字节文件署名(89 50 4E 47 0D 0A 1A 0A,十六进制),用来识别PNG格式。

xdd-png

PNG定义了两种类型的数据块:一种是PNG文件必须包含、读写软件也都必须要支持的关键块(critical chunk);另一种叫做辅助块(ancillary chunks),PNG允许软件忽略它不认识的附加块。这种基于数据块的设计,允许PNG格式在扩展时仍能保持与旧版本兼容。

常见数据块内容

关键数据块中有4个标准数据块:

  • 文件头数据块IHDR(header chunk):包含有图像基本信息,作为第一个数据块出现并只出现一次。
  • 调色板数据块PLTE(palette chunk):必须放在图像数据块之前。
  • 图像数据块IDAT(image data chunk):存储实际图像数据。PNG数据允许包含多个连续的图像数据块。
  • 图像结束数据IEND(image trailer chunk):放在文件尾部,表示PNG数据流结束。

每个数据块的结构都是一定的:

名称 字节数 说明
Length (长度) 4字节 指定数据块中数据域的长度,其长度不超过(231-1)字节
Chunk Type Code (数据块类型码) 4字节 数据块类型码由ASCII字母(A-Z和a-z)组成
Chunk Data (数据块数据) 可变长度 存储按照Chunk Type Code指定的数据
CRC (循环冗余检测) 4字节 存储用来检测是否有错误的循环冗余码

CRC(cyclic redundancy check)域中的值是对Chunk Type Code域和Chunk Data域中的数据进行计算得到的。CRC具体算法定义在ISO 3309和ITU-T V.42中,其值按下面的CRC码生成多项式进行计算:
x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1

下面是常见数据块种类,有可能没有,灰色为关键数据块。所以整个PNG文件的格式为8950 4E47 0D0A 1A0A+数据块+数据块+数据块+······

同样以实际例子来看一下各个字段:

xdd-png

IHDR 文件头数据块IHDR(header chunk)

它包含有PNG文件中存储的图像数据的基本信息,并要作为第一个数据块出现在PNG数据流中,而且一个PNG数据流中只能有一个文件头数据块。文件头数据块由13字节组成,它的格式如下表所示。

域的名称 字节数 说明
Width 4字节 图像宽度,以像素为单位
Height 4字节 图像高度,以像素为单位
Bit depth 1字节 图像深度:
索引彩色图像:1,2,4或8
灰度图像:1,2,4,8或16 真彩色图像:8或16
ColorType 1字节 颜色类型:
0:灰度图像, 1,2,4,8或16
2:真彩色图像,8或16
3:索引彩色图像,1,2,4或8
4:带α通道数据的灰度图像,8或16 6:带α通道数据的真彩色图像,8或16

PLTE 调色板数据块PLTE(palette chunk)

它包含有与索引彩色图像(indexed-color image)相关的彩色变换数据,它仅与索引彩色图像有关,而且要放在图像数据块(image data chunk)之前。

颜色 字节 意义
RED 1字节 0 = 黑色, 255 = 红
Green 1字节 0 = 黑色, 255 = 绿色
Blue 1字节 0 = 黑色, 255 = 蓝色

因此,调色板的长度应该是3的倍数,否则,这将是一个非法的调色板。
对于索引图像,调色板信息是必须的,调色板的颜色索引从0开始编号,然后是1、2……,调色板的颜色数不能超过色深中规定的颜色数(如图像色深为4的时候,调色板中的颜色数不可以超过2^4=16),否则,这将导致PNG图像不合法。
真彩色图像和带α通道数据的真彩色图像也可以有调色板数据块,目的是便于非真彩色显示程序用它来量化图像数据,从而显示该图像。

IDAT图像数据块IDAT(image data chunk)

它存储实际的数据,在数据流中可包含多个连续顺序的图像数据块。
格式如同块格式,注意的是一般前一个IDAT写满后再回另开一个IDAT,所以如果发现一个IDAT长度不对就开始一个新的IDAT,很有可能是在IDAT中进行隐写

IEND 图像结束数据IEND(image trailer chunk)

它用来标记PNG文件或者数据流已经结束,并且必须要放在文件的尾部。00 00 00 00 49 45 4E 44 AE 42 60 82,
IEND数据块的长度总是0000 0000,数据标识总是IEND4945 4E44,因此,CRC码也总是AE42 6082

3. GIF

图像互换格式(GIF,Graphics Interchange Format)是一种位图图形文件格式,以8位色(即256种颜色)重现真彩色的图像。它实际上是一种压缩文档,采用LZW压缩算法进行编码,有效地减少了图像文件在网络上传输的时间。它是目前万维网广泛应用的网络传输图像格式之一。只要图像不多于256色,则可既减少文件的大小,又保持成像的质量。一方面,在高彩图片上有着不俗表现的JPG格式却在简单的折线上效果不如人意。因此图像互换格式格式普遍适用于图表,按钮等等只需少量颜色的图像(如黑白照片)。
1989年,CompuServe发布了一个名为89a的增强版本,在这个版本中,为图像互换格式文档扩充了图形控制区块、备注、说明、应用程序接口等四个区块,并提供了对透明色和多帧动画的支持。现在我们一般所说的GIF动画都是指89a的格式。

GIF文件格式如下,中间的部分可以无限重复,下面分别介绍每一个块。
gif format

文件头 Header

文件头一般为6字节,由GIF署名(Signature)和版本号(version)组成,如下图选中部分:

gif header

开头必须为GIF三个字符,一般版本号为87a或者89a

逻辑屏幕标识符 Logical Screen Descriptor

紧跟在文件头后面,这个块告诉decoder(解码器)图片需要占用的空间,固定为7字节,内容如下:

gif logical screen descriptor
名称 字节数 说明
Canvas Width (画布宽度) 2字节 图像宽度
Canvas height (画布高度) 2字节 图像高度
Global Color Table Flag (全局颜色列表标志) 1bit 当置位时表示有全局颜色列表,pixel值有意义
Color resolution (颜色分辨率) 3bit cr+1的值为图像颜色的分辨率
Sort flag(排序标志) 1bit 如果为1,global color table中的颜色以“重要性降低”(decreasing importance)来排序,通常也就是“频率降低”方式。这个标志可能会对decoder有所帮助,不过并不是必要的,因此这里的例子将它简单地设置为0。这个标志是历史遗留产物。
pixel(全局颜色列表大小) 3bit 索引数2^(pixel+1)
背景颜色索引(background color index) 1字节 仅当global color table flag为1才有意义,此时这个标志应该设为0。在之前的“picture wall”模式中,我们说到GIF会有一个virtual canvas画布,因此理所当然会存在一个背景色,而我们这个标志就是指出这个背景颜色。同样,在现代引擎中它也随着“picture wall模式”一起被弃用了。
pixel aspect ratio(像素高宽比) 1字节 全局像素宽高比,GIF标准没有给出它存在的理由,不过目前对它的处理方式只是读取其值并保存下来。所有此值不为0的比例都是用(N+15)/64

注意,GIF格式多字节内容为小端模式,与PNG,GIF不同,即我们在实际图片中看到的宽度字节流40 01,其实值为0140宽度为320像素

gif Logical Screen Descriptor

现代的处理引擎一般会忽略掉canvas width和canvas height,因为GIF最初被设计为一种类似picture wall(照片墙)的东西,思路是讲图片在一个virtual canvas(虚拟画布)中显示;现在GIF通常是作为用来保存动画中的帧的图片库,相当于是一个图片集合,由GIF处理引擎来在自己的canvas(画布)中处理图片,因此现在GIF格式中保存的信息显得没太大作用了。所以,现在canvas width和canvas height基本上就是个摆设了。

全局颜色列表 Global Color Table

紧跟于逻辑屏幕标识符,GIF格式可以拥有global color table,或用于针对每个子图片集,提供local color table。每个color table由一个RGB(就像通常我们见到的(255,0,0)红色 那种)列表组成。由于之前表长度为2^(N+1),对于每个RGB值,则此表总共占用3*2^(N+1)个字节。

gif Global Color Table

如下图,本例中由于piexl值为101则全局颜色列表大小为3*2^(5+1)=192字节,内容如下:

color table

应用程序扩展 Application Extension

GIF中扩展块都以0x21开始,后一个字节是扩展标签,标识扩展用途。应用程序扩展的标签是0xFF,它包含有应用程序的标识信息和应用程序数据。其中 Netscape 应用程序扩展常用于控制GIF的动画循环次数。Netscape 扩展长19个字节,前14个是应用程序的ACSII信息,后四个是数据子块,用于指定GIF的循环次数, 按unsigned整型存储,0表示无限循环。
以本图为例:
Application Extension

名称 字节数 说明
扩展块标识 1字节 固定值0x21
扩展类型标识 1字节 应用程序扩展 固定值为0xFF
应用程序信息大小 1字节 此例为0x0B
应用程序信息 NETSCAPE2.0或其他值
应用程序数据大小 1字节 0x03
索引 1字节 0x01
数据 2字节 unsigned整数,0x00 00标识无限循环
结束符 1字节 固定值0x00

图像控制扩展 Graphics Control Extension

用来控制跟在他后面图像的显示。前三字节0x21 F9 04(这4字节不包括最终结束字节00)同应用程序扩展的前三字节,均为固定值。

Graphics Control Extension

对其字段进行剖析:

名称 字节数 说明
扩展块标识 1字节 固定值0x21
扩展类型标识 1字节 应用程序扩展 固定值为0xF9
应用程序信息大小 1字节 固定值0x04
保留位 3bit
disposal method(处理方法) 3bit 处置图形的方法:
0 - 表示不处置
1 - 不处置图形,把图形从当前位置移去
2 - 回复到背景色
3 - 回复到先前状态
4-7 自定义
Use Input Flag(用户输入标志) 1bit 指出是否期待用户有输入之后才继续进行下去,置位表示期待,值否表示不期待。用户输入可以是按回车键、鼠标点击等,可以和延迟时间一起使用,在设置的延迟时间内用户有输入则马上继续进行,或者没有输入直到延迟时间到达而继续
Transparent Color Flag(透明颜色标志) 1bit 置位表示使用透明颜色
Delay time(延时) 2字节 单位为1/100秒,暂停规定时间后再继续往下处理数据流
Transparent Color Index(透明色索引) 1字节 透明色索引值
结束符 1字节 固定值0x00

图像标识符 Image Descriptor

固定长度   10字节

gif image descriptor gif image descriptor
名称 字节数 说明
image separator(图像分隔符) 2字节 固定值0x2C
image left(X方向偏移量) 2字节
image top(Y方向偏移量) 1字节
image width(图片宽度) 2字节 图片宽度
image height(图片高度) 2字节 图片高度
local color table flag(局部颜色表标记) 1bit 置位时标识紧接在图象标识符之后有一个局部颜色列表,供紧跟在它之后的一幅图象使用;值否时使用全局颜色列表,忽略pixel值。
interlace flag(隔行扫描标志) 1bit 隔行扫描方式会改变图像渲染到屏幕上的方式,从而减少烦人的视觉闪烁(类似垂直同步)。隔行扫描在显示器上的效果是,第一轮扫描先立即模糊地显示图像,随后再一轮将其填充锐化。这种方式会让人们感觉更舒服,因为它可以让人们模糊地意识到即将显示的东西是什么,而不是等待像素点被一行一行地填充绘制。要支持这种显示方式,图片的扫描行需要以一种特定的顺序来存储,需要分为4个部分,每个部分都是一个完整的模糊显示,通过4次显示加成使得图片越来越清晰,最终完全呈现。
否则使用顺序排列。
sort flag(分类标志) 1bit 如果置位表示紧跟着的局部颜色列表分类排列
保留位 2bit 必须初始化为0
pixel 3bit 局部颜色表大小,2^(pixel+1)

局部颜色表 Local Color Table

如果local color table flag置位,则有这个内容,格式同全局,仅供此幅图像使用。

图像数据 Image data

这里就是真正的图像信息,第一个字节是LZW minimum code size,这个值用来解码这个经过压缩的输出编码。之后为数据子块,子块第1个字节告诉后面图像数据有多少字节,范围为0-255,子块可以有多个,不断重复,直到一个子块告诉你后面内容为0字节,即表示图像结束。

Image data

尾部标志 Trailer

固定值0x3B 表示文件结束

LZW 编码

与Huffman编码相比,LZW编码法被视作将不同长度字符串以固定长的码编辑(霍夫曼编码将固定长度字符用不同长度的码编辑)。其优点在于此方法只需存储一个相当小的表格,即可存储数据还原时相对应的值,所以所需成本相对地低;然而,这种算法的设计着重在实现的速度,由于它并没有对数据做任何分析,所以并不一定是最好的算法

LZW算法将每个第一次出现的串放在一个串表中,用一个数字来表示串,压缩文件只存贮数字,而不存贮串,从而使图像文件的压缩效率得到较大的提高。
LZW算法首先建立一个字符串表,把每一个第一次出现的字符串放入串表中,并用一个数字来表示,这个数字与此字符串在串表中的位置有关,并将这个数字存入压缩文件中,如果这个字符串再次出现时,即可用表示它的数字来代替,并将这个数字存入文件中。如AB字符串,如果在压缩时用258表示,只要再次出现,均用258表示,并将AB字符串存入串表中,在图像解码时遇到数字258,即可从串表中查出258所代表的字符串AB

假设我们要压缩ABCDAABBCCDABCD这一串,这四个字符可以用2bit来表示(0-A 1-B 2-C 3-D)。GIF规定的清除标志CLEAR的数值是原始数据字长表示的最大值加1,这时候就要用3位来表示了,即4-CLEAR,GIF还规定了一个结束标志END,它的值是清除标志CLEAR再加1,即5-END,之后的AB可能被记为6BC记为7等等,这就是生成的串表,最后压缩的结果可能是0123236398145(不是这个字串的最终答案,只是一个格式参考)这样的形式

随着新的串(string)不断被发现,标号也会不断地增长,如果原数据过大,生成的标号集(string table)会越来越大,这时候操作这个集合就会产生效率问题。如何避免这个问题呢?Gif在采用lzw算法的做法是当标号集足够大的时候,就不能增大了,干脆从头开始再来,在这个位置要插入一个标号,就是清除标志CLEAR,表示从这里我重新开始构造字典,以前的所有标记作废,开始使用新的标记。
这时候又有一个问题出现,足够大是多大?这个标号集的大小为比较合适呢?理论上是标号集大小越大,则压缩比率就越高,但开销也越高。 一般根据处理速度和内存空间连个因素来选定。GIF规范规定的是12位,超过12位的表达范围就推倒重来,并且GIF为了提高压缩率,采用的是变长的字长。比如说原始数据是8位,那么一开始,先加上一位再说,开始的字长就成了9位,然后开始加标号,当标号加到512时,也就是超过9为所能表达的最大数据时,也就意味着后面的标号要用10位字长才能表示了,那么从这里开始,后面的字长就是10位了。依此类推,到了2^12也就是4096时,在这里插一个清除标志,从后面开始,从9位再来。

这里给一个例子,看懂这个例子,基本就明白了。

LZW example