前面文章我们已经介绍了,如何点亮LCD屏幕、在屏幕上显示ASCII字符和中文,但是在实际开发产品的时候,很多时候需要显示图片,所以本篇文章将介绍如何解码存储在SD卡中的BMP/JPG/JPEG/PNG/GIF等图片,并在LCD上显示出来。一般最常用的图片格式有:BMP、JPEG(或 JPG)、 PNG 和 GIF。其中 PNG、JPEG(或JPG)和BMP是静态图片,而GIF则是可以实现动态图片。下面,简单介绍一下这几种图片格式。BMP(全称Bitmap)是Window操作系统中的标准图像文件格式,文件后缀名为“.bmp”,使用非常广。它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此,BMP文件所占用的空间很大,但是没有失真。BMP文件的图像深度可选1bit、4bit、8bit、16bit、24bit及 32bit。BMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。①:位图头文件数据结构,它包含BMP图像文件的类型、显示内容等信息; ②:位图信息数据结构,它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息 ③:调色板,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24位 的BMP)就不需要调色板; ④:位图数据,这部分的内容根据 BMP位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜色索引值。 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 的解码过程可以简单的概述为如下几个部分:JPEG 文件数据分为文件头和图像数据两大部分,其中文件头记录了图像的版本、长宽、采 样因子、量化表、哈夫曼表等重要信息。所以解码前必须将文件头信息读出,以备图像数据解 码过程之用。②:从图像数据流读取一个最小编码单元(MCU),并提取出里边的各个颜色分量单元。 使用文件头给出的哈夫曼表,对分割出来的颜色分量单元进行解码,把其恢复成8×8的数据矩阵。 此部分解码工作以8×8的数据矩阵为单位,其中包括相邻矩阵的直流系数差分解码、使用文件头给出的量化表反量化数据、反Zig-zag编码、隔行正负纠正、反向离散余弦变换等5个步 骤,最终输出仍然是一个8×8的数据矩阵。 将一个MCU的各个颜色分量单元解码结果整合起来,将图像颜色系统从YCrCb向RGB转换。 不断读取数据流中的MCU并对其解码,直至读完所有MCU为止,将各MCU解码后的数据正确排列成完整的图像。JPEG的解码本身是比较复杂的,这里FATFS的作者,提供了一个轻量级的 JPG/JPEG 解码库:TjpgDec,最少仅需 3KB 的 RAM 和 3.5KB 的 FLASH 即可实现 JPG/JPEG 解码,本例程采用 TjpgDec作为 JPG/JPEG 的解码库。PNG(Portable Network Graphics)是一种无损的位图图像格式,旨在替代GIF 格式并增加 一些GIF文件格式所不具备的特性。PNG图像使用一种称为DEFLATE的无损数据压缩算法来减小文件大小,不会损失图像质量。这种压缩算法结合了LZ77算法和哈夫曼编码,能够有效地压缩数据并减小文件大小。 在 PNG 编码过程中,预滤器编码格式被用来先对图像数据进行预处理,以便更好地利用 Deflate 算法进行压缩。PNG 定义了五种不同的预滤器,分别是 None、Sub、Up、Average 和 Paeth。这些预滤器根据图像像素的特性对每一行的像素进行编码,以更好地利用Deflate算法进行压缩。PNG还支持多种颜色模式,包括8位灰度图像、索引彩色图像和24位真彩色图像,并且可以支持 Alpha 通道透明度,这意味着可以在图像中创建半透明的效果。此外,PNG还支持多层图像,可以将多个图像组合在一起,每个图像可以具有自己的透明度和颜色。 ①:文件署名域(File Signature):这是文件的开头部分,由 8 个字节组成,用于标识该文件是一个PNG文件。其值固定为"89 50 4E 47 0D 0A 1A 0A"。 ②:关键数据块(Critical Chunk):这是PNG文件必须包含的数据块,包括IHDR、IDAT、 IEND等。IHDR块包含了图像的基本信息,如宽度、高度、像素格式等;IDAT块包含了图像的实际数据;IEND块标记了图像数据的结束。 ③:辅助数据块(Ancillary Chunk):这是可选的数据块,用于存储与图像相关的其他信息, 如文本注释、时间戳等。这些数据块对于解码图像是可选的,但如果存在,解码器应当对其进行解析。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 的字符 (';')表示文件结束。Demo程序在开机的时候先检测字库,然后检测SD卡是否存在,如果SD卡存在,则开始查找SD卡根目录下的PICTURE文件夹,如果找到则显示该文件夹下面的图片文件(支持bmp、jpg、jpeg、 png 格式),循环显示,通过按K1和K2可以快速浏览下一张和上一张,BO按键用于暂停/继续 播放。如果未找到 PICTURE文件夹或者文件夹中没有任何图片文件,则提示错误。需要准备不同格式的图片文件,格式支持bmp,jpg/jpeg,png和gif,也可以使用源代码PICTURE目录下的文件。为确保程序能正常读取到图片文件并显示在LCD屏幕上,请将准备好的图片文件,完整拷贝到 SD 卡根目录下的 PICTURE文件夹中。依赖:BSP/LCD,Middlewares/MYFATFS。由于项目代码量比较大,不太好贴出全部代码,可先下载源代码查看解析,代码地址:https://github.com/bkssxzdwy/esp32s3_samples/tree/main/examples/mcu_lcd_picture_project程序基于上一篇文章源代码,主要增加Middlewares/PICTURE和Middlewares/MYFATFS模块。PICTURE模块是解析和显示图片的核心代码,代码结构如下所示:bmp.c和 bmp.h用于实现对bmp文件的解码;gif.c和gif.h用于实现对gif文件的解码;jpeg.c、jpeg.h、tjpgd.c和tjpgd.h用于实现对jpeg/jpg文件的解码; png.c、png.h、pngle.c和pngle.h用于实现对png文件的解码; picture.c和picture.h是整个图片解码和显示的应用程序掉用主入口,主要包括初始化和显示图片函数,如下所示:voidpicture_init();uint8_tpicture_show(char *file_name, uint16_t x, uint16_t y, uint16_t width, uint16_t height);
上面这几个文件的代码太长了,而且也有规定的标准,结合上面介绍的各个图片编码的格式来编写,所以在这里不贴出来,可自行查看源码理解。MYFATFS模块下主要增加fstools.c和fstool.h类,主要方法如下:/*初始化*/uint8_tfstools_init(void);/*识别文件类型*/uint8_tfstools_file_type(char *fname); /*获取Path路径下,文件总个数*/uint16_tfstools_get_file_count(char *path); /*按偏移 seek 目录项的函数*//*把已打开目录的读指针直接跳到指定字节偏移 ofs,下次 f_readdir 就读到那条目录项。*//*对应 FatFs 源码里的内部函数 dir_sdi(directory seek)*/FRESULT fstools_dir_sdi(FF_DIR *dp,DWORD ofs);
其他文件由于篇幅就不多讲解,大家可以直接查看源代码,其中有详细的注释分析。至此,我们已经可以点亮LCD显示屏幕,并在上面显示文字、中文和图片了。下一篇文章将分享如何在LCD显示屏幕上实现触摸操作,敬请期待!谢谢!