把图片或视频批量转成 OLED 屏幕用的 C 语言字模数组,一键生成可直接烧录到 MCU 的
.c/.h文件。 同时附带一份已参数化的 SSD1306 风格 C 驱动 (oled/OLED.c),可裁剪到任何 8 的倍数屏幕尺寸。
- 图像 → C 字模:把一个文件夹里的所有 128×64 (或自定义 W×H) PNG/JPG/BMP 一键转成
IMG_DATA1[]、IMG_DATA2[]、... 字节数组,并生成一个配套gif()函数自动按幻灯片间隔播放。 - 视频 → 图像 → C 字模:从 mp4 / webm / mkv / avi / flv / wmv / m4v / 3gp / ts / m2ts / mpg / ogv / vob / rm 等任意 FFmpeg 支持的容器里按帧间隔抽帧,再走相同的转换流程。
- 可调参数:亮度、对比度、旋转 0/90/180/270°、水平/垂直镜像、反色、扫描方向(vertical/horizontal)、8 种扫描顺序。
- 自定义屏幕尺寸:W、H 可设到 16384 (16K 屏幕字面含义);非 8 的倍数自动向上 padding 并在生成代码里写明。
- C 驱动
#define可配置:oled/OLED.h改 3 个尺寸宏 (OLED_WIDTH/OLED_HEIGHT/OLED_PAGES) + 4 个 I2C 宏 (OLED_I2C_ADDR/OLED_I2C_CMD/OLED_I2C_DATA/OLED_CONTROLLER) 适配 SSD1306 / SH1106 / 自定义控制器;函数签名不变。
现代 ttk vista/clam 主题, 三 tab Notebook 布局 (左 屏幕/参数/操作 / 右 preview+slideshow 永远可见), 顶栏带版本号标题, 底栏 status bar, 默认窗口 1400x900。
+--------------------------------------------------------------------+
| 图像转字模工具 v2.0 W=128 H=64 | <- row 0: header
+--------------------------------------------------------------------+
| [屏幕] [参数] [操作] Notebook | +- 图像预览 ----------------+ | <- row 1: paned
| (left pane, 360px) | | [img_00][img_01][img_02] | |
| | | [img_03][img_04][img_05] | |
| 屏幕 tab: | | [img_06][img_07][img_08] | |
| 宽 (W): [128] 高 (H): [64] | +---------------------------+ |
| [应用到图像处理] | +- 幻灯片播放 ----------------+ |
| (非 8 倍会自动 pad...) | | | |
| | | (2× 缩放循环播放) | |
| 参数 tab: | | | |
| 亮度/对比度/反色/旋转/镜像 | +---------------------------+ |
| 扫描方向/8 种扫描顺序/间隔 | |
| | |
| 操作 tab: | |
| [刷新预览] [开始转换] [退出] | |
| 视频抽帧 + 进度条 + 暂停 | |
| [激励 (彩蛋)] | |
+--------------------------------------------------------------------+
| Ready | W=128 H=64 | v2.0 GUI (v0.4 三 tab) | ... | <- row 2: status
+--------------------------------------------------------------------+
pip install opencv-python numpy pillow
# tkinter 是 Python 自带;Windows / 官方 macOS / 大多数 Linux 发行版默认已装cd CC字模
python CC.pyRelease 页面 下载最新 CC.exe (约 80 MB, v0.4.2 收款二维码) 双击即可。
| 区域 | 作用 |
|---|---|
| 屏幕 tab | 宽 W / 高 H 输入 + "应用到图像处理" 按钮(必须 8 的倍数,上限 16384) |
| 视频编码/进度 | 选中视频后自动显示 codec / fps / 分辨率 / 总帧数;抽帧时显示进度条 + frame X/Y saved N;可暂停/恢复 |
| 参数 tab | 亮度、对比度、反色、旋转、镜像、扫描方向 (vertical/horizontal)、8 种扫描顺序、幻灯片间隔 |
| 图像预览 | 一次显示前 16 张图的处理结果 |
| 幻灯片播放 | 在 2× 缩放的画布上按选定间隔循环播放 |
| 操作 tab | 刷新预览 / 开始转换 / 退出 + 视频抽帧 (文件 + 帧间隔 + 进度 + 暂停) + 激励按钮(彩蛋) |
- 硬上限:16384 × 16384("16K 屏幕"字面含义)。
- W、H 都必须是 8 的倍数(SSD1306 / 其他 1 字节 8 像素控制器的硬要求)。
- 非 8 倍输入会被自动向上 pad 到下一个 8 倍,并在生成的 C 代码首行加注释:
pad 区域填白色。
// OLED font matrix data - 104x32 (NOTE: requested WxH was padded to 104x32)
C 端需要同步修改 oled/OLED.h 顶部 3 个 #define:
#ifndef OLED_WIDTH
#define OLED_WIDTH 128 // 改成你的实际宽度
#endif
#ifndef OLED_HEIGHT
#define OLED_HEIGHT 64 // 改成你的实际高度
#endif
#define OLED_PAGES ((OLED_HEIGHT + 7) / 8)支持的常见尺寸示例:
| W × H | 字节数/图 | 适用 |
|---|---|---|
| 128 × 64 | 1024 | SSD1306 0.96" OLED(默认) |
| 128 × 32 | 512 | SSD1306 0.91" OLED |
| 96 × 16 | 192 | 小尺寸 SSD1306 |
| 256 × 64 | 2048 | SSD1322 2.4" OLED |
| 64 × 128 | 1024 | 竖屏 SSD1306 |
| 1024 × 256 | 32 768 | 大屏 (需要足够 RAM) |
文件对话框里列出 19 种常见容器(mp4 / avi / mov / mkv / webm / flv / wmv / m4v / 3gp / ts / m2ts / mpg / mpeg / ogv / vob / rm / rmvb / asf),但 实际可读范围由 OpenCV 自带的 FFmpeg 决定——比这多得多,少见格式基本都覆盖。选中后自动显示:
codec: h264 | 30.0 fps | 1920x1080 | 1234 frames
抽帧时:
- 进度条实时更新(
frame 740/1234 saved 24)。 - 总帧数未知时切到 indeterminate 模式(动画条)。
- Pause / Resume 按钮可在任意时刻暂停和继续;暂停时 worker 在
Event.wait()阻塞,主线程不卡。 - 损坏帧(
cv2返回None)计入corrupt计数并跳过;完成后 messagebox 报告extracted N frames (M corrupt frames skipped)。
out.c 例子(96×16 屏,2 张图):
// OLED font matrix data - 96x16
#include <stdint.h>
const uint8_t IMG_DATA1[] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// ... 192 bytes total (96*16/8)
};
const uint8_t IMG_DATA2[] = {
// ...
};
// total: 2 image(s)
// OLED_ShowImage calls below use (96, 16)
void gif(void) {
OLED_ShowImage(0,0,96,16,IMG_DATA1);OLED_Update();delay_1ms(500);OLED_Clear();
OLED_ShowImage(0,0,96,16,IMG_DATA2);OLED_Update();delay_1ms(500);OLED_Clear();
}out.h:
#ifndef __OUT_H
#define __OUT_H
#include <stdint.h>
extern const uint8_t IMG_DATA1[];
extern const uint8_t IMG_DATA2[];
extern void gif(void);
#endif // __OUT_H注意:以前版本总是 padding 到 25 个 extern,导致 len(image_files) < 25 时链接失败;新版本只写实际生成的 N 个(已修复)。
仓库里附带一份已参数化的 SSD1306 风格 C 驱动(来自 jiangxiekeji,BSL 协议):
oled/
├── OLED.c # ~52 KB, 全部循环已用 OLED_WIDTH/OLED_PAGES 参数化
├── OLED.h # 顶部 3 个尺寸 + 6 个 I2C/控制器 #define
├── OLED_Controller.h # 按 OLED_CONTROLLER 切换 init 序列 (SSD1306 / SH1106 / USER)
├── OLED_Data.c # 字库数据(ASCII 6x8 / 8x16 / 中文 16x16)
└── OLED_Data.h
- 把
oled/整个目录加进工程。 - 在
main.c里OLED_Init();,然后gif();就能循环播放。 - 换屏幕尺寸只改
OLED.h顶部 3 行:#define OLED_WIDTH 96 #define OLED_HEIGHT 16
- 重新编译即可。
OLED.h 顶部还有 4 个 #define 控制 I2C 协议本身(不需要碰 OLED.c):
| 宏 | 默认 | 用途 |
|---|---|---|
OLED_I2C_ADDR |
0x78 |
I2C 从机地址字节 (SSD1306)。SH1106 用 0x7A |
OLED_I2C_CMD |
0x00 |
控制字节:命令流 |
OLED_I2C_DATA |
0x40 |
控制字节:数据流 |
OLED_CONTROLLER |
OLED_CTRL_SSD1306 |
见下表 |
OLED_CONTROLLER 值 |
适配 | 备注 |
|---|---|---|
OLED_CTRL_SSD1306 (0) |
0.96" / 1.3" SSD1306 | 默认 |
OLED_CTRL_SH1106 (1) |
1.3" SH1106 | charge pump / precharge / vcomh 微调 |
OLED_CTRL_USER (2) |
SSD1322 / SSD1351 / ILI9341 / 你自己的 | 必须 #define OLED_INIT_USER() { 你的 init 命令 } |
示例 1:换 SH1106
// 放在 include OLED.h 之前
#define OLED_CONTROLLER OLED_CTRL_SH1106示例 2:换 0x7A 地址的 SSD1306 模块
#define OLED_I2C_ADDR 0x7A示例 3:加 SSD1322 (256x64, 4-bit grayscale)
#define OLED_CONTROLLER OLED_CTRL_USER
#define OLED_INIT_USER() do { \
OLED_WriteCommand(0x15); OLED_WriteCommand(0x00); \
OLED_WriteCommand(0x3F); \
OLED_WriteCommand(0x75); OLED_WriteCommand(0x00); \
OLED_WriteCommand(0x3F); \
OLED_WriteCommand(0xA0); OLED_WriteCommand(0x53); \
OLED_WriteCommand(0xA3); OLED_WriteCommand(0x00); \
OLED_WriteCommand(0xAF); \
} while(0)SSD1322 是 4-bit grayscale,OLED_DisplayBuf 是按 8-bit 组织的——你的 OLED_UpdateArea 实现需要把 byte 拆成两个 nibble 再写。OLED_ShowImage 接口不变,硬件层你看着办。
16K 满配警告:
OLED_DisplayBuf[OLED_PAGES][OLED_WIDTH]在 16Kx16K 是 256 MB,远超任何 MCU 的 RAM。请按你的硬件能力选尺寸。
启动时自动加载上次设置,关闭时自动保存到:
| OS | 路径 |
|---|---|
| Windows | C:\Users\<you>\.cym_cc_config.json |
| macOS / Linux | ~/.cym_cc_config.json |
保存的参数(14 个):
{
"width": 128,
"height": 64,
"image_folder": "C:/Users/you/Pictures/gif/",
"output_file": "C:/Users/you/out.c",
"video_file": "",
"scan_direction": "vertical",
"scan_order": "left_to_right_top_to_bottom",
"slideshow_interval": 500,
"video_frame_interval": 30,
"brightness": 0,
"contrast": 1.0,
"rotation": "0",
"invert_color": false,
"horizontal_flip": false,
"vertical_flip": false
}失败静默(权限/磁盘满都不打扰用户)。
cd CC字模
pip install pyinstaller
python build.py # 单文件 -> dist/CC.exe
python build.py --onedir # 目录模式 -> dist/CC/CC.exe(启动更快)build.py 会自动:
- 扫描
cym_icon.ico和picture/目录,把所有图片用bytes字面量(不再是 base64)嵌进resources.py。 - 写一个 UPX 探测,没装 UPX 时
WARNING而不是默默跳过。 - 预先剔除
matplotlib / scipy / pandas / PyQt* / IPython / pytest / librosa / sklearn / pydoc / doctest等绝对用不到的重模块。
cd CC字模
# 128×64 行为回归(112 条用例,跨 16 个 (direction, order) × 7 张合成图)
python verify_original.py
# 期望: Total: 112 cases, Mismatches: 0
# 自定义尺寸行为(160 条用例:5 尺寸 × 2 方向 × 8 order × 2 图)
python verify_custom_size.py
# 期望: Total: 160 cases, Fails: 0
# convert_images 功能测试(128×64 / 96×16 / 100×30 pad / 空目录)
python test_convert.py
# 期望: All convert_images tests passed.
# 性能基准(4 尺寸 × 4 order)
python bench.py
# 128×64 ~1.5 ms/iter, 96×16 ~0.3 ms/iter, 256×64 ~2.9 ms/iter回归测试覆盖了关键的 bottom_to_top 边界 quirk(已固化的旧行为,故意保留以兼容历史生成的 C 数组)。
CC/
├── CC.exe # 预编译的可执行文件(Release 页面下载)
├── CC.zip # 整个项目的压缩包
├── LICENSE # BSL-1.0
├── README.md # 本文件
├── oled/ # SSD1306 风格 C 驱动
│ ├── OLED.c # 已参数化
│ ├── OLED.h # 顶部 3 个 #define
│ ├── OLED_Data.c
│ └── OLED_Data.h
└── CC字模/ # 主体(Python 工具)
├── CC.py # 主程序
├── CC_original.py # 最初版(供回归测试对比)
├── build.py # PyInstaller 打包脚本
├── resources.py # 自动生成的图标内嵌(bytes 字面量,265 KB)
├── cym_icon.ico # 应用图标
├── verify_original.py # 128×64 回归测试
├── verify_custom_size.py # 自定义尺寸测试
├── test_convert.py # convert_images 功能测试
├── bench.py # 性能基准
├── CHANGELOG.md # 详细变更日志
└── .gitignore
Q: 生成的 C 文件怎么用?
A: 直接 #include "out.h",在主循环里调 gif();。需要 delay_1ms(uint32_t ms) 这个延时函数(你的工程里自己提供;HAL 库里通常用 HAL_Delay 替代)。
Q: 选 W=100 H=30 会发生什么?
A: 内部 pad 到 104×32(多 4 列、2 行白色),生成的 C 代码首行会写明 requested WxH was padded to 104x32,用户需在硬件侧忽略多余列/行。
Q: 视频转出来的图片有水印 / 字幕怎么办? A: 视频本身有的话没法自动去掉,生成前用其它工具去水印。本工具不做视频编辑。
Q: 16K 屏幕真的能跑吗?
A: 取决于你 MCU 的 RAM。OLED_DisplayBuf 在 16K×16K 是 256 MB,没有任何 MCU 能装下。实际可用尺寸取决于你的硬件 + 编译器,建议先按你的屏幕真实分辨率设 W、H,剩下交给 #define 编译期检查。
Q: 为什么保留了 bottom_to_top col-major 的 boundary quirk(行 63, 55, …, 7 而不是 56, 48, …, 0)?
A: 这是原代码就有的 bug,但用户已经用这个 byte 序列烧过固件了,修复会导致已部署的 C 数组错位。112 条回归测试已经把它固化下来;如果你想从零开始、可以重写 _make_iter 里 bottom_to_top_left_to_right 的 horizontal col-major 分支为 range(H - 8, -1, -8)。
Q: 怎么减小 EXE 体积?
A: 装 UPX 即可(再压 30%)。build.py 会自动检测。其他手段比如替换 opencv-python 为 opencv-python-headless 省 20 MB;想自己定义,参见 build.py 的 EXCLUDED_MODULES。
本项目以 Boost Software License 1.0 发布。
附带 C 驱动 oled/OLED.c 来自 jiangxiekeji.com(桨协科技),同样 BSL-1.0 许可。