JPEGDEC 在 M5Stack 系列设备上的 JPEG 解码显示实战:从回调绘制到 EXIF 缩略图与缩放
【免费下载链接】TasmotaAlternative firmware for ESP8266 and ESP32 based devices with easy configuration using webUI, OTA updates, automation using timers or rules, expandability and entirely local control over MQTT, HTTP, Serial or KNX. Full documentation at项目地址: https://gitcode.com/GitHub_Trending/ta/Tasmota
本指南以 JPEGDEC 库的 M5Stack 示例 README 为主体,完整讲解如何在 M5Stack、M5Fire、M5StickC、M5StickC Plus、M5Core2 五类设备上解码并显示 JPEG 图片,涵盖 JPEGDraw 回调机制、EXIF 缩略图自动检测、1/2、1/4、1/8 缩放解码以及多图片切换技巧。读完本文,你将掌握 JPEGDEC 在 M5Stack 生态中的移植要点,能够直接复刻这套示例到自己的工程中。
示例概览:一套代码适配五款 M5 设备
JPEGDEC 库在 examples/M5Stack 目录下提供了五个近乎相同的 Arduino 示例,分别对应五款设备:
| 目录 | 目标设备 | 头文件 | 屏幕分辨率(宏定义值) |
|---|---|---|---|
| M5Stack | M5Stack Gray / Basic | M5Stack.h | 320×240 |
| M5Fire | M5Stack Fire | M5Stack.h | 320×240 |
| M5StickC | M5StickC | M5StickC.h | 80×160 |
| M5StickCPlus | M5StickC Plus | M5StickCPlus.h | 135×240 |
| M5Core2 | M5Core2 | M5Core2.h | 320×240 |
据 README 说明,这些草图是 adafruit_gfx_demo 的"微改版"(slightly modified versions),核心改动集中在两点:一是将 JPEGDraw 回调改写为适配 M5Stack 设备的M5.Lcd.drawBitmap调用;二是增加缩略图检测,在图片带 EXIF 缩略图时调用对应的解码例程。其余结构与上游示例保持一致,因此迁移成本极低。
每个设备目录下都包含同一组素材头文件:thumb_test.h(含 EXIF 缩略图的测试图)、ncc1701.h(星舰企业号 NCC-1701)、batman.h(蝙蝠侠),以及对应的.ino主程序。
核心机制:JPEGDraw 回调与 drawBitmap 像素搬运
JPEGDEC 是典型的"解码核心 + 回调输出"架构:纯 C 的解码核心不关心你的屏幕是什么,每当解码出一个像素块(MCU)时,就通过回调函数把像素交给你,由你负责搬运到显示设备上。这正是它能够轻易适配 M5Stack 的原因。
以 M5Stack.ino 为例,回调实现极其简洁:
int JPEGDraw(JPEGDRAW *pDraw) { M5.Lcd.drawBitmap((int16_t)pDraw->x, (int16_t)pDraw->y, (int16_t)pDraw->iWidth, (int16_t)pDraw->iHeight, pDraw->pPixels); return 1; }这里的JPEGDRAW结构体由库定义(见 src/JPEGDEC.h),解码器每完成一个 MCU 块就填充一次并回调:
x、y:当前像素块在屏幕上的左上角坐标;iWidth、iHeight:该像素块的宽高;pPixels:指向 16 位像素数据的指针(默认 RGB565 小端序)。
回调返回 1 表示继续解码。M5.Lcd 底层基于 Adafruit GFX,drawBitmap直接按坐标把pPixels拷到显存,完成显示。从源码结构看,库的JPEGFILE与JPEGDRAW分离设计,让解码逻辑与显示逻辑彻底解耦,这也是示例只需改一个回调函数即可跨设备运行的底层原因。
调用链为:jpeg.openFLASH(...)打开图片 →jpeg.decode(x, y, option)触发解码 → 解码核心逐块回调JPEGDraw→drawBitmap上屏。
关键改动一:EXIF 缩略图检测与解码
示例的第二个改动是缩略图判断。测试图thumb_test是一张被截断处理、内含 320×240 EXIF 缩略图的 JPEG。README 与代码(M5Stack.ino)给出了统一处理逻辑:
if (jpeg.hasThumb()) { jpeg.decode(iCenterX[i], iCenterY[i], JPEG_EXIF_THUMBNAIL | iOption[i]); } else { jpeg.decode(iCenterX[i], iCenterY[i], iOption[i]); }hasThumb():查询解码器是否在 JPEG 文件头(APP1/EXIF 段)中发现了内嵌缩略图;JPEG_EXIF_THUMBNAIL(值为 32,定义于 src/JPEGDEC.h):指示解码器直接解码 EXIF 缩略图,而不是主图。
这个特性对相册类应用非常实用:很多数码相机、手机拍的 JPEG 都内嵌小尺寸 EXIF 缩略图,解码缩略图比解码全尺寸主图快得多,非常适合内存紧张的 MCU 设备做快速预览。库内部还维护了iThumbWidth、iThumbHeight、iThumbData等字段(见 JPEGIMAGE 结构体),配合getThumbWidth()、getThumbHeight()可以预先获知缩略图尺寸。
关键改动二:四种解码档位与缩放定位
主循环用四个档位演示了 JPEGDEC 的硬件无关快速降采样能力(M5Stack.ino):
int iOption[4] = {0, JPEG_SCALE_HALF, JPEG_SCALE_QUARTER, JPEG_SCALE_EIGHTH}; int iCenterX[4] = {0, 80, 120, 140}; // M5StickC/CPlus 中全部为 0 int iCenterY[4] = {0, 60, 90, 105}; // M5StickC/CPlus 中全部为 0四个档位对应 JPEGDEC.h 中的解码选项:
| 选项宏 | 值 | 含义 |
|---|---|---|
| (无) | 0 | 原始分辨率解码 |
JPEG_SCALE_HALF | 2 | 输出缩至 1/2 |
JPEG_SCALE_QUARTER | 4 | 输出缩至 1/4 |
JPEG_SCALE_EIGHTH | 8 | 输出缩至 1/8 |
缩放是在 IDCT 阶段直接丢弃高频分量完成的,比"全尺寸解码后再软件缩放"快得多,对无浮点、无硬件加速的 ESP32 尤其重要。循环中每档解码后,示例通过Serial.printf输出实际解码耗时与当前档位下的图片尺寸:
lTime = micros() - lTime; Serial.printf("%d x %d image, decode time = %d us\n", jpeg.getWidth() >> i, jpeg.getHeight() >> i, (int)lTime);jpeg.getWidth() >> i巧妙地用右移计算缩放后的宽(1/2→>>1、1/4→>>2、1/8→>>3),可以在串口监视器(115200 波特率)上直观对比各档位耗时,是评估解码性能的最小可行方案。
注意 M5StickC / M5StickC Plus 的示例中iCenterX、iCenterY全部为 0(见 M5StickC.ino),因为小屏幕无需多位置演示,统一从原点显示即可。
切换显示图片:取消注释即换图
每个示例默认只解码thumb_test,其余两张图以注释形式保留(M5Stack.ino):
if (jpeg.openFLASH((uint8_t *)thumb_test, sizeof(thumb_test), JPEGDraw)) //if (jpeg.openFLASH((uint8_t *)ncc1701, sizeof(ncc1701), JPEGDraw)) //if (jpeg.openFLASH((uint8_t *)batman, sizeof(batman), JPEGDraw))按 README 的指引,切换图片只需两步:把想显示的#include取消注释、把另外两个注释掉;同时把openFLASH中对应的图片符号取消注释并注释其他两个。例如想显示蝙蝠侠:
//#include "thumb_test.h" //#include "ncc1701.h" #include "batman.h" ... if (jpeg.openFLASH((uint8_t *)batman, sizeof(batman), JPEGDraw))openFLASH与openRAM的区别要特别注意:ESP32 属于哈佛架构,代码与数据存储于 Flash,openFLASH接受const uint8_t *并保证从 Flash 直接读取;如果图片数组放在 RAM 中则应使用openRAM(两个方法均声明于 src/JPEGDEC.h)。对于 ARM Cortex-M 系列两者可互换,但 ESP32 上选错会导致读取异常。
各设备差异详解
README 明确指出五个示例"几乎相同",差异只在屏幕初始化和头文件:
- M5Stack 与 M5StickC/CPlus:使用
M5.begin()(如 M5Stack.ino); - M5Fire:使用
M5.Lcd.begin()(见 M5Fire.ino),因为 Fire 的屏幕初始化方式与其他型号不同; - M5Core2:使用带参数的
M5.begin(true, true, true, true)(见 M5Core2.ino),依次启用电源、LCD、触摸等外设; - M5StickC / M5StickC Plus:头文件分别为
M5StickC.h和M5StickCPlus.h,屏幕分辨率宏也不同(80×160 与 135×240)。
所有示例统一执行M5.Lcd.startWrite()后再解码(如 M5Stack.ino)。这是 Adafruit GFX 的"事务"机制:一次startWrite到endWrite之间的绘制调用会复用同一组 SPI 片选与总线设置,避免每块像素都重复切换 CS,从而显著提升连续drawBitmap的吞吐。
素材准备:JPEG 转 C 数组
thumb_test.h、ncc1701.h、batman.h本质上是把 JPEG 二进制字节转成了 C 数组,每字节写作0xAB形式,并加PROGMEM修饰符(见 thumb_test.h 的声明const uint8_t thumb_test[] PROGMEM = {...}),确保数据被链接器放入 Flash 而非 RAM——这对仅数百 KB RAM 的 ESP32 至关重要。
配套的原始素材图片位于 examples/M5Stack 目录下:ncc1701.jpg(240×77,星舰企业号 NCC-1701 剪影)与batman.jpg(240×240,蝙蝠侠图)。把任意 JPEG 转成这种头文件后,即可用同样的openFLASH方式显示自己的图片。
兼容性清单:已测试与预期可用的设备
README 提供了明确的验证状态:
- 已实测通过:M5Stack Gray、M5Stack Fire、M5StickC、M5StickC Plus、M5Core2;
- 未实测、预期可用:M5Stack Basic、M5Go Lite、M5Go——作者说明这些设备应与 M5Stack 兼容,但因手头无实物未能验证。
示例背后:JPEGDEC 库的移植要点
把示例逻辑推广到自己的 M5Stack 工程时,可直接复用 src/JPEGDEC.h 暴露的完整 API 面:
- 数据源:内存图用
openRAM/openFLASH;文件流可用open(File &file, ...)(需包含 FS.h)或自定义 open/close/read/seek 四回调的通用open; - 解码:
decode(x, y, iOptions)支持与JPEG_AUTO_ROTATE(自动旋转,依据 EXIF 方向)、JPEG_LE_PIXELS、JPEG_LUMA_ONLY(灰度输出)、JPEG_USES_DMA等按位或组合; - 信息查询:
getWidth()、getHeight()、getBpp()、getOrientation()、getLastError()等用于解码前后获取元数据与错误码; - 进阶:
setCropArea()支持裁剪解码,decodeDither()可做 1/2/4-bit Floyd-Steinberg 抖动灰度输出(适合墨水屏)。
结合 Tasmota 仓库中该库作为 ESP32 音频与显示相关功能依赖被引入的背景(位于 lib/libesp32/JPEGDEC),这套 M5Stack 示例展示了它在小型彩屏设备上的典型落地方式:无需浮点运算、内存占用低、通过回调与屏幕解耦,是 ESP32 系列 MCU 上显示 JPEG 的高性价比方案。
【免费下载链接】TasmotaAlternative firmware for ESP8266 and ESP32 based devices with easy configuration using webUI, OTA updates, automation using timers or rules, expandability and entirely local control over MQTT, HTTP, Serial or KNX. Full documentation at项目地址: https://gitcode.com/GitHub_Trending/ta/Tasmota
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考