当前位置: 首页 > news >正文

第四十章 ESP32S3 图片显示实验

        在开发产品的时候,很多时候,都会用到图片解码,本章将学习何通过 ESP32-S3 来解码 BMP/JPG/JPEG/PNG/GIF 等图片,并在 SPILCD 上显示出来。
本章分为如下几个小节:
40.1 图片格式简介
40.2 硬件设计
40.3 程序设计
40.4 下载验证

40.1 图片格式介绍

        常用的图片格式有很多,一般最常用的有四种: JPEG(或JPG)、BMP、PNG和GIF。其中 JPEG(或 JPG)、 PNG 和 BMP 是静态图片,而 GIF 则是可以实现动态图片。下面对比这四种图片格式。

格式

全称

压缩类型

色彩支持

核心特点

主要用途

​JPEG/JPG​

Joint Photographic Experts Group

​有损​​压缩

真彩色(24位)

​文件小​​,压缩率高,有损

​照片、复杂图像​

​PNG​

Portable Network Graphics

​无损​​压缩

调色板/真彩色+Alpha通道

​支持透明背景​​,无损

​网页图形、图标、UI元素​

​GIF​

Graphics Interchange Format

​无损​​压缩(索引色)

索引色(最多256色)

​支持简单动画​​,文件小

​简单动画、表情包​

​BMP​

Bitmap

​通常无压缩​

调色板/真彩色

​无压缩​​,​​质量最高​​,文件大

​临时存储、简单图标​

表40.1.1 JPEG(或JPG)、BMP、PNG和GIF对比

        下面介绍四种图片:

 (1)JPEG / JPG (联合图像专家小组格式)

        核心原理​​:​​有损压缩​​。其编码设计基于人眼视觉特性(对亮度敏感,对颜色细节不敏感)。​​色彩空间转换​​:将图像从RGB颜色空间转换为YCbCr颜色空间(Y代表亮度,Cb和Cr代表色度)。

  • ​​下采样(Chroma Subsampling)​​:减少色度通道(Cb, Cr)的分辨率(例如,从4:4:4变为4:2:0),这是主要压缩来源之一,人眼几乎察觉不到;
  • ​​分块与DCT​​:将图像分成8x8的小块,对每个块进行​​离散余弦变换(DCT)​​,将像素信息转换为频率信息;
  • ​​量化​​:用一个“量化表”去除高频分量(人眼不敏感的细节),这是​​有损压缩的关键步骤​​,质量等级就是控制这个表的强度;
  • ​​编码​​:对量化后的数据进行Zig-Zag扫描,然后使用​​霍夫曼编码​​进行熵编码,进一步压缩数据。

        ​​特点​​:
​​优点​​:极高的压缩率,文件体积小,非常适合存储照片和色彩丰富的图片。
​​缺点​​:不适合存储线条、文字或图标(会有明显的​​伪影​​),不支持透明背景。
 ​​适用场景​​:数码照片、网络图片、需要小体积文件的复杂图像。​

(2)PNG (便携式网络图形)

​​        核心原理​​:​​无损压缩​​。旨在替代GIF和专利的TIFF格式。

  • ​​过滤​​:对每一行像素数据进行预测(Filter),找出像素间的最佳差分关系,便于后续压缩;
  • ​​压缩​​:使用​​DEFLATE​​算法(与ZIP文件使用的算法相同)进行压缩,结合LZ77和霍夫曼编码。这个过程是​​无损的​​。

​​        特点​​:
​​        优点​​:

  • ​​无损压缩​​:图像质量没有任何损失。
  • ​​支持Alpha透明通道​​:可以实现边缘平滑的半透明效果,这是相比GIF的巨大优势。
  • 支持真彩色(24位)和调色板颜色。

​​        缺点​​:文件体积比JPEG大,尤其是对于照片类图像。
​​        适用场景​​:​​网页Logo、图标、UI元素、需要透明背景或高质量线条的图形。​​

(3)GIF (图形交换格式)

​​        核心原理​​:​​基于调色板的无损压缩​​(LZW算法)。

  • ​​索引颜色​​:将图像颜色限制在最多256色。首先为图像创建一个全局或局部调色板。
  • ​​LZW压缩​​:对索引后的颜色值进行LZW编码,寻找重复模式并进行压缩。

        ​​特点​​:
