第11章 BMP图片显示
第11章 BMP图片显示
本期主要讲emWin支持的BMP图片显示,官方支持的主要有两种显示方法,一种是从外部存储器读取数据到内部存储器然后来显示图片,这种的显示速度要快些,另一种方法是直接从外部存储器读取数据并显示,这种办法的好处就是不要大的RAM需求,每次读取一些数据显示一次,缺点就是显示速度比较的慢。
这里有一点必须的要说明一下,官方支持的这个BMP图片的显示速度没有咱们平时用的在TFT上面开窗然后填充图片数据的速度快,但是有一个非常大的好处就是使用官方的这个函数才能充分的发挥背景重绘等机制。
11. 1 BMP函数说明
11. 2 绘制已经加载到存储器的BMP图片
11. 3 绘制无须加载到存储器的BMP图片
11. 4 实验总结
11.1 BMP图片支持
关于BMP图片格式方面的知识,大家可以在google上面查找资料进行了解。这里重点的介绍一下STemWin对BMP图片的格式的支持。
对于一些频繁调用的BMP图片,大家可以用第9章上面说的位图转换器进行图片转换即可,只不过本期教程是将其转换成C文件。这里举两个例子,一个是转换一个带透明色的PNG格式图标,另一个是转换一张BMP格式的图片
11.1.1 PNG格式图标转BMP这里我们演示的是如下带透明色的PNG图标,PNG格式的图标用到的地方还是非常多的,虽然用位图转换器(BmpCvt.exe)可以打开,但是转换后的C文件是不具备透明色效果的,这时得使用软件IconWorkshop进行转换(请使用正版软件),将其转换为32位带Alpha通道的BMP图片。
l 第一步:先用位图软件直接打开PNG格式的图标,打开后的效果如下:
对于带透明色的PNG图片,用位图转换器打开后是这种效果的,转换成C文件后显示出来是不正确的,得用软件IconWorkshop进行转换。
l 第二步:用软件IconWorkshop打开这个图标,打开后的效果如下:
然后将其另存为BMP格式的图片
l 第三步:再次用位图转换器打开上面转换好的BMP图片,打开后效果如下:
打开是这种效果,转换后的C文件才能正常的显示。
l 第四步:将上面的图片另存为C文件方法如下:
点击另存为C文件后会出现如下的界面:
这里我们就选择第一个True color alpha chanel进行保存。点击OK后会生成C文件。生成的C文件保存在了这个图片所在的文件夹里面(注意是大家加载的这个BMP图片文件夹,不是位图软件文件夹)。
下面我们在emWin5.24模拟器上面显示一下我们刚才转换好的图标。
实际运行代码如下(图片数据就不贴出来了,看本期教程配套的例子)
- GUI_CONST_STORAGE GUI_BITMAP bm20140406102510816_easyicon_net_96 = {
- 96, // xSize
- 96, // ySize
- 384, // BytesPerLine
- 32, // BitsPerPixel
- (unsigned char *)_ac20140406102510816_easyicon_net_96, // Pointer to picture data
- NULL, // Pointer to palette
- GUI_DRAW_BMP8888
- };
-
-
- void MainTask(void)
- {
- GUI_Init();
- GUI_DrawBitmap(&bm20140406102510816_easyicon_net_96, 30, 30);
- while(1)
- {
- GUI_Delay(100);
- }
- }
实际显示效果如下:
11.1.2 BMP格式图标转换
下面我们讲解一下BMP格式的位图转换。
l 第一步:比如我们要转如下所示的BMP图片。
l 第二步:这里我们做一次图片的格式转换,转换格式如下:
l 第三步:转换后,另存为C文件的格式如下:
转换后生成的C文件和图片在同一个文件夹里面。下面我们在emWin5.24模拟器上面显示下这个图片
下面是模拟器上面实际运行的代码(图片数据没有贴,全部运行代码请看本期教程配套的例子)
- GUI_CONST_STORAGE GUI_BITMAP bm1 = {
- 480, // xSize
- 272, // ySize
- 960, // BytesPerLine
- 16, // BitsPerPixel
- (unsigned char *)_ac1, // Pointer to picture data
- NULL, // Pointer to palette
- GUI_DRAW_BMP444_12
- };
-
-
- void MainTask(void)
- {
- GUI_Init();
- GUI_DrawBitmap(&bm1, 30, 30);
- while(1)
- {
- GUI_Delay(100);
- }
- }
下面是实际显示效果:
讲完这两个例子以后,大家应该对位图转换器的使用有所了解了。下面开始讲直接的显示BMP格式的图片。STemWin支持的API函数如下:
11.2 绘制已经加载到存储器的BMP图片
将图片加载到存储器后进行显示比较的耗内存,所以这里就使用开发板外置的2MB SRAM做STemWin的动态内存空间,并通过相应的API函数申请动态内存来加载SD卡等外部存储器中的BMP图片。申请和释放STemWin动态内存的方法如下:
- /* 申请一块内存空间 并且将其清零 */
- hMem = GUI_ALLOC_AllocZero(100000);
- /* 将申请到内存的句柄转换成指针类型 */
- _acBuffer2 = GUI_ALLOC_h2p(hMem);
- /* 释放申请的动态内存 */
- GUI_ALLOC_Free(hMem);
比如我们要显示下面的BMP格式的图片:
就可以把这个图片放到SD卡中,然后通过程序把这个图片数据全部的加载到SRAM中,最后在屏上进行显示。这个工程的实现主要分为如下三个部分:
? SRAM和SD卡及其文件系统的初始化
? 图片的加载以及显示函数
? 主函数
下面把这三部分详细的讲解下:
l SRAM和SD卡及其文件系统的初始化
- /*
- *********************************************************************************************************
- * 函 数 名: bsp_Init
- * 功能说明: 初始化硬件设备
- * 形 参:无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- void bsp_Init(void)
- {
- /* Enable CRC clock */
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_CRC, ENABLE);
- NVIC_Configuration(); /* 中断优先级分组配置 */
- bsp_InitUart(); /* 初始化串口 */
- bsp_InitLed(); /* 初始LED指示灯端口 */
- bsp_InitKey(); /* 按键初始化 */
- bsp_InitExtSRAM();(1)
- LCD_InitHard(); /* 初始化显示器硬件(配置GPIO和FSMC,给LCD发送初始化指令) */
- TOUCH_InitHard();
- /* 挂载文件系统 */
- result = f_mount(0, &fs);(2)
- if (result != FR_OK)
- {
- return;
- }
- #ifdef TRACE_EN /* See project / compiler preprocessor options. */
- BSP_CPU_REG_DBGMCU_CR |= BSP_DBGMCU_CR_TRACE_IOEN_MASK; /* Enable tracing (see Note #2). */
- BSP_CPU_REG_DBGMCU_CR &= ~BSP_DBGMCU_CR_TRACE_MODE_MASK; /* Clr trace mode sel bits. */
- BSP_CPU_REG_DBGMCU_CR |= BSP_DBGMCU_CR_TRACE_MODE_SYNC_04; /* Cfg trace mode to synch 4-bit. */
- #endif
- }
1. 初始化SRAM,主要是FSMC的配置
2. 初始化文件系统FatFS,用于外接SD卡的读写操作。
l 图片的加载以及显示函数
- /*
- *********************************************************************************************************
- * 函 数 名: _ShowBMP
- * 功能说明: 显示BMP图片
- * 形 参:sFilename 要显示的图片名字
- * 返 回 值: 无
- *********************************************************************************************************
- */
- static void _ShowBMP(const char * sFilename)
- {
- int XSize, YSize;
- GUI_HMEM hMem;
- char *_acBuffer2;
- int i;
- /* 申请一块内存空间 并且将其清零 */
- hMem = GUI_ALLOC_AllocZero(1024*1024);(1)
- /* 将申请到内存的句柄转换成指针类型 */
- _acBuffer2 = GUI_ALLOC_h2p(hMem);
- /* 打开文件 */
- result = f_open(&file, sFilename, FA_OPEN_EXISTING | FA_READ | FA_OPEN_ALWAYS);(2)
- if (result != FR_OK)
- {
- return;
- }
- result = f_read(&file, _acBuffer2, file.fsize, &bw);(3)
- if (result != FR_OK)
- {
- return;
- }
- XSize = GUI_BMP_GetXSize(_acBuffer2);(4)
- YSize = GUI_BMP_GetYSize(_acBuffer2);
- while(1)
- {
- for(i = 100; i < 600; i += 10)
- {
- GUI_BMP_DrawScaled(_acBuffer2, (5)
- (LCD_GetXSize() - XSize*i/100)/2,
- (LCD_GetYSize() - YSize*i/100)/2,
- i,
- 100);
- GUI_Delay(100);
- }
- GUI_Clear();
- }
- // GUI_ALLOC_Free(hMem);(6)
- // f_close(&file);
- }
1. 申请1MB的动态内存。并将申请到内存的句柄转换为指针类型。
2. 打开相应的BMP文件。
3. 读取BMP文件数据,并将数据全部存储到申请的动态内存里面。
4. 函数GUI_BMP_GetXSize()和GUI_BMP_GetYSize()用于返回BMP图片的大小。
5. 函数GUI_BMP_DrawScaled主要用于实现图片的放缩操作。这里是将图片显示到屏的正中。
6. 释放申请的动态内存,申请的动态内存一定要记得释放。
l 主函数
- /*
- *********************************************************************************************************
- * 函 数 名: MainTask
- * 功能说明: GUI主函数
- * 形 参:无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- void MainTask(void)
- {
- GUI_Init();
- /* 设置皮肤函数 */ (1)
- PROGBAR_SetDefaultSkin(PROGBAR_SKIN_FLEX);
- FRAMEWIN_SetDefaultSkin(FRAMEWIN_SKIN_FLEX);
- PROGBAR_SetDefaultSkin(PROGBAR_SKIN_FLEX);
- BUTTON_SetDefaultSkin(BUTTON_SKIN_FLEX);
- CHECKBOX_SetDefaultSkin(CHECKBOX_SKIN_FLEX);
- DROPDOWN_SetDefaultSkin(DROPDOWN_SKIN_FLEX);
- SCROLLBAR_SetDefaultSkin(SCROLLBAR_SKIN_FLEX);
- SLIDER_SetDefaultSkin(SLIDER_SKIN_FLEX);
- HEADER_SetDefaultSkin(HEADER_SKIN_FLEX);
- RADIO_SetDefaultSkin(RADIO_SKIN_FLEX);
- while(1) (2)
- {
- _ShowBMP("1.bmp");
- }
- }
1. 这里是开启皮肤色。
2. 在主程序中调用图片显示即可。
实际显示效果如下:
第一幅是原始图片:
第二幅是放大后的图片:
11.3 绘制无需加载到存储器的BMP图片
绘制无需加载到存储器的BMP图片方式可以有效的解决内部动态内存不够的情况,不过缺点也很明显,图片的显示速度很慢。这种方式一般是每次读取一行像素的数据,然后进行显示。这个工程的实现主要分为如下三个部分:
? 使用芯片内部的SRAM作为动态内存
? 图片的加载以及显示函数
? 主函数
下面把这三部分详细的讲解下:
l 使用芯片内部的SRAM作为动态内存(在文件GUIConf.c里面)
- /*
- *********************************************************************************************************
- *
- * Defines
- *
- ********************************************************************************************************
- */
- /* Define the available number of bytes available for the GUI */
- #define GUI_NUMBYTES (1024*70)(1)
- /* Define the average block size */
- #define GUI_BLOCKSIZE 0x80(2)
- /*********************************************************************
- *
- * GUI_X_Config
- *
- * Purpose:
- * Called during the initialization process in order to set up the
- * available memory for the GUI.
- **********************************************************************
- */
- void GUI_X_Config(void)
- {
- #if 1(3)
- /* 32 bit aligned memory area */
- static U32 aMemory[GUI_NUMBYTES / 4];
- /* Assign memory to emWin */
- GUI_ALLOC_AssignMemory(aMemory, GUI_NUMBYTES);
- GUI_ALLOC_SetAvBlockSize(GUI_BLOCKSIZE);
- #else
- static U32 *aMemory;
- aMemory = (U32 *)EXT_SRAM_ADDR;
- /* Assign memory to emWin */
- GUI_ALLOC_AssignMemory(aMemory, GUI_NUMBYTES);
- GUI_ALLOC_SetAvBlockSize(GUI_BLOCKSIZE);
- #endif
- }
1. 定义动态内存的大小,单位是字节。
2. 这里是定义内存块的大小,默认每个内存的大小是128字节。内存块的实际大小要看用户程序的需要,测试那种情况下最省内存,一般情况下取128个字节即可。
3. 这里是使用芯片内部的内存作为STemWin的动态内存空间。
l 图片的加载以及显示函数
- /* 实际的测试需要是图像宽度的4倍即可,切记(也就是保证每个像素如果是32位数据的情况) */
- static char _acBuffer[480*4]; (1)
- /*
- *********************************************************************************************************
- *
- * _GetData
- *
- * Purpose:
- * This routine is called by GUI_JPEG_DrawEx(). The routine is responsible
- * for setting the data pointer to a valid data location with at least
- * one valid byte.
- *
- * Parameters:
- * p - Pointer to application defined data.
- * NumBytesReq - Number of bytes requested.
- * ppData - Pointer to data pointer. This pointer should be set to
- * a valid location.
- * StartOfFile - If this flag is 1, the data pointer should be set to the
- * beginning of the data stream.
- *
- * Return value:
- * Number of data bytes available.
- *********************************************************************************************************
- */
- static int _GetData(void * p, const U8 ** ppData, unsigned NumBytesReq, U32 Off) (2)
- {
- static int FileAddress = 0;
- UINT NumBytesRead;
- FIL *PicFile;
- PicFile = (FIL *)p;
- /*
- * Check buffer size
- */
- if (NumBytesReq > sizeof(_acBuffer)) {
- NumBytesReq = sizeof(_acBuffer);
- }
- /*
- * Set file pointer to the required position
- */
- if(Off == 1) FileAddress = 0;
- else FileAddress = Off;
- result =f_lseek(PicFile, FileAddress);
- /*
- * Read data into buffer
- */
- result = f_read(PicFile, _acBuffer, NumBytesReq, &NumBytesRead);
- /*
- * Set data pointer to the beginning of the buffer
- */
- *ppData = (const U8 *)_acBuffer;
- /*
- * Return number of available bytes
- */
- return NumBytesRead;
- }
- /*
- *********************************************************************************************************
- * 函 数 名: _ShowBMPEx
- * 功能说明: 显示BMP图片
- * 形 参:sFilename 要显示图片的名字
- * 返 回 值: 无
- *********************************************************************************************************
- */
- static void _ShowBMPEx(const char * sFilename)
- {
- OS_ERR err;
- /* 打开文件 */
- result = f_open(&file, sFilename, FA_OPEN_EXISTING | FA_READ | FA_OPEN_ALWAYS);
- if (result != FR_OK)
- {
- return;
- }
- // XSize = GUI_BMP_GetXSizeEx(_GetData, &file);
- // YSize = GUI_BMP_GetYSizeEx(_GetData, &file);
- OSSchedLock(&err); (3)
- GUI_BMP_DrawEx(_GetData, &file, 0, 0);(4)
- OSSchedUnlock(&err);
- f_close(&file);
- }
1. 这个数据空间的大小比较讲究,至少得保证大于等于实际要显示图片一行像素的数据。比如要显示800*480分辨率,16bpp的一幅图片,那这个buffer的大小至少得是800*(16/8) = 1600字节。
2. 这个函数非常重要,大家要认真看一下英文注释即可。
3. 加上调度锁,防止图片显示的过程中出现异常。
4. 实现图片的显示函数。
l 主函数
- /*
- *********************************************************************************************************
- * 函 数 名: MainTask
- * 功能说明: GUI主函数
- * 形 参:无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- void MainTask(void)
- {
- GUI_Init();
- /* 设置皮肤函数 */
- PROGBAR_SetDefaultSkin(PROGBAR_SKIN_FLEX);
- FRAMEWIN_SetDefaultSkin(FRAMEWIN_SKIN_FLEX);
- PROGBAR_SetDefaultSkin(PROGBAR_SKIN_FLEX);
- BUTTON_SetDefaultSkin(BUTTON_SKIN_FLEX);
- CHECKBOX_SetDefaultSkin(CHECKBOX_SKIN_FLEX);
- DROPDOWN_SetDefaultSkin(DROPDOWN_SKIN_FLEX);
- SCROLLBAR_SetDefaultSkin(SCROLLBAR_SKIN_FLEX);
- SLIDER_SetDefaultSkin(SLIDER_SKIN_FLEX);
- HEADER_SetDefaultSkin(HEADER_SKIN_FLEX);
- RADIO_SetDefaultSkin(RADIO_SKIN_FLEX);
- _ShowBMPEx("1.bmp");
- while(1)
- {
- GUI_Delay(200);
- }
- }
实际显示效果如下:
11.4 实验总结
总的来说,使用STM32F407刷新图片的能力有限,主要有以下四个限制图片显示速度的地方:
1、图片数据的读取(比如从SD卡中读取图片)。
2、FSMC总线的速度有限。
3、图片的解码过程也需要花费时间。
4、由于使用的是emWin本身的函数实现BMP图片的显示,不能够实现屏幕的开窗功能进行加速。这个在一定程度上面也限制了图片的显示速度。