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文件格式这里推荐一篇文章,讲得比较详细,在这里就不再赘述了。
这里推荐打开一张图片,然后对照着去看每一个标记代码和字段,会理解的更好一些,放上我对照的图片。
下面列出了一些常见标记内容。
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格式。
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
+数据块
+数据块
+数据块
+······
同样以实际例子来看一下各个字段:
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文件格式如下,中间的部分可以无限重复,下面分别介绍每一个块。
文件头 Header
文件头一般为6字节
,由GIF署名(Signature)和版本号(version)组成,如下图选中部分:
开头必须为GIF
三个字符,一般版本号为87a
或者89a
逻辑屏幕标识符 Logical Screen Descriptor
紧跟在文件头后面,这个块告诉decoder(解码器)图片需要占用的空间,固定为7字节
,内容如下:
名称 | 字节数 | 说明 |
---|---|---|
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像素
现代的处理引擎一般会忽略掉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)
个字节。
如下图,本例中由于piexl
值为101
则全局颜色列表大小为3*2^(5+1)=192字节
,内容如下:
应用程序扩展 Application Extension
GIF中扩展块都以0x21
开始,后一个字节是扩展标签,标识扩展用途。应用程序扩展的标签是0xFF
,它包含有应用程序的标识信息和应用程序数据。其中 Netscape
应用程序扩展常用于控制GIF的动画循环次数。Netscape
扩展长19个字节
,前14个是应用程序的ACSII信息,后四个是数据子块,用于指定GIF的循环次数, 按unsigned整型存储,0表示无限循环。
以本图为例:
名称 | 字节数 | 说明 |
---|---|---|
扩展块标识 | 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)
同应用程序扩展的前三字节,均为固定值。
对其字段进行剖析:
名称 | 字节数 | 说明 |
---|---|---|
扩展块标识 | 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字节
名称 | 字节数 | 说明 |
---|---|---|
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字节
,即表示图像结束。
尾部标志 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
可能被记为6
,BC
记为7
等等,这就是生成的串表,最后压缩的结果可能是0123236398145
(不是这个字串的最终答案,只是一个格式参考)这样的形式
随着新的串(string)不断被发现,标号也会不断地增长,如果原数据过大,生成的标号集(string table)会越来越大,这时候操作这个集合就会产生效率问题。如何避免这个问题呢?Gif在采用lzw算法的做法是当标号集足够大的时候,就不能增大了,干脆从头开始再来,在这个位置要插入一个标号,就是清除标志CLEAR,表示从这里我重新开始构造字典,以前的所有标记作废,开始使用新的标记。
这时候又有一个问题出现,足够大是多大?这个标号集的大小为比较合适呢?理论上是标号集大小越大,则压缩比率就越高,但开销也越高。 一般根据处理速度和内存空间连个因素来选定。GIF规范规定的是12位,超过12位的表达范围就推倒重来,并且GIF为了提高压缩率,采用的是变长的字长。比如说原始数据是8位,那么一开始,先加上一位再说,开始的字长就成了9位,然后开始加标号,当标号加到512时,也就是超过9为所能表达的最大数据时,也就意味着后面的标号要用10位字长才能表示了,那么从这里开始,后面的字长就是10位了。依此类推,到了2^12也就是4096时,在这里插一个清除标志,从后面开始,从9位再来。
这里给一个例子,看懂这个例子,基本就明白了。