​​优点​​:

  • 支持​​动画​​。
  • 文件体积小(对于颜色简单的图像)。
  • 支持简单的布尔透明(一个像素要么完全透明,要么完全不透明)。

​​        缺点​​:

  • 仅支持256色,不适合存储照片或色彩丰富的图片,会出现​​颜色 banding​​。
  • 透明效果粗糙,没有半透明过渡。
  • ​​适用场景​​:​​简单动画、表情包、颜色单一的简单图形。​​

(4)BMP (位图)

        ​​核心原理​​:​​几乎无压缩​​(也可以选择简单的RLE压缩,但不常用)。它是最简单的图像格式之一,直接存储每个像素的颜色值。

  • ​​文件头​​:包含图像的基本信息(如大小、宽度、高度、色深)。
  • ​​调色板​​(对于256色及以下的图像):定义使用的颜色。
  • ​​像素数据​​:按照从下到上、从左到右的顺序,直接记录每个像素的RGB值。

​​        特点​​:
​​        优点​​:格式简单,易于程序读写,无损存储,显示速度快。
​​缺点​​:文件体积非常大,因为几乎没有压缩。
​​适用场景​​:​​临时存储、操作系统壁纸、屏幕截图、对体积无要求的简单应用。​

40.2 硬件设计

40.2.1 例程功能
开机的时候先检测字库,然后检测 SD 卡是否存在,如果 SD 卡存在,则开始查找 SD 卡根目录下的 PICTURE 文件夹,如果找到则显示该文件夹下面的图片文件(支持 bmp、 jpg、 jpeg、png 和 gif 格式),循环显示,通过按 KEY0和 KEY2可以快速浏览下一张和上一张, KEY_UP 按键用于暂停/继续播放, DS1 用于指示当前是否处于暂停状态。如果未找到 PICTURE 文件夹/任何图片文件,则提示错误。同样我们也是用 DS0 来指示程序正在运行。

40.2.2 硬件资源
1.LED
LED-IO1
2.XL9555
IIC_SDA-IO41
IIC_SCL-IO42
3.SPILCD
CS-IO21
SCK-IO12
SDA-IO11
DC-IO40(在 P5 端口,使用跳线帽将 IO_SET 和 LCD_DC 相连)
PWR- IO1_3(XL9555)
RST- IO1_2(XL9555)
4.SD
CS-IO2
SCK-IO12
MOSI-IO11
MISO-IO13

40.3 程序设计

40.3.1 程序流程图
本实验的程序流程图:

图 40.3.1.1 图片显示实验程序流程图

40.3.2 图片显示函数解析

        原子提供的 PICTURE 驱动源码包括以下文件,并且已经针对正点原子 ESP32-S3 软硬件进行了移植适配,在使用时,仅需将这以下文件添加到自己的工程中即可,如下图所示:

图 40.3.2.1 正点原子 PICTURE 驱动源码文件

其中:
bmp.c 和 bmp.h 用于实现对 bmp 文件的解码;
tjpgd.c 和 tjpgd.h 用于实现对 jpeg/jpg 文件的解码;
gif.c 和 gif.h 用于实现对 gif 文件的解码;
代码太长,而且也有规定的标准, 需要结合各个图片编码的格式来编写,具体查看源码的实现过程即可。

40.3.3 图片显示函数驱动解析

        在 IDF 版StandardExampleIDF(v5.3.x)\29_pitures例程中,具体实现在该目录的PICTURE目录下,StandardExampleIDF(v5.3.x)\29_pitures\components\Middlewares。

(1)解码库的控制句柄_pic_phy 和_pic_info

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

#define rgb565(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3))/* 图片显示物理层接口 */
/* 在移植的时候,必须由用户自己实现这几个函数 */
typedef struct
{/* void draw_point(uint16_t x,uint16_t y,uint32_t color) 画点函数 */void(*draw_point)(uint16_t, uint16_t, uint16_t);/* void fill(uint16_t sx,uint16_t sy,uint16_t ex,uint16_t ey,uint32_t color) 单色填充函数 */void(*fill)(uint16_t, uint16_t, uint16_t, uint16_t, uint16_t);/* void draw_hline(uint16_t x0,uint16_t y0,uint16_t len,uint16_t color) 画水平线函数 */void(*draw_hline)(uint16_t, uint16_t, uint16_t, uint16_t);/* void piclib_multi_color(uint16_t x, uint16_t y, uint16_t width, uint16_t *color) 多点填充 */void(*multicolor)(uint16_t, uint16_t, uint16_t, uint16_t *);
} _pic_phy;extern _pic_phy pic_phy;/* 图像信息 */
typedef struct
{uint16_t lcdwidth;      /* LCD的宽度 */uint16_t lcdheight;     /* LCD的高度 */
} _pic_info;extern _pic_info picinfo;   /* 图像信息 */

        在 piclib.c 文件中,我们用上述类型定义了两个结构体,声明如下:

