BMP、JPEG、GIF图片详解
BMP(全称Bitmap),又叫位图文件,是Window操作系统中的标准图像文件格式。默认文件扩展名:BMP或者bmp(有时也会以.DIB或.RLE作扩展名)。它采用位映射存储格式除了图像深度可选以外,不采用其他任何压缩,因此,BMP文件占用空间很大,但是没有失真。BMP文件的图像深度可选lbit4bit8bit16bit24bit及32bit。BMP文件存储数据时,图像的扫描方式是按从左到右、从
常用的图片格式:JPEG(或 JPG)、BMP 和 GIF。其中 JPEG(或 JPG)和 BMP 是静态图片,而 GIF 则是动态图片。
BMP
参考自:
正点原子光盘中的BMP图片文件详解.pdf
位图
普通的显示器屏幕是由许许多多的点构成的,这些点被称之为像素。显示屏显示时采用扫描的方法:电子枪每次从左到右扫描一行,为每个像素着色,然后从上到下这样扫描若干行,就扫过了一屏。为了防止闪烁,每秒要重复上述过程几十次。例如我们常说的屏幕分辨率为 640*480,刷新频率为 70Hz,意思是说每行要扫描 640 个象素,一共有 480 行,每秒重复扫描屏幕 70 次。
我们称这种显示器为位映象设备。所谓位映象,就是指一个二维的像素矩阵,而位图就是采用位映象方法显示和存储的图象。
调色板
有一个长宽各 200 像素,颜色数为 16 色的彩色图,每像素 3 字节。整个图象要用 200 * 200 * 3,约 120k 字节,如果我们用下面的方法,就能省的多:
16 色图,也就是说这幅图中最多只有 16 种颜色,我们可以用一个表:表中的每一行记录一种颜色的 RGB 值。这样当我们表示一个像素的颜色时,只需要指出该颜色是在第几行,即该颜色在表中的索引值。假设表的第 0 行为 255,0,0(红色),那么当某个像素为红色时,只需要标明 0 即可。
16 种颜色可以用 4 位(bit)表示,即一个像素半个字节。整个图象要用 200 * 200 * 0.5,约 20k 字节,再加上表占用的字节为 3 * 16 = 48 字节,一共 20 + 48 = 68 字节,整个占用的字节数约为前面的 1 / 6。
这张 RGB 的表,即是我们常说的调色板 (Palette) ,另一种叫法是颜色查找表 LUT(LookUpTable),似乎更确切一些。
真彩图
三元色 RGB 每个颜色可以分成 0 到 255 共 256 个等级。因此 RGB 一共可以表示 256 * 256 * 256,约 1 千 6 百万种颜色。
有一种图,它的颜色数高达 256 * 256 * 256 种,也就是说包含 RGB 中的所有颜色,这种图叫做真彩色图(TrueColor)。真彩色图并不是说一幅图包含了所有的颜色,而是说它具有显示所有颜色的能力,即最多可以包含所有的颜色。
表示真彩色图时,每个像素直接用 RGB 三个分量字节表示,而不采用调色板技术:
总共有 2 的 24 次方种颜色 -> 调色板有 2 的 24 次方行 -> 调色板表示一个像素要用 24 位
而直接用 RGB 表示像素,字节数也是 24 位。并且使用调色盘时,还要加上一个 256 * 256 * 256 * 3 个字节的大调色板。所以真彩色图直接用 RGB 三个分量表示,它又叫做 24 位色图。
BMP简介
- BMP(全称 Bitmap),又叫位图文件,是 Window 操作系统中的标准图像文件格式。
- 默认文件扩展名:BMP 或者 bmp(有时也会以.DIB 或.RLE 作扩展名)。
- 它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此,BMP 文件占用空间很大,但是没有失真。
- BMP 文件的图像深度可选 lbit、4bit、8bit、16bit、24bit 及 32bit。
- BMP 文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。
- 典型的 BMP 图像文件由四部分组成:
文件头 信息头 调色板 位图数据 文件的格式、大小等信息 图像数据的尺寸、位平面数、压缩方式、颜色索引等信息
可选,如使用索引来表示图像,调色板就是索引与其对应的颜色的映射表; 根据 BMP 位图使用的位数不同而不同。图像的像素值可以是RGB值,也可以是调色板的索引值。如果使用了调色板,那像素值就是调色板的索引值,根据索引值找到调色板中相应的颜色。
BMP文件结构
一个BMP文件可以用如下代码表示:
typedef struct tagBITMAP_FILE{
BITMAPFILEHEADER bitmapheader;//文件头
BITMAPINFOHEADER bitmapinfoheader;//信息头
PALETTEENTRY palette[256];//调色板(可选)
UCHAR *buffer; //UCHAR 大小1字节(同C语言的unchar),指向图像数据信息
} BITMAP_FILE;
文件头
该结构体的长度固定为 14 个字节
typedef struct tagBITMAPFILEHEADER { // bmfh
WORD bfType; //占2字节,WORD 为无符号 16 位整数
DWORD bfSize; //占4字节,DWORD 为无符号 32 位整数
WORD bfReserved1; //占2字节
WORD bfReserved2; //占2字节
DWORD bfOffBits; //占4字节
} BITMAPFILEHEADER;
- bfType :指定文件类型,必须是 0x424D,即字符串"BM",也就是说所有.bmp 文件的头两个字节都是"BM"。
- bfSize :指定文件大小,包括这 14 个字节。
- bfReserved1,bfReserved2 为保留字,不用考虑
- bfOffBits :为从文件头到实际的位图数据的字节偏移量,即文件头、信息头、调色板(如果需要)的长度之和。用这个偏移值,可以迅速的从文件中读取到位图数据。
以 1 张 1 bit 灰度图像为例:

