首页 > 其他分享 >《MiniPRO H750开发指南》第四十八章 图片显示实验

《MiniPRO H750开发指南》第四十八章 图片显示实验

时间:2022-10-12 10:03:28浏览次数:66  
标签:MiniPRO 解码 GIF uint16 函数 第四十八章 picinfo H750 图片

第四十八章图片显示实验​


在开发产品的时候,很多时候,我们都会用到图片解码,在本章中,我们将向大家介绍如何通过STM32H7来解码BMP/JPG/JPEG/GIF等图片,并在LCD上显示出来。​

本章分为如下几个部分:​

48.1 图片格式简介​

48.2 硬件设计​

48.3 程序设计​

48.4 下载验证​



48.1 图片格式介绍

我们常用的图片格式有很多,一般最常用的有三种:JPEG(或JPG)、BMP和GIF。其中JPEG(或JPG)和BMP是静态图片,而GIF则是可以实现动态图片。下面,我们简单介绍一下这三种图片格式。​

48.1.1 BMP编码简介

我们常用的图片格式有很多,一般最常用的有三种:JPEG(或JPG)、BMP和GIF。其中JPEG(或JPG)和BMP是静态图片,而GIF则是可以实现动态图片。下面,我们简单介绍一下这三种图片格式。​

首先,我们来看看BMP图片格式。BMP(全称Bitmap)是Window操作系统中的标准图像文件格式,文件后缀名为“.bmp”,使用非常广。它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此,BMP文件所占用的空间很大,但是没有失真。BMP文件的图像深度可选lbit、4bit、8bit、16bit、24bit及32bit。BMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。​

典型的BMP图像文件由四部分组成:​

1、位图头文件数据结构,它包含BMP图像文件的类型、显示内容等信息;​

2、位图信息数据结构,它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息;​

3、调色板,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24位的BMP)就不需要调色板;​

4、位图数据,这部分的内容根据BMP位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜色索引值。​

关于BMP的详细介绍,请参考光盘的《BMP图片文件详解.pdf》。​

48.1.2 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图片格式。​

48.1.3 GIF编码简介

GIF(Graphics Interchange Format)是CompuServe公司开发的图像文件存储格式,1987年开发的GIF文件格式版本号是GIF87a,1989年进行了扩充,扩充后的版本号定义为GIF89a。GIF图像文件以数据块(block)为单位来存储图像的相关信息。一个GIF文件由表示图形/图像的数据块、数据子块以及显示图形/图像的控制信息块组成,称为GIF数据流(DataStream)。数据流中的所有控制信息块和数据块都必须在文件头(Header)和文件结束块(Trailer)之间。​

GIF文件格式采用了LZW(Lempel-ZivWalch)压缩算法来存储图像数据,定义了允许用户为图像设置背景的透明(transparency)属性。此外,GIF文件格式可在一个文件中存放多幅彩色图形/图像。如果在GIF文件中存放有多幅图,它们可以像演幻灯片那样显示或者像动画那样演示。​

一个GIF文件的结构可分为文件头(FileHeader)、GIF数据流(GIFDataStream)和文件终结器(Trailer)三个部分。文件头包含GIF文件署名(Signature)和版本号(Version);GIF数据流由控制标识符、图象块(ImageBlock)和其他的一些扩展块组成;文件终结器只有一个值为0x3B的字符(';')表示文件结束。​

关于GIF的详细介绍,请参考光盘GIF解码相关资料。图片格式简介,我们就介绍到这里。​

48.2 硬件设计

1. 例程功能

开机的时候先检测字库,然后检测SD卡是否存在,如果SD卡存在,则开始查找SD卡根目录下的PICTURE文件夹,如果找到则显示该文件夹下面的图片文件(支持bmp、jpg、jpeg或gif格式),循环显示,通过按KEY0和KEY1可以快速浏览下一张和上一张,KEY_UP按键用于暂停/继续播放,LED1用于指示当前是否处于暂停状态。如果未找到PICTURE文件夹/任何图片文件,则提示错误。​