_pic_info picinfo; /* 图片信息 */
_pic_phy pic_phy; /* 图片显示物理接口 */

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

/*** @brief       画图初始化*   @note      在画图之前,必须先调用此函数, 指定相关函数* @param       无* @retval      无*/
void piclib_init(void)
{pic_phy.draw_point = spilcd_draw_point;         /* 画点函数实现,仅GIF需要 */pic_phy.fill = spilcd_fill;                     /* 填充函数实现,仅GIF需要 */pic_phy.draw_hline = spilcd_draw_hline;         /* 画线函数实现,仅GIF需要 */pic_phy.multicolor = piclib_multi_color;        /* 颜色填充函数实现,JPEG、BMP、PNG需要 */picinfo.lcdwidth = spilcddev.width;               /* 得到LCD的宽度像素 */picinfo.lcdheight = spilcddev.height;             /* 得到LCD的高度像素 */
}

        初始化图片解码的相关信息,这些函数必须由用户在外部实现。使用之前 LCD 的操作函数对这个结构体中的绘制操作:画点、画线、画圆等定义与我们的 LCD 操作对应起来。具体这些操作可以查看 SPILCD 一节的描述。

(3)piclib_ai_load_picfile 函数
piclib_ai_load_picfile 得到需要显示的图片信息,并助于下一步的绘制。本函数需要结合文件系统来操作,图片根据后缀区分。

/*** @brief       智能画图*   @note      图片仅在x,y和width, height限定的区域内显示.** @param       filename      : 包含路径的文件名(.bmp/.jpg/.jpeg/.gif等)* @param       x, y          : 起始坐标* @param       width, height : 显示区域* @param       fast          : 使能快速解码*   @arg                       0, 不使能*   @arg                       1, 使能*   @note                      图片尺寸小于等于液晶分辨率,才支持快速解码* @retval      无*/
uint8_t piclib_ai_load_picfile(char *filename, uint16_t x, uint16_t y, uint16_t width, uint16_t height)
{uint8_t	res = 0;/* 返回值 */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;        /* 窗口设定错误 *//* 文件名传递 */temp = exfuns_file_type(filename);   /* 得到文件的类型 */ESP_LOGI("here","temp:%#x ", temp);switch (temp){case T_BMP:ESP_LOGI("here","enter");res = bmp_decode(filename,width, height);           /* 解码BMP */break;case T_JPG:case T_JPEG:res = jpeg_decode(filename,width, height);          /* 解码JPG/JPEG */break;case T_GIF:res = gif_decode(filename, x, y, width, height);    /* 解码gif */break;case T_PNG:res = png_decode(filename, width, height);          /* 解码PNG */break;default:res = PIC_FORMAT_ERR;                               /* 非图片格式!!! */break;}return res;
}

        piclib_ai_load_picfile 函数,整个图片显示的对外接口,外部程序,通过调用该函数,可以实现 bmp、 jpg/jpeg、 png 和 gif 的显示,该函数根据输入文件的后缀名,判断文件格式,然后交给相应的解码程序(bmp 解码/jpeg 解码/gif 解码/png 解码),执行解码,完成图片显示。
这里用到的 exfuns_file_type()函数是用来判断文件类型。由于图片显示需要用到大内存,使用动态内存分配来实现,仍使用自定的内存管理函数来管理程序内存。申请内存函数piclib_mem_malloc()和内存释放函数 piclib_mem_free()的实现就比较简单。

40.3.4 CMakeLists.txt 文件     

        打开本实验 BSP 下的 CMakeLists.txt 文件,其内容如下所示:   

set(src_dirsLEDKEYMYIICXL9555MYSPISPILCDSPI_SD)set(include_dirsLEDKEYMYIICXL9555MYSPISPILCDSPI_SD)set(requiresdriveresp_lcdfatfs)idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)

        打开本实验 Middlewares 文件下的 CMakeLists.txt 文件,其内容如下所示:

