Bmp学习

Bmp笔记

 

下面总结一下我使用stm32读取和解析bmp图片的经验,首先我使用的是stm32f407zet56,Keil MDK的版本是5.10,同时使用了文件系统FatFs R0.10c。

 

 

 

要读取解析bmp,首先的了解它的存储方式。

Bmp的存储结构划分为4部分。

 

 

第一部分:位图文件头,这部分存储的是文件的信息。

我们使用下面的结构体表示:


可以看到,它总共占据14字节。

bfType表示的是文件类型,它必须是”BM”。

bfSize表示位图文件的大小,它的单位是字节。

bfReserved1和bfReserved2是保留字,这两个值必须是0。

bfOffBits表示文件数据相对于文件起始的偏移,其单位为字节;一般来说这个数据指的就是像素数据或者说像素矩阵。

 

 

第二部分:位图信息头,这部分存储的是位图的信息。


biSize表示的是信息头的大小,也就是这个结构体的大小,单位字节。

biWidth和biHeight分别是图像的宽度和高度,单位都是像素。

biPlanes是目标设备级别,一般情况下都应当为1。

biBitCount是像素深度,也就是每个像素点的色彩位数,说白了就是多少位二进制表示一个像素点。典型的有1,2,4,8,16,24,32。

biCompression压缩类型,0表示不压缩,1表示RLE8压缩,2表示RLE4压缩,3表示每个像素值由指定的掩码决定。我这里只讨论0。

biSizeImage表示位图的大小,这并不是文件的大小,实际上他应当是像素矩阵的大小,单位是字节。

biXPelsPerMeter和biYPelsPerMeter,分别表示水平和垂直的分辨率,它是以每米多少像素来表示的,所以其单位是像素每米。

biClrUsed表示使用的颜色的数量,如果为0表示所有颜色均使用。

biClrImportant表示重要的颜色数量,为0表示都重要。这个数字主要在显卡不能够显示所有颜色时起作用,它将辅助驱动程序显示颜色。

 

 

第三部分:调色板/彩色表,它实际上决定了像素矩阵中数据表示颜色的方式,如果像素深度为24,即真彩图,那么它就没有调色板这一部分,因为它的每一个字节就代表一个颜色值。


调色板中的有四个数据,从这个结构体中就可以看出。与前两个部分不同,前两个部分结构体中定义的数据的字节数与实际的存储是一样,但是这里不一样。这里的四个值在实际的图片存储中都是4字节的。

这4个值怎么用,举个例子,例如彩色板如下(注意数据是低位在前):

rgbBlue

rgbGreen

rgbRed

rgbReserved

00FB 0000

E007 0000

1F00 0000

0000 0000

转为二进制:

F800    -   1111 1000 0000 0000

07E0    -   0000 0111 1110 0000

001F    -   0000 0000 0001 1111

可以看到,实际上这表示的是rgb565,也就是说我们将得到的颜色值与这三个值相与后右移相应位(11,6,0)就可以得到相应的颜色值,但是这个值并不是标准的RGB值,因为位数不够,所以将相与后右移得到的三个值分别在左移2,3,2位(也即分别乘以4,8,4)后就可以得到标准的RGB值。

 

 

第四部分:像素矩阵,这里存储了图片每个像素点的信息。

我们如果要显示图像,那么这里的数据就是我们要显示的部分。而前面的信息则告诉我们如何处理这里的数据。

这里的数据存储也有一定的规则。这里说一下真彩也就是像素深度为24的存储方式:

首先,像素矩阵,虽然我们叫的是矩阵,但是实际的存储肯定是线性的,矩阵的起始像素实际上是图片的左下角,而终止像素是图片的右上角,而且是逐行存储的;也就是说矩阵中的数据是从图片像素的倒数第一行开始存储的。

再次24位表示一个颜色值,24位可以拆分为3个字节(蓝绿红),这三个字节分别表示蓝、绿、红;但是有一个规定,像素每行的开始数据相对于矩阵的开始的字节数偏移必须是一个4的倍数值。这是什么意思呢,举个例子,假如图片的宽度为1个像素,也就是3字节,那么倒数第一行的各个字节的偏移就是0,1,2,这样数到倒数第二行的第一个字节就变成了3,很显然3不是4的倍数,所以不符合规则,于是在每一行的3个字节的末尾再补一个字节,这样就符合规则了。但是补的这个字节并不一定为0。同理,如果图像宽度为2像素,也就是6个字节,那么就要补2个字节;图像宽度为3像素,也就是9字节,那么就要补3字节;如果图像宽度像数对应的字节总数刚好是4的倍数,自然就不用补了。

 

 

 

这四个部分在图像文件中的存储是连续的,需要注意一点的是,所有的数据存储都是以字节为单位,低位在前,高位在后进行存储的(不懂没关系,看下面的例子)。下面我们以一个24位的真彩图的头数据为例来实际说明一下:


其中红线部分是文件头,绿线部分是图像信息头,没有彩色板,蓝色部分开始就是像素矩阵(截图只是截取了一部分,像素矩阵的内容还有很多),截图中表示的都是16进制数据。

bfType,文件类型,2字节,4D42,也就是字母M和B的ASCII码值

bfSize,文件大小,4字节,00038436

……

bfOffBits,像素矩阵偏移,4字节,00000036

……

biWidth,图像宽度,4字节,000000F0

biHeight,图像高度,4字节,00000140

……

这里就不一一列举了。

 

 

 

 

现在知道了图像数据的存储,那么我们就可以编写程序来读取图片了。这里有一个细节问题需要处理,一开始我也是卡了很久读到的数据感觉都不对,那就是结构体数据的对齐问题。我使用的Keil MDK默认的是4字节对齐,而我的前两个结构体(Bmp_FileHeaderType和Bmp_InfoHeaderType)中既有2字节的也有4字节的,这就导致每次打出的数据都是错位的,但是查看内存区又发现没有问题,所以认定是字节对齐问题。

我的做法是在定义这两个结构体的时候使用#pragma pack (2)和#pragma pack ()包起来,这样一来Keil MDK就会将这两个结构体按2字节对齐了。

为了使用方便,我还进行了以下声明和定义:


其中LCD_DrawPoint是我的一个显示屏描点函数,其原型如下:


他需要两个参数:点的坐标和颜色值。

FIL是FatFs提供的文件对象类型。

下面看函数,注意,为简单起见,这里写的程序只适合读取24位真彩图,要适应其他类型还需要修改。

先看打开图片文件并读取文件和图片信息的函数:


其中FRESULT是FatFs提供的一个类型。这个函数中打开了文件,并且读取了文件的前三部分信息(前面已经讲过总共四部分)。

下面再看一下我将图片打到显示屏的一个函数:


这里可以看到我注释,调整坐标是因为我规定,传入函数的坐标是图片左上角要放的位置,因为图片的像素矩阵是从图片的左下角开始存储的,因此,我要将坐标转换到坐下叫得位置开始显示。buff为什么要补到4字节倍数的大小,可以看我在上面关于图像存储的第四部分的详细分析。重置文件指针是将文件指针移动到像素矩阵的开始位置。

其中的xGUI_SYS_ToColorType是我的一个宏,它的作用是将给定的标准RGB值转换为rgb565,因为我的显示屏要求这样的数据格式。

LCD_ToPointType也是一个宏,我使用它将x,y坐标值强制转换为一个LCD_PointType类型的结构体。

该函数的缺点就是,读取一部分显示一部分,速率不够,但是这样节省内存。

 

 

就是使用这些函数,我成功的将图片读取并显示到了显示屏上。水平有限,错误难免。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值