还可以通过USMART调用ai_load_picfile和minibmp_decode解码任意指定路径的图片。​

LED0闪烁,提示程序运行。​

2. 硬件资源

1)LED灯​

RED :LED0 - PB4​

GREEN:LED1 - PE6​

2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面)​

3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)​

4)独立按键:KEY0 - PA1、KEY1 – PA15、WK_UP - PA0​

5)SD卡,通过SDMMC1(SDMMC_D0~D4(PC8~PC11),SDMMC_SCK(PC12),​

SDMMC_CMD(PD2))连接​

6)norflash(QSPI FLASH芯片,连接在QSPI上)​

48.3 程序设计

48.3.1 程序流程图

《MiniPRO H750开发指南》第四十八章 图片显示实验_初始化


图48.3.1.1 图片显示实验程序流程图​

本程序主要靠文件操作,打开指定位置的图片并调用图片解码库解码后显示不同格式的图片。我们加入了一些交互信息,以控制图片的显示切换等。​

48.3.2 程序解析

1. PICTURE代码

这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。PICTURE驱动源码包括八个文件:bmp.c、bmp.h、tjpgd.c、tjpgd.h、gif.c、gif.h、piclib.c和piclib.h。 ​

其中:​

bmp.c和bmp.h用于实现对bmp文件的解码;​

tjpgd.c和tjpgd.h用于实现对jpeg/jpg文件的解码;​

gif.c和gif.h用于实现对gif文件的解码;​

这几个代码太长了,而且也有规定的标准,需要结合各个图片编码的格式来编写,所以我们在这里不贴出来,大家查看光盘中的源码的实现过程即可。下面我们重点讲解这几个解码库对应到我们的LCD的显示部分。​

1解码的控制句柄_pic_phy_pic_info

我们使用这个接口,把解码后的图形数据与LCD的实际操作对应起来。为了方便去显示图片,我们需要将图片的信息与我们的LCD联系上。这里我们定义了_pic_phy和_pic_info分别用于定义图片解码库的LCD操作和存放解码后的图片尺寸颜色信息。它们的定义如下:​

/* 在移植的时候,必须由用户自己实现这几个函数 */​
typedef struct​
{​
/* 读点函数 */​
uint32_t(*read_point)(uint16_t, uint16_t);​
/* 画点函数 */​
void(*draw_point)(uint16_t, uint16_t, uint32_t);​
/* 单色填充函数 */​
void(*fill)(uint16_t, uint16_t, uint16_t, uint16_t, uint32_t);​
/* 画水平线函数 */​
void(*draw_hline)(uint16_t, uint16_t, uint16_t, uint16_t);​
/* 颜色填充 */​
void(*fillcolor)(uint16_t, uint16_t, uint16_t, uint16_t, uint16_t *);​
} _pic_phy;​

/* 图像信息 */​
typedef struct​
{​
uint16_t lcdwidth; /* LCD的宽度 */​
uint16_t lcdheight; /* LCD的高度 */​
uint32_t ImgWidth; /* 图像的实际宽度和高度 */​
uint32_t ImgHeight;​
uint32_t Div_Fac; /* 缩放系数 (扩大了8192倍的) */​
uint32_t S_Height; /* 设定的高度和宽度 */​
uint32_t S_Width;​
uint32_t S_XOFF; /* x轴和y轴的偏移量 */​
uint32_t S_YOFF;​
uint32_t staticx; /* 当前显示到的xy坐标 */​
uint32_t staticy;​
} _pic_info;​
在piclib.c文件中,我们用上述类型定义了两个结构体,具体如下:​
_pic_info picinfo; /* 图片信息 */​
_pic_phy pic_phy; /* 图片显示物理接口 */

2piclib_init函数

piclib_init函数,该函数用于初始化图片解码的相关信息,用于定义解码后的LCD操作。 具体定义如下:​