list(APPEND srcs    fonts.ctext.c)idf_component_register(SRCS "${srcs}"INCLUDE_DIRS "."REQUIRES esp_partitionspi_flashfatfsBSP)

40.3.5 实验应用代码

        打开 main/main.c 文件,该文件定义了工程入口函数,名为 app_main。该函数代码如下。

/*** @brief       程序入口* @param       无* @retval      无*/
void app_main(void)
{esp_err_t ret;FF_DIR picdir; FILINFO *picfileinfo;char *pname;uint16_t totpicnum;uint16_t curindex = 0;uint8_t key = 0; uint8_t t;uint16_t temp;uint32_t *picoffsettbl;ret = nvs_flash_init();if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND){ESP_ERROR_CHECK(nvs_flash_erase());ESP_ERROR_CHECK(nvs_flash_init());}led_init(); my_spi_init();key_init();myiic_init();xl9555_init();spilcd_init();while (sd_spi_init()){spilcd_show_string(30, 110, 200, 16, 16, "SD Card Error!", RED);vTaskDelay(pdMS_TO_TICKS(500));spilcd_show_string(30, 130, 200, 16, 16, "Please Check! ", RED);vTaskDelay(pdMS_TO_TICKS(500));}ret = exfuns_init();while (fonts_init()){spilcd_clear(WHITE);spilcd_show_string(30, 30, 200, 16, 16, "ESP32-S3", RED);key = fonts_update_font(30, 50, 16, (uint8_t *)"0:", RED);while (key){spilcd_show_string(30, 50, 200, 16, 16, "Font Update Failed!", RED);vTaskDelay(pdMS_TO_TICKS(200));spilcd_fill(20, 50, 200 + 20, 90 + 16, WHITE);vTaskDelay(pdMS_TO_TICKS(200));}spilcd_show_string(30, 50, 200, 16, 16, "Font Update Success!   ", RED);vTaskDelay(pdMS_TO_TICKS(1000));spilcd_clear(WHITE); }text_show_string(30, 50, 200, 16, "正点原子ESP32-S3开发板",16,0, RED);text_show_string(30, 70, 200, 16, "图片显示 实验", 16, 0, RED);text_show_string(30, 90, 200, 16, "正点原子@ALIENTEK", 16, 0, RED);text_show_string(30, 110, 200, 16, "KEY0:NEXT KEY1:PREV", 16, 0, RED);text_show_string(30, 130, 200, 16, "KEY_UP:PAUSE:", 16, 0, RED);while (f_opendir(&picdir, "0:/PICTURE")) {text_show_string(30, 150, 240, 16, "PICTURE文件夹错误!", 16, 0, RED);vTaskDelay(pdMS_TO_TICKS(200));spilcd_fill(30, 150, 240, 186, WHITE);vTaskDelay(pdMS_TO_TICKS(200));}totpicnum = pic_get_tnum("0:/PICTURE");while (totpicnum == 0) {text_show_string(30, 150, 240, 16, "没有图片文件", 16, 0, RED);vTaskDelay(pdMS_TO_TICKS(200));spilcd_fill(30, 150, 240, 186, WHITE);vTaskDelay(pdMS_TO_TICKS(200));}picfileinfo = (FILINFO *)malloc(sizeof(FILINFO));pname = malloc(255 * 2 + 1);picoffsettbl = malloc(4 * totpicnum);while (!picfileinfo || !pname || !picoffsettbl){text_show_string(30, 150, 240, 16, "没有图片文件!", 16, 0, RED);vTaskDelay(pdMS_TO_TICKS(200));spilcd_fill(30, 150, 240, 186, WHITE);vTaskDelay(pdMS_TO_TICKS(200));}ret = f_opendir(&picdir, "0:/PICTURE");if (ret == FR_OK){curindex = 0;while (1){temp = picdir.dptr;ret = f_readdir(&picdir, picfileinfo); if (ret != FR_OK || picfileinfo->fname[0] == 0) break;ret = exfuns_file_type(picfileinfo->fname);if ((ret & 0X0F) != 0X00){picoffsettbl[curindex] = temp; curindex++;}}ESP_LOGI("main", "0:/PICTURE pic_num:%d", curindex);}text_show_string(30, 150, 240, 16, "图片开始显示......", 16, 0, RED);vTaskDelay(pdMS_TO_TICKS(1000));piclib_init();curindex = 0; ret = f_opendir(&picdir, (const TCHAR *)"0:/PICTURE");while (ret == FR_OK)                                                        {atk_dir_sdi(&picdir, picoffsettbl[curindex]);ret = f_readdir(&picdir, picfileinfo);                                  //显示一轮,重新循环if (ret != FR_OK || picfileinfo->fname[0] == 0){picdir.dptr = picoffsettbl[curindex];ret = f_readdir(&picdir, picfileinfo);                              if (ret != FR_OK || picfileinfo->fname[0] == 0){ESP_LOGE(__FUNCTION__, "Read Failed");break;                                                          }}strcpy((char *)pname, "0:/PICTURE/");                                   strcat((char *)pname, (const char *)picfileinfo->fname);                spilcd_clear(BLACK);piclib_ai_load_picfile(pname, 0, 0, spilcddev.width, spilcddev.height);       text_show_string(2, 2, spilcddev.width, 16, (char *)pname, 16, 0, RED);    t = 0;while (1){t ++;key = xl9555_key_scan(0);if (t > 250) key = KEY0_PRES;if ((t % 20) == 0){LED0_TOGGLE();}if (key == KEY1_PRES){if (curindex){curindex--;}else{curindex = totpicnum - 1;}break;}else if (key == KEY0_PRES){curindex++;if (curindex >= totpicnum){curindex = 0;}break;}vTaskDelay(pdMS_TO_TICKS(10));}ret = 0;}free(picfileinfo);free(pname);free(picoffsettbl);
}

        整个设计思路是跟据图片解码库来设计的, piclib_ai_load_picfile()是这套代码的核心,其它的交互是围绕它和图片解码后的图片信息作的显示。具体实现可以自行分析。另外,程序中只分配了 4 个文件索引,大于4个文件需要单独适配。

