7.5 在Framebuffer上显示图片
本节源码位于如下目录:
7.5.1
BMP文件格式解析
BMP是一种常见的图像格式,BMP文件可看成由4个部分组成:位图文件头(bitmap-file header)、位图信息头(bitmap-information header)、调色板(colorpalette)和定义位图的字节阵列。以最简单的24位真彩色BMP文件作例子讲解:
1
位图文件头(bitmap-file header)
这部分可以理解为是一个结构体,里面的每一个成员都表示一个属性位数文件头由以下信息组成:
名称 | 字节数 | 含义 |
bfType | 2字节 | 表明它是BMP格式的文件,内容固定为0x42,0x4D,即ASCII字符中的“B”“M” |
bfSize | 4字节 | BMP文件的大小,单位为字节 |
bfReserved1 | 2字节 | 保留 |
bfReserved2 | 2字节 | 保留 |
bfOffBits | 4字节 | 位图文件头+位图信息头+调色板 |
我们用UltraEdit打开一个BMP文件,可以看到如下信息:
这是该BMP文件前32字节的数据,可以看到,前两个字节分别为0x42,0x4D。
接着后面4个字节依次是0x36,0xF9,0x15,0x00。
在BMP格式中,文件的存储方式是小端模式,即如果一个数据需要用几个字节来表示的话,那么,低位数据存在低位地址上,高位数据存在高位地址上。类似的,还有大端模式,即:如果一个数据需要用几个字节来表示的话,那么,低位数据存在高位地址上,高位数据存在低位地址上。
所以0x36,0xF9,0x15,0x00四个数据拼接方法应该是:0x0015F936(在数字中个位即最右边才是最低位),它正好就是这个文件的大小:
紧接着是4个保留位字节,其数据必须为0x00。
最后是4个字节的便宜位,可以看到位图文件头+位图信息头+调色板的大小应该是0x36。
2
位图信息头(bitmap-information header)
位图信息头也可以理解为是一个结构体,其成员有:
名称 | 字节数 | 含义 |
biSize | 4 | 整个位图信息头结构体的大小 |
biWidth | 4 | 图像宽度,单位为像素 |
biHeight | 4 | 图像高度,单位为像素。此外,这个数的正负可以判断图像是正向还是倒向的,若为正,则表示是正向;若为负,则表示反向。其实根本不同就是坐标系的建立方法不一样。后面写代码时会讲。 |
biPlanes | 2 | 颜色平面书,其值总为1 |
biBitCount | 2 | 即1个像素用多少位的数据来表示,其值可能为1,4,8,16,24,32。我们是以24位真彩色为例子讲解的 |
biCompression | 4 | 数据的压缩类型 |
biSizeImage | 4 | 图像数据的大小,单位为字节 |
biXPelsPerMeter | 4 | 水平分辨率,单位是像素/米 |
biYPelsPerMeter | 4 | 垂直分辨率,单位是像素/米 |
biClrUsed | 4 | 调色板中的颜色 |
biClrImportant | 4 | 说明有对图像有重要影响的颜色索引的数目,若为0,表示都重要 |
对照源文件数据:
0E-11:00000028h=40,表示这个结构体大小是40字节。
12-15:00000320h=800,图像宽为800像素。
16-19:00000258h=600,图像高为600像素,与文件属性一致。这是一个正数,说明图像是正向的,数据是以图像左下角为原点,以水平向右为X轴正方向,以垂直向上为Y轴正方向排列的。若为负,则说明图像是反向的,数据是以图像左上角角为原点,以水平向右为X轴正方向,以垂直向下为Y轴正方向排列的。
1A-1B:0001h,该值总为1。
1C-1D:0018h=24,表示每个像素占24个比特,即24位真彩色
上面这几个信息跟文件属性是一致的:
1E-21:00000000h,BI_RGB,说明本图像不压缩。
22-25:00000000h,图像的大小,因为使用BI_RGB,所以设置为0。
26-29:00000000h,水平分辨率,缺省。
2A-2D:00000000h,垂直分辨率,缺省。
2E-31:00000000h,对于24位真彩色来说,是没有调色板的,所以为0。
32-35:00000000h,对于24位真彩色来说,是没有调色板的,所以为0。
3
调色板(color palette)
24位真彩色没有调色板,这里为了简化不赘述。
4
定义位图的字节阵列
这一部分就是真正的图像数据了,24位真彩色数据是按按BGR各一字节循环排列而成。
代码实现:
15
16
17 typedef struct tagBITMAPFILEHEADER { /* bmfh */
18 unsigned short bfType;
19 unsigned int bfSize;
20 unsigned short bfReserved1;
21 unsigned short bfReserved2;
22 unsigned int bfOffBits;
23 } BITMAPFILEHEADER;
24
25 typedef struct tagBITMAPINFOHEADER { /* bmih */
26 unsigned int biSize;
27 unsigned int biWidth;
28 unsigned int biHeight;
29 unsigned short biPlanes;
30 unsigned short biBitCount;
31 unsigned int biCompression;
32 unsigned int biSizeImage;
33 unsigned int biXPelsPerMeter;
34 unsigned int biYPelsPerMeter;
35 unsigned int biClrUsed;
36 unsigned int biClrImportant;
37 } BITMAPINFOHEADER;
38
39
40
41
42 static int DecodeBmp2Rgb(const char *strFileName, PT_PictureData ptData) ;
43 static void ReleaseBmp2Rgb(PT_PictureData ptPicData);
44 static T_Picture2RGB g_tBmp2RgbOpr = {
45 .name = "bmp2rgb",
46 .PictureParsing = DecodeBmp2Rgb,
47 .PictureRelease = ReleaseBmp2Rgb,
48 };
49
50
51 /**********************************************************************
52 * 函数名称:IsBmp
53 * 功能描述:判断该文件是否为 BMP 文件
54 * 输入参数:ptFileMap - 内含文件信息
55 * 输出参数:无
56 * 返 回 值:0 - 是 BMP 格式, -1 -不是 BMP 格式
57 ***********************************************************************/
58 int IsBmp(FILE **ppFp, const char *strFileName)
59 {
60 char strCheckHeader[2];
61 *ppFp= fopen(strFileName, "rb+");
62 if (*ppFp== NULL) {
63 return -1;
64 }
65 if (fread(strCheckHeader, 1, 2, *ppFp) != 2)
66 return -1;
67
68 if (strCheckHeader[0] != 0x42 || strCheckHeader[1] != 0x4d)
69 return -1;
70 else
71 return 0;
72 }
73
74
75
76 /**********************************************************************
77 * 函数名称:MapFile
78 * 功能描述:使用 mmap 函数映射一个文件到内存,以后就可以直接通过内存来访问文件
79 * 输入参数:PT_PictureData ptData 内含图像数据
80 * 输出参数:ptData->iFileSize : 文件大小
81 * ptData->pucFileData : 映射内存的首地址
82 * 返 回 值:0 - 成功其他值 - 失败
83 ***********************************************************************/
84 int MapFile(PT_PictureData ptData)
85 {
86 int iFd;
87 struct stat tStat;
88
89 /* 打开文件 */
90 iFd = fileno(ptData->ptFp);
91 fstat(iFd, &tStat);
92 ptData->iFileSize= tStat.st_size;
93 ptData->pucFileData= (unsigned char *)mmap(NULL , tStat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, iFd, 0);
94 if (ptData->pucFileData == (unsigned char *)-1)
95 {
96 printf("mmap error!\n");
97 return -1;
98 }
99 return 0;
100 }
101
102 /**********************************************************************
103 * 函数名称:DecodeBmp2Rgb
104 * 功能描述:把 BMP 文件转化为 rgb 格式
105 * 输入参数:strFileName - 文件名
106 * ptData - 内含图像信息
107 * 返 回 值:0 - 成功其他值 - 失败
108 * -1 - 文件不是 BMP 格式
109 * -2 - 不支持的 bpp
110 * -3 - 图像缓存区分配失败
111 ***********************************************************************/
112 static int DecodeBmp2Rgb(const char *strFileName, PT_PictureData ptData) {
113 int x,y;
114 int iPos = 0;
115 int iLineWidthAlign;
116 BITMAPFILEHEADER *ptBITMAPFILEHEADER;
117 BITMAPINFOHEADER *ptBITMAPINFOHEADER;
118 unsigned char *aFileHead;
119 unsigned char *pucSrc;
120 unsigned char *pucDest;
121 int iLineBytes;
122
123 /* 判断该文件是否为 BMP 格式 */
124 if (IsBmp(&ptData->ptFp, strFileName))
125 return -1;
126
127 /* 将 BMP 文件映射到内存空间 */
128 MapFile(ptData);
129
130
131 aFileHead = ptData->pucFileData;
132
133 ptBITMAPFILEHEADER = (BITMAPFILEHEADER *)aFileHead;
134 ptBITMAPINFOHEADER = (BITMAPINFOHEADER *)(aFileHead + sizeof(BITMAPFILEHEADER));
135 /* 获取必要的图像信息 */
136 ptData->iWidth = ptBITMAPINFOHEADER->biWidth;
137 ptData->iHeight = ptBITMAPINFOHEADER->biHeight;
138 ptData->iBpp = ptBITMAPINFOHEADER->biBitCount;
139 iLineBytes = ptData->iWidth*ptData->iBpp/8;//一行数据的字节数
140 ptData->iBmpDataSize= ptData->iHeight * iLineBytes;//整个 BMP 图像的字节数
141 /*暂时只支持 24bpp 格式*/
142 if (ptData->iBpp != 24)
143 {
144 printf("iBMPBpp = %d\n", ptData->iBpp);
145 printf("sizeof(BITMAPFILEHEADER) = %d\n", sizeof(BITMAPFILEHEADER));
146 return -2;
147 }
148
149 /* 分配空间 */
150 ptData->pucBmpData = malloc(ptData->iBmpDataSize);
151 ptData->pucRgbData = malloc(ptData->iBmpDataSize);
152
153 if (NULL == ptData->pucBmpData||NULL == ptData->pucRgbData)
154 return -2;
155
156 /* 从 bmp 文件中读取图像信息,24bpp 的 BMP 图像为 BGR 格式 */
157 pucDest = ptData->pucBmpData;
158 iLineWidthAlign = (iLineBytes + 3) & ~0x3; /* 向 4 取整 */
159 pucSrc = aFileHead + ptBITMAPFILEHEADER->bfOffBits;
160
161 pucSrc = pucSrc + (ptData->iHeight - 1) * iLineWidthAlign;
162
163 /* 对于 bmp 文件中的源数据,是以左下角为原点计算坐标的,因此拷贝数据时需要转换坐标 */
164 for (y = 0; y < ptData->iHeight; y++)
165 {
166 memcpy(pucDest, pucSrc, ptData->iWidth*3);
167 pucSrc -= iLineWidthAlign;
168 pucDest += iLineBytes;
169 }
170
171
172 /* 将得到的 BGR 数据转化为 RGB 数据 */
173 for (y = 0; y < ptData->iHeight; y++){
174 for(x = 0;x
iWidth*3;x+=3){ 175 ptData->pucRgbData[iPos++] = ptData->pucBmpData[y*ptData->iWidth*3+x+2];
176 ptData->pucRgbData[iPos++] = ptData->pucBmpData[y*ptData->iWidth*3+x+1];
177 ptData->pucRgbData[iPos++] = ptData->pucBmpData[y*ptData->iWidth*3+x+0];
178 }
179 }
180
181 return 0;
182
183 }
184
185
186
187 /**********************************************************************
188 * 函数名称:ReleaseBmp2Rgb
189 * 功能描述:释放解析及使用 BMP 时的空间
190 * 输入参数:ptData - 内含图像信息
191 * 返 回 值:无
192 ***********************************************************************/
193 static void ReleaseBmp2Rgb(PT_PictureData ptData){
194 free(ptData->pucRotateData);
195 free(ptData->pucZoomData);
196 free(ptData->pucRgbData);
197 free(ptData->pucBmpData);
198 if(munmap(ptData->pucFileData,ptData->iFileSize)!=0)
199 printf("munmap fault\n");
200 }
201
202 /**********************************************************************
203 * 函数名称:GetBMPPaser
204 * 功能描述:返回"BMP 解析器"
205 * 输入参数:无
206 * 输出参数:无
207 * 返 回 值:BMP 解析器的指针
208 ***********************************************************************/
209 PT_Picture2RGB GetBMPPaser(void)
210 {
211 return &g_tBmp2RgbOpr;
212 }
7.5.2
在LCD上显示BMP图像
让BMP文件在开发板的LCD上显示出来,有几个需要注意的点:
1.开发板LCD上的显示格式是RGB格式的,而且有多种表示格式:可能用2字节表示(RGB565格式),可能用3字节表示(RGB888),而原始的24位真彩色BMP文件则是按BGR格式排列的,需要对原始的图像数据进行转化。
2.在转化过程中,LCD上的显存地址固定是以LCD左上角为首地址,而BMP格式中正向图像是以图片的左下角为数据首地址的。因此在进行数据转化时还需要注意坐标的变换。
代码实现:
20 int setpixel(int x, int y, int color)
21 {
22 unsigned int offset;
23 unsigned short color_16;
24 unsigned int r, g, b;
25 unsigned short *p16;
26 unsigned int *p32;
27
28 /* 1. get offset */
29 offset = y * g_line_length + x*g_bytes_per_pixel;
30
31 /* 2. color convert to ... */
32 switch (g_var.bits_per_pixel)
33 {
34 case 8:
35 {
36 return -1;
37 break;
38 }
39 case 16:
40 {
41 /* RGB565 */
42 r = (color >> 16) & 0xff;
43 g = (color >> 8) & 0xff;
44 b = (color >> 0) & 0xff;
45
46 r >>= 3;
47 g >>= 2;
48 b >>= 3;
49
50 color_16 = (r<<11) | (g << 5) | b;
51
52 /* 3. write buffer */
53 p16 = (unsigned short *)(g_fb + offset);
54 *p16 = color_16;
55 return 0;
56 }
57 case 32:
58 {
59 /* 3. write buffer */
60 p32 = (unsigned int *)(g_fb + offset);
61 *p32 = color;
62 return 0;
63 }
64 }
65 return -1;
66 }
67
68 /* ./show_bmp /dev/fb0
*/ 69 int main(int argc, char **argv)
70 {
71 int fd;
72 PT_Picture2RGB bmpParser;
73 T_PictureData tpicData;
74
75 int xMax, yMax;
76
77 /* 1. open */
78 if (argc != 3)
79 {
80 printf("Usage: %s
, argv[0]); \n" 81 return -1;
82 }
83
84 fd = open(argv[1], O_RDWR);
85 if (fd < 0)
86 {
87 printf("can not open %s\n", argv[1]);
88 return -1;
89 }
90
91
92 /* 2. get var screen info */
93 if (ioctl(fd, FBIOGET_VSCREENINFO, &g_var))
94 {
95 printf("can't get var\n");
96 return -1;
97 }
98 g_line_length = g_var.xres * g_var.bits_per_pixel / 8;
99 g_bytes_per_pixel = g_var.bits_per_pixel / 8;
100
101
102 /* 3. mmap */
103 g_fb = mmap(NULL, g_line_length * g_var.yres, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
104 if (g_fb == (void *)-1)
105 {
106 printf("can't mmap\n");
107 return -1;
108 }
109
110 /* 4. 使用 BMP 解析器 解析 BMP 文件 */
111 bmpParser = GetBMPPaser();
112 bmpParser->PictureParsing(argv[2], &tpicData);
113
114 /* 5. 在 LCD 上显示 : tpicData.pucRgbData */
115 xMax = tpicData.iWidth > g_var.xres ? g_var.xres : tpicData.iWidth;
116 yMax = tpicData.iHeight > g_var.yres ? g_var.yres : tpicData.iHeight;
117
118 for (int x = 0; x < xMax; x++)
119 for (int y = 0; y < yMax; y++)
120 {
121 unsigned int iLineBytes = tpicData.iWidth*tpicData.iBpp/8;//一行数据的字节数
122 unsigned int offset = y * iLineBytes + x*tpicData.iBpp/8;
123 unsigned char *p = tpicData.pucRgbData + offset;
124 unsigned int color = ((unsigned int)p[0]<<16) | ((unsigned int)p[1]<<8) | ((unsigned int)p[2]);
125 setpixel(x, y, color);
126 }
127 bmpParser->PictureRelease(&tpicData);
128 return 0;
7.5.3
上机实验
编译:
source /opt/remi-sdk/environment-setup-aarch64-poky-linux
$CC -I ./ -o show_bmp show_bmp.c bmp2rgb.c
假设设置开发板的IP为:192.168.5.9,上传程序到开发板上
scp ./24bpp.bmp root@192.168.5.9:/mnt/
scp ./show_bmp root@192.168.5.9:/mnt/
测试:进入/mnt目录运行程序:
root@myir-remi-1g:~
root@myir-remi-1g:/mnt
24bpp.bmp show_bmp
在运行程序前,请确保已经接好HMDI显示屏。
root@myir-remi-1g:/mnt# ./show_bmp /dev/fb0 24bpp.bmp
运行程序后,可以看到HDMI显示屏上会显示bmp图片。
如您在使用瑞萨MCU/MPU产品中有任何问题,可识别下方二维码或复制网址到浏览器中打开,进入瑞萨技术论坛寻找答案或获取在线技术支持。
https://community-ja.renesas.com/zh/forums-groups/mcu-mpu/
未完待续
推荐阅读
Linux I2C设备访问方法 - RZ MPU工业控制教程连载(9)
用户态接口应用与编写APP - RZ MPU工业控制教程连载(10)
Linux SPI协议介绍与访问方法 - RZ MPU工业控制教程连载(11)