/**​
画图初始化​
在画图之前,必须先调用此函数, 指定相关函数​
无​
无​
*/​
void piclib_init(void)​
{​
pic_phy.read_point = lcd_read_point; /* 读点函数实现,仅BMP需要 */​
pic_phy.draw_point = lcd_draw_point; /* 画点函数实现 */​
pic_phy.fill = lcd_fill; /* 填充函数实现,仅GIF需要 */​
pic_phy.draw_hline = lcd_draw_hline; /* 画线函数实现,仅GIF需要 */​
pic_phy.fillcolor = piclib_fill_color; /* 颜色填充函数实现,仅TJPGD需要 */​
picinfo.lcdwidth = lcddev.width; /* 得到LCD的宽度像素 */​
picinfo.lcdheight = lcddev.height; /* 得到LCD的高度像素 */​
picinfo.ImgWidth = 0; /* 初始化宽度为0 */​
picinfo.ImgHeight = 0; /* 初始化高度为0 */​
picinfo.Div_Fac = 0; /* 初始化缩放系数为0 */​
picinfo.S_Height = 0; /* 初始化设定的高度为0 */​
picinfo.S_Width = 0; /* 初始化设定的宽度为0 */​
picinfo.S_XOFF = 0; /* 初始化x轴的偏移量为0 */​
picinfo.S_YOFF = 0; /* 初始化y轴的偏移量为0 */​
picinfo.staticx = 0; /* 初始化当前显示到的x坐标为0 */​
picinfo.staticy = 0; /* 初始化当前显示到的y坐标为0 */​
}
  • 函数描述:初始化图片解码的相关信息,这些函数必须由用户在外部实现。我们使用之前LCD的操作函数对这个结构体中的绘制操作:画点、画线、画圆等定义与我们的LCD操作对应起来。具体这些操作可以查看TFT LCD一节的描述。​
  • 函数形参:无。​
  • 函数返回值:无。​3piclib_alpha_blend函数RGB色彩中,一个标准像素由32位组成:透明度(8bit)+R(8bit)+B(8bit)+B(8bit),8位的α通道(alpha channel)位表示该像素如何产生特技效果,即通常我们说的半透明。alpha的取值一般为0到255。为0时,表示是全透明的,即图片是看不见的。为255时,表示图片是显示原始图的。中间值即为半透明状态。计算alpha blending时,通常的方法是将源像素的RGB值,分别与目标像素(如背景)的RGB按比例混合,最后得到一个混合后的RGB值。函数定义如下:​
/**​
快速ALPHA BLENDING算法​
颜色数​
目标颜色​
透明程度(0~32)​
混合后的颜色​
*/​
uint16_t piclib_alpha_blend(uint16_t src, uint16_t dst, uint8_t alpha)​
{​
uint32_t src2;​
uint32_t dst2;​
/* Convert to 32bit |-----GGGGGG-----RRRRR------BBBBB| */​
src2 = ((src << 16) | src) & 0x07E0F81F;​
dst2 = ((dst << 16) | dst) & 0x07E0F81F;​
dst2 = ((((dst2 - src2) * alpha) >> 5) + src2) & 0x07E0F81F;​
return (dst2 >> 16) | dst2;​
}
  • 函数描述:piclib_alpha_blend函数,该函数用于实现半透明效果,在小格式(图片分辨率小于LCD分辨率)bmp解码的时候,可能被用到。​
  • 函数形参:形参1是为RGB色彩编号,这里我们使用的是RGB565模式,故只有16位;​
    形参2是目标象素,使用时我们一般指背景颜色。​
    3是透明度:有效范围为0~255,0表示全透明,255表不透明。​
  • 函数返回值:返回计算后的透明度颜色数值。​4piclib_ai_draw_init函数对于给定区域,为了显示更好看,一般会选择图片居中显示,此函数实现此功能,把图片在显示区域中居中。函数定义如下:​
