在Framebuffer上显示图片-RZMPU工业控制教程连载(15)

瑞萨MCU小百科 2024-09-20 12:04

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 #pragma pack(push) /* 将当前 pack 设置压栈保存 */16 #pragma pack(1) /* 必须在结构体定义之前使用,这是为了让结构体中各成员按 1 字节对齐 */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;2425 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;3839 #pragma pack(pop) /* 恢复先前的 pack 设置 */404142 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 };495051 /**********************************************************************52 * 函数名称:IsBmp53 * 功能描述:判断该文件是否为 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;6768 if (strCheckHeader[0] != 0x42 || strCheckHeader[1] != 0x4d)69 return -1;70 else71 return 0;72 }73747576 /**********************************************************************77 * 函数名称:MapFile78 * 功能描述:使用 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;8889 /* 打开文件 */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 }101102 /**********************************************************************103 * 函数名称:DecodeBmp2Rgb104 * 功能描述:把 BMP 文件转化为 rgb 格式105 * 输入参数:strFileName - 文件名106 * ptData - 内含图像信息107 * 返 回 值:0 - 成功其他值 - 失败108 * -1 - 文件不是 BMP 格式109 * -2 - 不支持的 bpp110 * -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;122123 /* 判断该文件是否为 BMP 格式 */124 if (IsBmp(&ptData->ptFp, strFileName))125 return -1;126127 /* 将 BMP 文件映射到内存空间 */128 MapFile(ptData);129130131 aFileHead = ptData->pucFileData;132133 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 }148149 /* 分配空间 */150 ptData->pucBmpData = malloc(ptData->iBmpDataSize);151 ptData->pucRgbData = malloc(ptData->iBmpDataSize);152153 if (NULL == ptData->pucBmpData||NULL == ptData->pucRgbData)154 return -2;155156 /* 从 bmp 文件中读取图像信息,24bpp 的 BMP 图像为 BGR 格式 */157 pucDest = ptData->pucBmpData;158 iLineWidthAlign = (iLineBytes + 3) & ~0x3; /* 向 4 取整 */159 pucSrc = aFileHead + ptBITMAPFILEHEADER->bfOffBits;160161 pucSrc = pucSrc + (ptData->iHeight - 1) * iLineWidthAlign;162163 /* 对于 bmp 文件中的源数据,是以左下角为原点计算坐标的,因此拷贝数据时需要转换坐标 */164 for (y = 0; y < ptData->iHeight; y++)165 {166 memcpy(pucDest, pucSrc, ptData->iWidth*3);167 pucSrc -= iLineWidthAlign;168 pucDest += iLineBytes;169 }170171172 /* 将得到的 BGR 数据转化为 RGB 数据 */173 for (y = 0; y < ptData->iHeight; y++){174 for(x = 0;xiWidth*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 }180181 return 0;182183 }184185186187 /**********************************************************************188 * 函数名称:ReleaseBmp2Rgb189 * 功能描述:释放解析及使用 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 }201202 /**********************************************************************203 * 函数名称:GetBMPPaser204 * 功能描述:返回"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;2728 /* 1. get offset */29 offset = y * g_line_length + x*g_bytes_per_pixel;3031 /* 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;4546 r >>= 3;47 g >>= 2;48 b >>= 3;4950 color_16 = (r<<11) | (g << 5) | b;5152 /* 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 }6768 /* ./show_bmp /dev/fb0  */69 int main(int argc, char **argv)70 {71 int fd;72 PT_Picture2RGB bmpParser;73 T_PictureData tpicData;7475 int xMax, yMax;7677 /* 1. open */78 if (argc != 3)79 {80 printf("Usage: %s  \n", argv[0]);81 return -1;82 }8384 fd = open(argv[1], O_RDWR);85 if (fd < 0)86 {87 printf("can not open %s\n", argv[1]);88 return -1;89 }909192 /* 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;100101102 /* 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 }109110 /* 4. 使用 BMP 解析器 解析 BMP 文件 */111 bmpParser = GetBMPPaser();112 bmpParser->PictureParsing(argv[2], &tpicData);113114 /* 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;117118 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:~# cd /mnt/root@myir-remi-1g:/mnt# ls24bpp.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)

更多精彩,敬请关注