40.4 下载验证

        代码编译成功,下载代码到开发板上,可以看到 LCD 开始显示图片(假设 SD卡及文件都准备好了,即:在 SD 卡根目录新建: PICTURE 文件夹,并存放一些图片文件(.bmp/.jpg/.gif/.png)在该文件夹内),如图 40.4.1 所示:

图 40.4.1 图片显示实验显示效果

        按 KEY0 和 KEY2 可以快速切换到下一张或上一张, KEY_UP 按键可以暂停自动播放,同时 DS1亮,指示处于暂停状态,再按一次 KEY_UP则继续播放。

http://www.dtcms.com/a/447195.html

相关文章:

  • 网站建设收费分几次汕头网站设计价格
  • 各大网站代下单怎么做延吉市建设厅网站
  • 基于51单片机步数检测计步器无线蓝牙APP上传设计
  • 网站制作邯郸平台网站建设哪家好
  • 微专题:C++中的进制转换
  • 【多线程】多线程的底层实现
  • 台州网站建设公司哪个好互联网销售公司起名
  • 网站可以同时做竞价和优化吗网站建设的优势与不足
  • 做网站怎么去文化局备案phpstudy和wordpress
  • 网站嵌套代码wordpress 文章 指定
  • 什么是速成网站3d建模培训学校哪家好
  • 做律师网站导购网站怎么建设
  • 佛山优化网站排名福建省港航建设发展有限公司网站
  • 上杭网站设计公司安阳市有几个区几个县
  • XGBoost工业级痛点解决:样本不平衡+大数据优化+部署落地
  • CCF-CSP认证考试 202312-4 宝藏 题解
  • 个人网站备案号被注销了网站运营优化推广
  • Python数据清洗实战指南
  • s网站优化西安咪豆网站建设公司
  • 怀柔建设网站公司公司网站后台维护怎么做
  • 网站开发 ssh 菜鸟东阳网络科技有限公司
  • 宁波网站建设费用网站运营岗位介绍
  • 公司的网 网站打不开怎么办网络营销宏观环境有哪些
  • 网站建设中扁平化结构chrome浏览器官网入口
  • 网站建设属于什么合同做网站游戏推广赚钱
  • asrpro2.0天问语音模块搭配STM32(STM32F103c8t6)-杨桃电子开发板
  • 网站发布时间更改wordpress大前端哪个好
  • php租车网站网站软件下载大全
  • LangChain 学习 - LangChain 引入(LangChain 概述、LangChain 的使用场景、LangChain 架构设计)
  • 门户网站是网络表达吗杭州建设网电焊工报名入口