/**​
初始化智能画点​
无​
无​
*/​
void piclib_ai_draw_init(void)​
{​
float temp, temp1;​
temp = (float)picinfo.S_Width / picinfo.ImgWidth;​
temp1 = (float)picinfo.S_Height / picinfo.ImgHeight;​
if (temp < temp1)temp1 = temp; /* 取较小的那个 */​
if (temp1 > 1)temp1 = 1;​
/* 使图片处于所给区域的中间 */​
picinfo.S_XOFF += (picinfo.S_Width - temp1 * picinfo.ImgWidth) / 2;​
picinfo.S_YOFF += (picinfo.S_Height - temp1 * picinfo.ImgHeight) / 2;​
temp1 *= 8192; /* 扩大8192倍 */​
picinfo.Div_Fac = temp1;​
picinfo.staticx = 0xffff;​
picinfo.staticy = 0xffff; /* 放到一个不可能的值上面 */​
}
  • 函数描述:piclib_ai_draw_init函数,该函数使解码后的图片信息处于所给的区域的中间。​
  • 函数形参:无。​
  • 函数返回值:无。我们可以在显示实例中测试加与不加此函数的显示效果差异。​5piclib_is_element_ok函数对于给定区域,为了显示更好看,一般会选择图片居中显示,此函数实现此功能,把图片在显示区域中居中。函数定义如下:​
/**​
判断这个像素是否可以显示​
像素原始坐标​
功能变量​
无​
操作结果​
不需要显示​
需要显示​
*/​
__inline uint8_t piclib_is_element_ok(uint16_t x, uint16_t y, uint8_t chg)​
{​
if (x != picinfo.staticx || y != picinfo.staticy)​
{​
if (chg == 1)​
{​
picinfo.staticx = x;​
picinfo.staticy = y;​
}​
return 1;​
}​
else​
{​
return 0;​
}​
}
  • 函数描述:piclib_is_element_ok函数,该函数用于判断一个点是不是应该显示出来,在图片缩放的时候该函数是必须用到的。这里用__inline修饰,保证该部分的代码不被优化。​
  • 函数形参:无。​
  • 函数返回值:1:需要显示。​0:不需要显示。​其它函数使用到时,根据此返回值进行判定显示操作。​6piclib_ai_load_picfile函数piclib_ai_load_picfile帮助我们得到需要显示的图片信息并助于下一步的绘制。本函数需要结合文件系统来操作,图片根据后缀区分并且在文件夹在保存是我们在PC下的习分类,也是我们处理和分类图片的最方便的方式。​