1:0x424D -> bfType: BM
2:0x0004a8e2 -> bfSize: 305378B(278KB)
打开文件属性,可以看到文件大小计算无误

3:0x00000000 -> bfReserved1 和 bfReserved2:共4字节的保留字节,全设为0
4:0x0000003e -> bfOffBits: 偏移量大小为 62 字节(14 字节的文件头 + 40 字节的信息头 + 8 字节的调色板)
信息头
该结构体的长度固定为 40 个字节。
typedef struct tagBITMAPINFOHEADER{ // bmih
DWORD biSize; //DWORD 无符号 32 位整数
LONG biWidth; //4字节,LONG 为 32 位整数
LONG biHeight;
WORD biPlanes; //WORD 为无符号 16 位整数
WORD biBitCount
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER;
- biSize :该结构体的长度,为 40。这个值并不一定是 BITMAPINFOHEADER 的大小,它也可能是 sizeof(BITMAPV4HEADER) 或 sizeof(BITMAPV5HEADER) 的值。这要根据位图文件的格式版本来决定,不过,目前绝大多数的 BMP 图像都是 BITMAPINFOHEADER 结构(可能是后两者太新的缘故吧)。
- biWidth :图象宽度,单位像素。
- biHeight :图象高度,单位像素。这个值除了用于描述图像的高度之外,还指明该图像是倒向还是正向的位图。正数说明图像倒向,负数说明图像正向。大多数的 BMP 文件都是倒向的。当高度值为负数时,图像将不能被压缩(也就是说 biCompression 成员将不能是 BI_RLE8 或 BI_RLE4)。
- biPlanes :为目标设备说明位面数,显然显示器只有 1 个平面,所以必须是 1,不用考虑。
- biBitCount :表示颜色时要用到的位数,常用的值为 1(黑白二色图),4(16 色图),8(256 色),24(真彩色图)(新的.bmp 格式支持 32 位色,这里不做讨论)。
- biCompression :位图压缩类型,后面以第一种不压缩,即 BI_RGB 为前提探讨。
BI_RGB 没有压缩 BI_RLE8 每个像素 8 比特的 RLE 压缩编码,压缩格式由 2 字节组成(重复像素计数和颜色索引) BI_RLE4 每个像素 4 比特的 RLE 压缩编码,压缩格式由 2 字节组成 BI_BITFIELDS 每个像素的比特由指定的掩码决定 - biSizeImage :位图数据占用的字节数,可以用以下公式计算:biSizeImage = biWidth' * biHeight,注意这里 biWidth' 必须是 4 的整倍数(所以不是 biWidth,而是 biWidth',表示>= biWidth 的,离 4 最近的整倍数。当用 BI_RGB 不压缩格式时,可设置为 0。
- biXPelsPerMeter :目标设备的水平分辨率,单位是像素/米。
- biYPelsPerMeter :目标设备的垂直分辨率,单位是像素/米。
- biClrUsed :位图实际用到的颜色数,为 0 则使用所有调色板项,颜色数为 2^biBitCount 。
- biClrImportant :本图象中重要的颜色数,如果该值为零,则认为所有的颜色都是重要的。
还是以 1 张 1 bit 灰度图像为例:

1:0x00000028 -> biSize = 40字节
2:0x0000040f -> biWidth = 1039像素
3:0x00000909 -> biHeight = 2313像素
4:0x0001 -> biPlanes = 1
5:0x000a -> biBitCount = 1
通过图片检测计算无误

6:0x00000000 -> biCompression = 0(没有压缩)
7:0x0004a8a4 -> biSizeImage = 305316字节
8:0x00000000 -> biXPelsPerMeter = 0
9:0x00000000 -> biYPelsPerMeter = 0
10:0x00000000 -> biCirUsed = 0
11:0x00000000 -> biClrImportant = 0
验证图像大小 biSizeImage 的大小:
- 每行字节数 LineBytes = (width * bitCount + 31) / 32 * 4 = (1039 * 1 + 31)/ 32 * 4 = 132B,
- 图像大小为132 * 2313 = 305316B,大小正确。
biSize、bfSize、biSizeImage 的区别
- biSize:信息头大小,通常占 40 字节。
- bfSize:整个图像文件大小,包括文件头 + 信息头 + 调色板(可选)+ 图像大小。
- biSizeImage:图像数据大小。我们之前算的 biSizeImage = 305316,bfSize = 305378 = 文件头(14)+ 信息头(40)+ 调色板(8)+ biSizeImage(305316)。
调色板

这里是对那些需要调色板的位图文件而言的。有些位图(如真彩色图)是不需要调色板的,BITMAPINFOHEADER 后直接是位图数据。
调色板实际上是一个数组,共有 biClrUsed 个元素(如果该值为 0,则有 2^biBitCount 个元素)。数组中每个元素的类型是一个 RGBQUAD 结构,占 4 个字节,其定义如下:
typedef struct tagRGBQUAD {
BYTE rgbBlue;//该颜色的蓝色分量
BYTE rgbGreen;//该颜色的绿色分量
BYTE rgbRed;//该颜色的红色分量
BYTE rgbReserved;//保留值
} RGBQUAD;
具体定义方式如下:
//定义一个 1 bit 图像的调色板,1 bit 图像只有 2 种颜色
RGBQUAD colors1[2];//定义调色板
//实现调色板
//每个分量占8位,所以1个颜色占4字节
colors1[0].rgbBlue = 0; // 黑色
colors1[0].rgbGreen = 0;
colors1[0].rgbRed = 0;
colors1[0].rgbReserved = 0;
colors1[1].rgbBlue = 255; // 白色
colors1[1].rgbGreen = 255;
colors1[1].rgbRed = 255;
colors1[1].rgbReserved = 0;
上面测试的图像为 1 bit 图像,其中文件头占 14 字节,信息头占 40 字节,而文件头到实际数据之间的偏移量却为 62 字节,是因为文件头和信息头后面还有 8 字节(2 种颜色,1 个颜色 4 字节)的调色板。
位图数据
位图数据大小取决于压缩方法、图像尺寸、图像位深度,对于用到调色板的位图,位图数据就是该像素在调色板中的索引值,对于真彩色图,位图数据就是实际的 RGB 值。
- 每一行的字节数必须是 4 的整倍数,如果不是,则需要补齐。这在前面介绍 biSizeImage 时已经提到了。
- 一般来说,.BMP 文件的数据从下到上,从左到右的(倒向位图)。也就是说,从文件中最先读到的是图象最下面一行的左边第一个像素,然后是左边第二个像素…接下来是倒数第二行左边第一个像素,左边第二个像素…依次类推,最后得到的是最上面一行的最右一个像素(如果是正向,则由上到下,即最先读到的是左上角第一个像素)。
图象数据压缩
| BI_RGB | 没有压缩 |
| BI_RLE8 | 每个像素 8 比特的 RLE 压缩编码,压缩格式由 2 字节组成(重复像素计数和颜色索引) |
| BI_RLE4 | 每个像素 4 比特的 RLE 压缩编码,压缩格式由 2 字节组成 |
| BI_BITFIELDS | 每个像素的比特由指定的掩码决定 |
BI_RLE8
每个像素为 8 比特的 RLE 压缩编码,可使用编码方式和绝对方式中的任何一种进行压缩,这两种方式可在同一幅图中的任何地方使用。
编码方式:
- 2 个字节,第一个字节指定使用相同颜色的像素数目,第二个字节指定使用的颜色索引。
- 第一个字节可设置为 00 时,第二个字节为 00 表示行的结束;01 表示图象结束;02 表示其后的两个字节表示下一个像素从当前开始的水平和垂直位置的偏移量。
绝对方式:
- 第一个字节设置为 00,第二个字节设置为 0x03~0xFF 之间的一个值。
- 第二个字节表示跟在这个字节后面的字节数,每个字节包含单个象素的颜色索引。
16 进制表示的 8-位压缩图象数据:
03 04 05 06 00 03 45 56 67 00 02 78 00 02 05 01 02 78 00 00 09 1E 00 01
这些压缩数据可解释为 :
03 04 04 04 04 //第一个字节 03,则和第二个字节表示的颜色索引 04 相同的像素有 3 个。
05 06 06 06 06 06 06//同理,相同的像素有 5 个。
00 03 45 56 67 00 45 56 67//第一个字节 00,但第二个字节 03,所有这里意思是没有相同像素。
02 78 78 78
00 02 05 01 //从当前位置右移 5 个位置后向下移一行
02 78 78 78
00 00 //行结束
09 1E 1E 1E 1E 1E 1E 1E 1E 1E 1E
00 01 //RLE 编码图象结束
BI_RLE4
每个像素为 4 比特的 RLE 压缩编码,同样也可使用编码方式和绝对方式中的任何一种进行压缩,这两种方式也可在同一幅图中的任何地方使用。
- 编码方式:2 个字节,第一个字节指定像素数目,第二个字节包含两种颜色索引,一个在高 4 位,另一个在低 4 位。第 1 个像素使用高 4 位的颜色索引,第 2 个使用低 4 位的颜色索引,第 3 个使用高 4 位,依此类推。
- 绝对方式:第一个字节设置为 00,第二个字节包含有颜色索引数,其后续字节包含有颜色索引,颜色索引存放在该字节的高、低 4 位中,一个颜色索引对应一个像素。
此外,BI_RLE4 也同样联合使用第二个字节中的值表示:
- 第二个字节的值为 0:行的结束。
- 第二个字节的值为 1:图象结束。
- 第二个字节的值为 2:其后两个字节表示下一个像素从当前开始的水平和垂直位置的偏移量。
16 进制数表示的 4-位压缩图象数据:
03 04 05 06 00 06 45 56 67 00 04 78 00 02 05 01 04 78 00 00 09 1E 00 01
这些压缩数据可解释为 :
03 04 0 4 0 //像素数目 3,颜色索引 04,第 1 个使用高四位 0,第 2 个第四位 4……
05 06 0 6 0 6 0
00 06 45 56 67 00 4 5 5 6 6 7//第 2 个字节 06,即 6 个颜色索引,对应 6 个像素 4 5 5 6 6 7。
04 78 7 8 7 8
00 02 05 01 //从当前位置右移 5 个位置后向下移一行
04 78 7 8 7 8
00 00 //行结束
09 1E 1 E 1 E 1 E 1 E 1
00 01 //RLE 图象结束
JPEG
JPEG 是 Joint Photographic Experts Group(联合图像专家组)的缩写,文件后辍名为“.jpg” 或“.jpeg”,是最常用的图像文件格式,由一个软件开发联合会组织制定。
同 BMP 格式不同,JPEG 是一种有损压缩格式,能够将图像压缩在很小的储存空间,图像中重复或不重要的资料会被丢失,因此容易造成图像数据的损伤(BMP 不会,但是 BMP 占用空间大)。尤其是使用过高的压缩比例,将使最终解压缩后恢复的图像质量明显降低,如果追求高品质图像,不宜采用过高压缩比例。
但是 JPEG 压缩技术十分先进,它用有损压缩方式去除冗余的图像数据,在获得极高的压缩率的同时能展现十分丰富生动的图像,换句话说,就是可以用最少的磁盘空间得到较好的图像品质。
JPEG 具有调节图像质量的功能,允许用不同的压缩比例对文件进行压缩,支持多种压缩级别,压缩比率通常在 10:1 到 40:1 之间,压缩比越大,品质就越低;相反地,压缩比越小,品质就越好。比如可以把 1.37Mb 的 BMP 位图文件压缩至 20.3KB。
JPEG 格式压缩的主要是高频信息,对色彩的信息保留较好,适合应用于互联网,可减少图像的传输时间,可以支持 24bit 真彩色,也普遍应用于需要连续色调的图像。
JPEG/JPG 的解码过程可以简单的概述为如下几个部分:
1、从文件头读出文件的相关信息。
JPEG 文件数据分为文件头和图像数据两大部分,其中文件头记录了图像的版本、长宽、
采样因子、量化表、哈夫曼表等重要信息。所以解码前必须将文件头信息读出,以备
图像数据解码过程之用。
2、从图像数据流读取一个最小编码单元(MCU) ,并提取出里边的各个颜色分量单元。
3、将颜色分量单元从数据流恢复成矩阵数据。
使用文件头给出的哈夫曼表,对分割出来的颜色分量单元进行解码,把其恢复成 8×8
的数据矩阵。
4、8×8 的数据矩阵进一步解码。
此部分解码工作以 8×8 的数据矩阵为单位, 其中包括相邻矩阵的直流系数差分解码、
使用文件头给出的量化表反量化数据、反 Zig- zag 编码、隔行正负纠正、反向离散余弦变
换等 5 个步骤, 最终输出仍然是一个 8×8 的数据矩阵。
5、颜色系统 YCrCb 向 RGB 转换。
将一个 MCU 的各个颜色分量单元解码结果整合起来,将图像颜色系统从 YCrCb 向
RGB 转换。
6、排列整合各个 MCU 的解码数据。
不断读取数据流中的 MCU 并对其解码,直至读完所有 MCU 为止,将各 MCU 解码
后的数据正确排列成完整的图像。
JPEG 的解码本身是比较复杂的,这里 FATFS 的作者,提供了一个轻量级的 JPG/JPEG 解
码库:TjpgDec,最少仅需 3KB 的 RAM 和 3.5KB 的 FLASH 即可实现 JPG/JPEG 解码,本例程
采用 TjpgDec 作为 JPG/JPEG 的解码库,关于 TjpgDec 的详细使用,请参考光盘:6,软件资料
\图片编解码\TjpgDec 技术手册 这个文档。
BMP 和 JPEG 这两种图片格式均不支持动态效果,而 GIF 则是可以支持动态效果。最后,
GIF
GIF(Graphics Interchange Format)是 CompuServe 公司开发的图像文件存储格式。
GIF 图像文件以数据块(block)为单位来存储图像的相关信息。
一个 GIF 文件由表示图形/图像的数据块、数据子块以及显示图形/图像的控制信息块组成,称为 GIF 数据流(Data Stream)。
数据流中的所有控制信息块和数据块都必须在文件头(Header)和文件结束块(Trailer)之间。
GIF 文件格式采用了 LZW(Lempel-Ziv Walch)压缩算法来存储图像数据,定义了允许用户为图像设置背景的透明(transparency)属性。
GIF 文件格式可在一个文件中存放多幅彩色图形/图像。如果存放有多幅图,它们可以像幻灯片或者动画那样演示。
一个 GIF 文件的结构可分为文件头(File Header)、GIF 数据流(GIF Data Stream)和文件结束块(Trailer)三个部分。
文件头包含 GIF 文件署名(Signature)和版本号(Version);GIF 数据流由控制标识符、图象块(Image Block)和其他的一些扩展块组成;文件终结器只有一个值为 0x3B 的字符(';')表示文件结束。
| 文件头(File Header) | GIF 数据流 | 文件结束块(Trailer) | |||
| GIF 文件署名 | 版本号 | 控制标识符 | 图象块 | 其他的一些扩展块 | 值为 0x3B 的字符(';')表示文件结束 |
关于 GIF 的详细介绍,请参考光盘 GIF 解码相关资料。图片格式简介,我们就介绍到这里。
48.2 硬件设计
检测 SD 卡是否存在,如果 SD 卡存在,则开始查找 SD 卡根目录下的 PICTURE 文件夹,如果找到则显示该文件夹下面的图片文件(支持 bmp、jpg、jpeg 或 gif 格式),循环显示。如果未找到 PICTURE 文件夹/任何图片文件,则提示错误。
5) SD 卡
6) SPI FLASH
在 SD 卡根目录下要建一个 PICTURE 的文件夹,用来存放 JPEG、JPG、BMP 或 GIF 等图片。 新建 PICTURE 文件夹。 在该文件夹里面新建 bmp.c、bmp.h、tjpgd.c、tjpgd.h、integer.h、gif.c、gif.h、piclib.c 和 piclib.h 等 9 个文件。
打开实验工程,新建 PICTURE 分组,添加相关源文件到工程,同时将 PICTURE 文件夹加入头文件包含路径。
bmp.c 和 bmp.h 用于实现对 bmp 文件的解码;tjpgd.c 和 tjpgd.h 用于实现对 jpeg/jpg 文件的解码;gif.c 和 gif.h 用于实现对 gif 文件的解码;
piclib.c,代码如下:
piclib_draw_hline 和 piclib_fill_color 函数因为 LCD 驱动代码没有提供,所以在这里单独实现,如果 LCD 驱动代码有提供,则直接用 LCD 提供的即可。
piclib_init 函数,该函数用于初始化图片解码的相关信息,其中_pic_phy 是我们在 piclib.h 里面定义的一个结构体,用于管理底层 LCD 接口函数,这些函数必须由用户在外部实现。
_pic_info 则是另外一个结构体,用于图片缩放处理。
piclib_alpha_blend 函数,该函数用于实现半透明效果,在小格式(图片分辨率小于 LCD 分辨率)bmp 解码的时候,可能被用到。
ai_draw_init 函数,该函数用于实现图片在显示区域的居中显示初始化,其实就是根据图片大小选择缩放比例和坐标偏移值。
is_element_ok 函数,该函数用于判断一个点是不是应该显示出来,在图片缩放的时候该函数是必须用到的。
ai_load_picfile 函数,该函数是整个图片显示的对外接口,外部程序,通过调用该函数,可以实现 bmp、jpg/jpeg 和 gif 的显示,该函数根据输入文件的后缀名,判断文件格式,然后交给相应的解码程序(bmp 解码/jpeg 解码/gif 解码),执行解码,完成图片显示。注意,这里我们用到一个 f_typetell 的函数,来判断文件的后缀名,f_typetell 函数在 exfuns.c 里面实现,具体请参考光盘本例程源码。
最后,pic_memalloc 和 pic_memfree 分别用于图片解码时需要用到的内存申请和释放,通过调用 mymalloc 和 myfreee 来实现。
接下来我们看看头文件 piclib.h 关键代码如下:
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)