评论
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2025-01-09 09:58 36浏览
  • 故障现象一辆2017款东风风神AX7车,搭载DFMA14T发动机,累计行驶里程约为13.7万km。该车冷起动后怠速运转正常,热机后怠速运转不稳,组合仪表上的发动机转速表指针上下轻微抖动。 故障诊断 用故障检测仪检测,发动机控制单元中无故障代码存储;读取发动机数据流,发现进气歧管绝对压力波动明显,有时能达到69 kPa,明显偏高,推断可能的原因有:进气系统漏气;进气歧管绝对压力传感器信号失真;发动机机械故障。首先从节气门处打烟雾,没有发现进气管周围有漏气的地方;接着拔下进气管上的两个真空
    虹科Pico汽车示波器 2025-01-08 16:51 101浏览
  • 一个真正的质量工程师(QE)必须将一件产品设计的“意图”与系统的可制造性、可服务性以及资源在现实中实现设计和产品的能力结合起来。所以,可以说,这确实是一种工程学科。我们常开玩笑说,质量工程师是工程领域里的「侦探」、「警察」或「律师」,守护神是"墨菲”,信奉的哲学就是「墨菲定律」。(注:墨菲定律是一种启发性原则,常被表述为:任何可能出错的事情最终都会出错。)做质量工程师的,有时会不受欢迎,也会被忽视,甚至可能遭遇主动或被动的阻碍,而一旦出了问题,责任往往就落在质量工程师的头上。虽然质量工程师并不负
    优思学院 2025-01-09 11:48 66浏览
  • 1月7日-10日,2025年国际消费电子产品展览会(CES 2025)盛大举行,广和通发布Fibocom AI Stack,赋智千行百业端侧应用。Fibocom AI Stack提供集高性能模组、AI工具链、高性能推理引擎、海量模型、支持与服务一体化的端侧AI解决方案,帮助智能设备快速实现AI能力商用。为适应不同端侧场景的应用,AI Stack具备海量端侧AI模型及行业端侧模型,基于不同等级算力的芯片平台或模组,Fibocom AI Stack可将TensorFlow、PyTorch、ONNX、
    物吾悟小通 2025-01-08 18:17 46浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球中空长航时无人机产值达到9009百万美元,2024-2030年期间年复合增长率CAGR为8.0%。 环洋市场咨询机构出版了的【全球中空长航时无人机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球中空长航时无人机总体规模,包括产量、产值、消费量、主要生产地区、主要生产商及市场份额,同时分析中空长航时无人机市场主要驱动因素、阻碍因素、市场机遇、挑战、新产品发布等。报告从中空长航时
    GIRtina 2025-01-09 10:35 40浏览
  • 本文介绍编译Android13 ROOT权限固件的方法,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。关闭selinux修改此文件("+"号为修改内容)device/rockchip/common/BoardConfig.mkBOARD_BOOT_HEADER_VERSION ?= 2BOARD_MKBOOTIMG_ARGS :=BOARD_PREBUILT_DTB
    Industio_触觉智能 2025-01-08 00:06 105浏览
  • 在智能网联汽车中,各种通信技术如2G/3G/4G/5G、GNSS(全球导航卫星系统)、V2X(车联网通信)等在行业内被广泛使用。这些技术让汽车能够实现紧急呼叫、在线娱乐、导航等多种功能。EMC测试就是为了确保在复杂电磁环境下,汽车的通信系统仍然可以正常工作,保护驾乘者的安全。参考《QCT-基于LTE-V2X直连通信的车载信息交互系统技术要求及试验方法-1》标准10.5电磁兼容试验方法,下面将会从整车功能层面为大家解读V2X整车电磁兼容试验的过程。测试过程揭秘1. 设备准备为了进行电磁兼容试验,技
    北汇信息 2025-01-09 11:24 52浏览
  •  在全球能源结构加速向清洁、可再生方向转型的今天,风力发电作为一种绿色能源,已成为各国新能源发展的重要组成部分。然而,风力发电系统在复杂的环境中长时间运行,对系统的安全性、稳定性和抗干扰能力提出了极高要求。光耦(光电耦合器)作为一种电气隔离与信号传输器件,凭借其优秀的隔离保护性能和信号传输能力,已成为风力发电系统中不可或缺的关键组件。 风力发电系统对隔离与控制的需求风力发电系统中,包括发电机、变流器、变压器和控制系统等多个部分,通常工作在高压、大功率的环境中。光耦在这里扮演了
    晶台光耦 2025-01-08 16:03 83浏览
  • 在过去十年中,自动驾驶和高级驾驶辅助系统(AD/ADAS)软件与硬件的快速发展对多传感器数据采集的设计需求提出了更高的要求。然而,目前仍缺乏能够高质量集成多传感器数据采集的解决方案。康谋ADTF正是应运而生,它提供了一个广受认可和广泛引用的软件框架,包含模块化的标准化应用程序和工具,旨在为ADAS功能的开发提供一站式体验。一、ADTF的关键之处!无论是奥迪、大众、宝马还是梅赛德斯-奔驰:他们都依赖我们不断发展的ADTF来开发智能驾驶辅助解决方案,直至实现自动驾驶的目标。从新功能的最初构思到批量生
    康谋 2025-01-09 10:04 44浏览
  • 「他明明跟我同梯进来,为什么就是升得比我快?」许多人都有这样的疑问:明明就战绩也不比隔壁同事差,升迁之路却比别人苦。其实,之间的差异就在于「领导力」。並非必须当管理者才需要「领导力」,而是散发领导力特质的人,才更容易被晓明。许多领导力和特质,都可以通过努力和学习获得,因此就算不是天生的领导者,也能成为一个具备领导魅力的人,进而被老板看见,向你伸出升迁的橘子枝。领导力是什么?领导力是一种能力或特质,甚至可以说是一种「影响力」。好的领导者通常具备影响和鼓励他人的能力,并导引他们朝着共同的目标和愿景前
    优思学院 2025-01-08 14:54 90浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