/**​
智能画图​
图片仅在x,y和width, height限定的区域内显示.​
*​
包含路径的文件名(.bmp/.jpg/.jpeg/.gif等)​
起始坐标​
显示区域​
使能快速解码​
不使能​
使能​
图片尺寸小于等于液晶分辨率,才支持快速解码​
* @retval res : 操作结果0,成功其他,错误码​
*/​
uint8_t piclib_ai_load_picfile(const uint8_t *filename, uint16_t x, uint16_t y,​
uint16_t width, uint16_t height, uint8_t fast)​
{​
uint8_t res;/* 返回值 */​
uint8_t temp;​
if((x + width) > picinfo.lcdwidth)return PIC_WINDOW_ERR;/*x坐标超范围了 */​
if((y + height) > picinfo.lcdheight)return PIC_WINDOW_ERR;/*y坐标超范围了*/​
/* 得到显示方框大小 */​
if (width == 0 || height == 0)return PIC_WINDOW_ERR; /* 窗口设定错误 */​
picinfo.S_Height = height;​
picinfo.S_Width = width;​
/* 显示区域无效 */​
if (picinfo.S_Height == 0 || picinfo.S_Width == 0)​
{​
picinfo.S_Height = lcddev.height;​
picinfo.S_Width = lcddev.width;​
return FALSE;​
}​
if (pic_phy.fillcolor == NULL)fast = 0; /* 颜色填充函数未实现,不能快速显示 */​
/* 显示的开始坐标点 */​
picinfo.S_YOFF = y;​
picinfo.S_XOFF = x;​
/* 文件名传递 */​
temp = exfuns_file_type((uint8_t *)filename); /* 得到文件的类型 */​
switch (temp)​
{​
case T_BMP:​
res = stdbmp_decode(filename); /* 解码bmp */​
break;​
case T_JPG:​
case T_JPEG:​
res = jpg_decode(filename, fast); /* 解码JPG/JPEG */​
break;​
case T_GIF:​
res = gif_decode(filename, x, y, width, height); /* 解码gif */​
break;​
default:​
res = PIC_FORMAT_ERR; /* 非图片格式!!! */​
break;​
}​
return res;​
}
  • 函数描述:piclib_ai_load_picfile函数,整个图片显示的对外接口,外部程序,通过调用该函数,可以实现bmp、jpg/jpeg和gif的显示,该函数根据输入文件的后缀名,判断文件格式,然后交给相应的解码程序(bmp解码/jpeg解码/gif解码),执行解码,完成图片显示。​
  • 函数形参:1 filename是文件的路径名,具体可以参考FATFS一节的描述,为字符口,我们的例程采用的是SD卡存图片,故一般为”0:/PICTURE/*.GIF”等类似格式。​
    形参2为画图的起始x坐标;​
    3为画图的起始y坐标;​
    形参4的width和形参5的height形成了以x、y为起点的(x,y)~(x+width,y+height)的矩形显示区域,对屏幕坐标不理解的可能参考我们的TFT LCD一节的描述。​
    6根据我们的LCD进行适应的一个快速解的操作,仅jgp/jpeg模式下有效。​
    这里用到的exfuns_file_type()函数是我们前面FATFS一节提到的FATFS扩展应用,我们用这个函数来判断文件类型,方便我们进行程序设计。这部分参考文件系统下exfuns文件夹下的相关文件。​
  • 函数返回值:

0:成功 其他:错误码​

由于图片显示需要用到大内存,我们使用动态内存分配来实现,我们仍使用我们自定的内存管理函数来管理程序内存。申请内存函数piclib_mem_malloc()和内存释放函数piclib_mem_free()的实现就比较简单了,大家参考光盘的源码即可。​

2. main.c代码

main.c函数我们利用FATFS的接口来操作和查找图片文件,我们在microSD/SD卡的根目录下新建一个PICTURE文件夹,然后放置我们准备要显示的BMP、JPG、GIF图片,接下来按我们程序流程图设置的思路,先扫描图像文件的数量并切换显示,并加入按键支持图片翻页,主要的代码如下所示:​

int main(void)​
{​
uint8_t res;​
DIR picdir; /* 图片目录 */​
FILINFO *picfileinfo; /* 文件信息 */​
uint8_t *pname; /* 带路径的文件名 */​
uint16_t totpicnum; /* 图片文件总数 */​
uint16_t curindex; /* 图片当前索引 */​
uint8_t key; /* 键值 */​
uint8_t pause = 0; /* 暂停标记 */​
uint8_t t;​
uint16_t temp;​
uint32_t *picoffsettbl; /* 图片文件offset索引表 */​

sys_cache_enable(); /* 打开L1-Cache */​
HAL_Init(); /* 初始化HAL库 */​
sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */​
delay_init(480); /* 延时初始化 */​
usart_init(115200); /* 串口初始化为115200 */​
usmart_dev.init(240); /* 初始化USMART */​
mpu_memory_protection(); /* 保护相关存储区域 */​
led_init(); /* 初始化LED */​
lcd_init(); /* 初始化LCD */​
key_init(); /* 初始化按键 */​
my_mem_init(SRAMIN); /* 初始化内部内存池(AXI) */​
my_mem_init(SRAM12); /* 初始化SRAM12内存池(SRAM1+SRAM2) */​
my_mem_init(SRAM4); /* 初始化SRAM4内存池(SRAM4) */​
my_mem_init(SRAMDTCM); /* 初始化DTCM内存池(DTCM) */​
my_mem_init(SRAMITCM); /* 初始化ITCM内存池(ITCM) */​
exfuns_init(); /* 为fatfs相关变量申请内存 */​
f_mount(fs[0], "0:", 1); /* 挂载SD卡 */​
f_mount(fs[1], "1:", 1); /* 挂载FLASH */​
while (fonts_init()) /* 检查字库 */​
{​
lcd_show_string(30, 50, 200, 16, 16, "Font Error!", RED);​
delay_ms(200);​
lcd_fill(30, 50, 240, 66, WHITE); /* 清除显示 */​
delay_ms(200);​
}​
text_show_string(30, 50, 200, 16, "STM32", 16, 0, RED);​
text_show_string(30, 70, 200, 16, "图片显示 实验", 16, 0, RED);​
text_show_string(30, 90, 200, 16, "KEY0:NEXT KEY1:PREV", 16, 0, RED);​
text_show_string(30, 110, 200, 16, "KEY_UP:PAUSE", 16, 0, RED);​
text_show_string(30, 130, 200, 16, " ATOM@ALIENTEK", 16, 0, RED);​

while (f_opendir(&picdir, "0:/PICTURE")) /* 打开图片文件夹 */​
{​
text_show_string(30, 170, 240, 16, "PICTURE文件夹错误!", 16, 0, RED);​
delay_ms(200);​
lcd_fill(30, 170, 240, 186, WHITE); /* 清除显示 */​
delay_ms(200);​
}​

totpicnum = pic_get_tnum((uint8_t *)"0:/PICTURE"); /* 得到总有效文件数 */​

while (totpicnum == NULL) /* 图片文件为0 */​
{​
text_show_string(30, 170, 240, 16, "没有图片文件!", 16, 0, RED);​
delay_ms(200);​
lcd_fill(30, 170, 240, 186, WHITE); /* 清除显示 */​
delay_ms(200);​
}​

picfileinfo = (FILINFO *)mymalloc(SRAMIN, sizeof(FILINFO)); /* 申请内存 */​
pname = mymalloc(SRAMIN, FF_MAX_LFN * 2 + 1); /* 为带路径的文件名分配内存 */​
/* 申请4*totpicnum个字节的内存,用于存放图片索引 */​
picoffsettbl = mymalloc(SRAMIN, 4 * totpicnum);​

while (!picfileinfo || !pname || !picoffsettbl) /* 内存分配出错 */​
{​
text_show_string(30, 170, 240, 16, "内存分配失败!", 16, 0, RED);​
delay_ms(200);​
lcd_fill(30, 170, 240, 186, WHITE); /* 清除显示 */​
delay_ms(200);​
}​

/* 记录索引 */​
res = f_opendir(&picdir, "0:/PICTURE"); /* 打开目录 */​
if (res == FR_OK)​
{​
curindex = 0; /* 当前索引为0 */​

while (1) /* 全部查询一遍 */​
{​
temp = picdir.dptr; /* 记录当前dptr偏移 */​
res = f_readdir(&picdir, picfileinfo); /* 读取目录下的一个文件 */​
/* 错误了/到末尾了,退出 */​
if (res != FR_OK || picfileinfo->fname[0] == 0)break; ​

res = exfuns_file_type((uint8_t *)picfileinfo->fname);​
if ((res & 0XF0) == 0X50) /* 取高四位,看看是不是图片文件 */​
{​
picoffsettbl[curindex] = temp; /* 记录索引 */​
curindex++;​
}​
}​
}​
text_show_string(30, 150, 240, 16, "开始显示...", 16, 0, RED);​
delay_ms(1500);​
piclib_init(); /* 初始化画图 */​
curindex = 0; /* 从0开始显示 */​
res = f_opendir(&picdir, (const TCHAR *)"0:/PICTURE"); /* 打开目录 */​
while (res == FR_OK) /* 打开成功 */​
{​
dir_sdi(&picdir, picoffsettbl[curindex]); /* 改变当前目录索引 */​
res = f_readdir(&picdir, picfileinfo); /* 读取目录下的一个文件 */​
/* 错误了/到末尾了,退出 */​
if (res != FR_OK || picfileinfo->fname[0] == 0)break;​

strcpy((char *)pname, "0:/PICTURE/"); /* 复制路径(目录) */​
/* 将文件名接在后面 */​
strcat((char *)pname, (const char *)picfileinfo->fname); ​
lcd_clear(BLACK);​
/* 显示图片 */​
piclib_ai_load_picfile(pname, 0, 0, lcddev.width, lcddev.height, 1); ​
/* 显示图片名字 */​
text_show_string(2, 2, lcddev.width, 16, (char*)pname, 16, 1, RED); ​
t = 0;​
while (1)​
{​
key = key_scan(0); /* 扫描按键 */​

if (t > 250)key = 1; /* 模拟一次按下KEY0 */​

if ((t % 20) == 0)​
{​
LED0_TOGGLE(); /* LED0闪烁,提示程序正在运行. */​
}​

if (key == KEY1_PRES) /* 上一张 */​
{​
if (curindex)​
{​
curindex--;​
}​
else​
{​
curindex = totpicnum - 1;​
}​

break;​
}​
else if (key == KEY0_PRES) /* 下一张 */​
{​
curindex++;​
if (curindex >= totpicnum)curindex = 0;/* 到末尾的时候,自动从头开始 */​
break;​
}​
else if (key == WKUP_PRES)​
{​
pause = !pause;​
LED1(!pause); /* 暂停的时候LED1亮. */​
}​
if (pause == 0)t++;​
delay_ms(10);​
}​
res = 0;​
}​

myfree(SRAMIN, picfileinfo); /* 释放内存 */​
myfree(SRAMIN, pname); /* 释放内存 */​
myfree(SRAMIN, picoffsettbl); /* 释放内存 */​
}

可以看到整个设计思路是跟据图片解码库来设计的,piclib_ai_load_picfile()是这套代码的核心,其它的交互是围绕它和图片解码后的图片信息作的显示。大家再仔细对照光盘中的源码进一步了解整个设置思路。另外,我们的程序中只分配了4个文件索引,故更多数量的图片无法直接在本程序下演示,大家根据自己的需要再进行修改即可。​

48.4 下载验证

将程序下载到开发板后,可以看到LCD开始显示图片(假设SD卡及文件都准备好了,即:在SD卡根目录新建:PICTURE文件夹,并存放一些图片文件(.bmp/.jpg/.gif)在该文件夹内),如图48.4.1所示:​

《MiniPRO H750开发指南》第四十八章 图片显示实验_数据_02


图48.4.1图片显示实验显示效果​

按KEY0和KEY1可以快速切换到下一张或上一张,KEY_UP按键可以暂停自动播放,同时LED1亮,指示处于暂停状态,再按一次KEY_UP则继续播放。同时,由于我们的代码支持gif格式的图片显示(注意尺寸不能超过LCD屏幕尺寸),所以可以放一些gif图片到PICTURE文件夹,来看动画了。​

本章,同样可以通过USMART来测试该实验,将piclib_ai_load_picfile函数加入USMART控制(方法前面已经讲了很多次了),就可以通过串口调用该函数,在屏幕上任何区域显示任何你想要显示的图片了!同时,可以发送:runtime1,来开启USMART的函数执行时间统计功能,从而获取解码一张图片所需时间,方便验证。​

注意:本例程在支持AC6时,jpeg解码库中的函数容易被优化,故如果使用AC6进行本实验时,建议单独对其进行优化设置,MDK也支持对单一文件进行优化等级设置,操作方法如图48.4.2所示:​

《MiniPRO H750开发指南》第四十八章 图片显示实验_初始化_03


图48.4.2 对tjpgd.c进行单独的优化设置​

图片显示实验我们就讲解到这里。​


标签:MiniPRO,解码,GIF,uint16,函数,第四十八章,picinfo,H750,图片
From: https://blog.51cto.com/u_15046463/5748970

相关文章