.film 文件是 FrameFilm 项目用于存储电子纸显示屏帧数据的专用二进制文件格式。该格式针对墨水屏(e-Paper)特性优化,文件头固定为 32 字节,通过 Reserved[0](现定义为 Format 判别码)区分编码方式与版本。
当前协议包含两个版本:
0x00):4bit 颜色编码,每字节存储 2 个像素,ColorCount 取值 2-6,ColorTable 自描述颜色映射。0x01-0x03):面向 48 色 spectra 与黑白快刷的扩展格式,包括 1bpp 黑白(MonoFast)、8bpp 索引 ColorQual(3 相刷新)与 ColorFast(2 相刷新),详见第 10 节。+------------------+
| 文件头 (32B) |
+------------------+
| 文件主体 (N B) |
+------------------+
文件头占用固定的 32 字节,包含元数据信息:
| 偏移量 | 字段名 | 长度 | 类型 | 描述 |
|---|---|---|---|---|
| 0x00 | FileSize | 4 B | uint32 | 文件主体数据大小(不含文件头,单位:字节) |
| 0x04 | ScreenWidth | 2 B | uint16 | 屏幕宽度(像素),仅定义字段,暂不使用 |
| 0x06 | ScreenHeight | 2 B | uint16 | 屏幕高度(像素),仅定义字段,暂不使用 |
| 0x08 | ColorCount | 1 B | uint8 | 屏幕颜色数量,v1 有效值范围:2-6 |
| 0x09 | Format | 1 B | uint8 | 格式判别码,区分 v1/v2 编码方式 |
| 0x0A | FrameCount | 2 B | uint16 | 帧数(多帧动画),0 或 1 表示单帧 |
| 0x0C | Reserved | 4 B | byte[] | 保留字段,用于未来扩展 |
| 0x10 | ColorTable | 16 B | byte[] | 文件颜色表,定义 16 种颜色的编码映射 |
0x2710(小端序存储为 10 27 00 00)0x0320(小端序存储为 20 03)0x0258(小端序存储为 58 02)Format 决定实际编码2:双色屏(如黑白屏)3:三色屏(如黑白红)4:四色屏5:五色屏6:六色屏| 值 | 名称 | 每像素位数 | 单帧主体大小 | 说明 |
|---|---|---|---|---|
| 0x00 | v1 4bpp | 4 bit | W×H/2 | v1 兼容,ColorCount 2-6,用 ColorTable |
| 0x01 | MonoFast | 1 bit | W×H/8 | 黑白快刷(差分),详见第 10 节 |
| 0x02 | ColorQual | 8 bit | W×H | 48 色高质量,3 相刷新,详见第 10 节 |
| 0x03 | ColorFast | 8 bit | W×H | 48 色快刷,2 相刷新,详见第 10 节 |
未列出的值(
0x04-0xFF)保留,读取方应视为未知格式并拒绝解析。
0 或 1 表示单帧;大于 1 时主体按帧顺序连续拼接。FileSize = 单帧主体大小 × 帧数(帧数 ≤ 1 时按 1 计算)。0x000x00)使用;v2 的 8bpp 格式使用固件内置色板,本字段填充 0x00ColorTable[0] = 0x00 // 编码 0 -> 实际颜色 0x00(通常是白色或背景色)
ColorTable[1] = 0x01 // 编码 1 -> 实际颜色 0x01
ColorTable[2] = 0xFF // 编码 2 -> 实际颜色 0xFF(黑色)
ColorTable[3] = 0x03 // 编码 3 -> 实际颜色 0x03
...(其余字节根据实际颜色定义填充)
以下描述仅适用于 Format == 0x00 的 v1 格式。文件主体是连续的 颜色编码数据流,总字节数由文件头的 FileSize 字段指定。v2 格式的主体结构见第 10 节。
字节结构:
+--------+--------+
| 高 4 位 | 低 4 位 |
+--------+--------+
像素 1 像素 2
颜色编码范围:0x0 - 0xF(0-15)
假设屏幕宽度为 10 像素,第一行像素的颜色编码序列为:
[3, 5, 7, 2, 9, 1, 4, 6, 8, 0]
编码过程:
像素 0-1: 3(0011), 5(0101) -> 字节 = 0x35
像素 2-3: 7(0111), 2(0010) -> 字节 = 0x72
像素 4-5: 9(1001), 1(0001) -> 字节 = 0x91
像素 6-7: 4(0100), 6(0110) -> 字节 = 0x46
像素 8-9: 8(1000), 0(0000) -> 字节 = 0x80
结果字节序列:0x35 0x72 0x91 0x46 0x80
假设配置如下:
文件头字节序列(十六进制):
Offset 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
----------------------------------------------------------------------------------------------------------------
0x00 10 27 00 00 20 03 58 02 04 00 00 00 00 00 00 00 00 00 01 FF 03 02 00 00 00 00 00 00 00 00 00 00
字段解析:
| 字段 | 偏移 | 长度 | 值 | 说明 |
|---|---|---|---|---|
| FileSize | 0x00 | 4 B | 0x00 0x27 0x10 0x00 |
文件主体大小 = 10000 字节 |
| ScreenWidth | 0x04 | 2 B | 0x20 0x03 |
屏幕宽度 = 800 像素 |
| ScreenHeight | 0x06 | 2 B | 0x58 0x02 |
屏幕高度 = 600 像素 |
| ColorCount | 0x08 | 1 B | 0x04 |
4 色屏 |
| Format | 0x09 | 1 B | 0x00 |
v1 六色编码 |
| FrameCount | 0x0A | 2 B | 0x00 0x00 |
单帧 |
| Reserved | 0x0C | 4 B | 00 00 00 00 |
保留字段 |
| ColorTable | 0x10 | 16 B | 00 01 FF 03 ... |
颜色编码映射表 |
FileSizeScreenWidthScreenHeightColorCountFormatFrameCountFormat == 0x00,读取文件头偏移 0x10-0x1F 的 16 字节,获取 ColorTableFileSize 读取文件主体数据Format 解析主体(详见第 4、10 节):
0x00:每字节拆分为两个 4bit 颜色编码0x01:每字节 8 个像素的 1bpp 位图0x02 / 0x03:每字节一个 8bpp 颜色索引ColorTable 转换;v2 的 8bpp 格式使用固件内置色板Format 字段是版本与编码方式的唯一判别依据,读取方必须先读取 Format 再决定解析逻辑;0x0C 起的 4 字节 Reserved 保留用于未来扩展Format == 0x00,原 v1 读取器可照常工作;读取方对未知 Format(0x04-0xFF)应拒绝解析而非猜测FileSize 与实际文件大小是否匹配#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define FILM_HEADER_SIZE 32
#define FILM_COLOR_TABLE_SIZE 16
typedef struct {
uint32_t fileSize;
uint16_t screenWidth;
uint16_t screenHeight;
uint8_t colorCount;
uint8_t reserved[7];
uint8_t colorTable[FILM_COLOR_TABLE_SIZE];
} __attribute__((packed)) FilmHeader;
typedef struct {
FilmHeader header;
uint8_t* pixelData;
} FilmFile;
上例为 v1 视图(
reserved[7]未细分)。v2 读取时,reserved[0]为Format,reserved[1..2](小端)为FrameCount,reserved[3..6]为保留。可扩展结构体:typedef struct { uint32_t fileSize; uint16_t screenWidth; uint16_t screenHeight; uint8_t colorCount; uint8_t format; // 0x09 uint16_t frameCount; // 0x0A(小端) uint8_t reserved[4]; // 0x0C uint8_t colorTable[FILM_COLOR_TABLE_SIZE]; } __attribute__((packed)) FilmHeaderV2;
在 ESP32、STM32 等小端序 MCU 上,可直接通过指针转换读取:
FilmFile* film_load(const char* filepath) {
FILE* fp = fopen(filepath, "rb");
if (!fp) {
return NULL;
}
FilmFile* film = (FilmFile*)malloc(sizeof(FilmFile));
if (!film) {
fclose(fp);
return NULL;
}
uint8_t headerBuffer[FILM_HEADER_SIZE];
if (fread(headerBuffer, 1, FILM_HEADER_SIZE, fp) != FILM_HEADER_SIZE) {
free(film);
fclose(fp);
return NULL;
}
film->header.fileSize = *(uint32_t*)&headerBuffer[0x00];
film->header.screenWidth = *(uint16_t*)&headerBuffer[0x04];
film->header.screenHeight = *(uint16_t*)&headerBuffer[0x06];
film->header.colorCount = *(uint8_t*) &headerBuffer[0x08];
memcpy(film->header.reserved, &headerBuffer[0x09], 7);
memcpy(film->header.colorTable, &headerBuffer[0x10], FILM_COLOR_TABLE_SIZE);
film->pixelData = (uint8_t*)malloc(film->header.fileSize);
if (!film->pixelData) {
free(film);
fclose(fp);
return NULL;
}
fread(film->pixelData, 1, film->header.fileSize, fp);
fclose(fp);
return film;
}
使用 memcpy 保证字节序正确:
FilmFile* film_load_safe(const char* filepath) {
FILE* fp = fopen(filepath, "rb");
if (!fp) {
return NULL;
}
FilmFile* film = (FilmFile*)malloc(sizeof(FilmFile));
if (!film) {
fclose(fp);
return NULL;
}
memset(film, 0, sizeof(FilmFile));
uint8_t headerBuffer[FILM_HEADER_SIZE];
if (fread(headerBuffer, 1, FILM_HEADER_SIZE, fp) != FILM_HEADER_SIZE) {
free(film);
fclose(fp);
return NULL;
}
memcpy(&film->header.fileSize, &headerBuffer[0x00], 4);
memcpy(&film->header.screenWidth, &headerBuffer[0x04], 2);
memcpy(&film->header.screenHeight, &headerBuffer[0x06], 2);
memcpy(&film->header.colorCount, &headerBuffer[0x08], 1);
memcpy(film->header.colorTable, &headerBuffer[0x10], FILM_COLOR_TABLE_SIZE);
film->pixelData = (uint8_t*)malloc(film->header.fileSize);
if (!film->pixelData) {
free(film);
fclose(fp);
return NULL;
}
fread(film->pixelData, 1, film->header.fileSize, fp);
fclose(fp);
return film;
}
手动拼接字节,兼容任意字节序:
static inline uint32_t read_uint32_le(const uint8_t* buffer) {
return buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24);
}
static inline uint16_t read_uint16_le(const uint8_t* buffer) {
return buffer[0] | (buffer[1] << 8);
}
FilmFile* film_load_manual(const char* filepath) {
FILE* fp = fopen(filepath, "rb");
if (!fp) return NULL;
FilmFile* film = (FilmFile*)malloc(sizeof(FilmFile));
if (!film) {
fclose(fp);
return NULL;
}
uint8_t headerBuffer[FILM_HEADER_SIZE];
if (fread(headerBuffer, 1, FILM_HEADER_SIZE, fp) != FILM_HEADER_SIZE) {
free(film);
fclose(fp);
return NULL;
}
film->header.fileSize = read_uint32_le(&headerBuffer[0x00]);
film->header.screenWidth = read_uint16_le(&headerBuffer[0x04]);
film->header.screenHeight = read_uint16_le(&headerBuffer[0x06]);
film->header.colorCount = headerBuffer[0x08];
memcpy(film->header.colorTable, &headerBuffer[0x10], FILM_COLOR_TABLE_SIZE);
film->pixelData = (uint8_t*)malloc(film->header.fileSize);
fread(film->pixelData, 1, film->header.fileSize, fp);
fclose(fp);
return film;
}
将 4bit 编码转换为实际像素颜色:
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600
typedef struct {
uint8_t* frameBuffer;
uint16_t width;
uint16_t height;
uint8_t colorCount;
uint8_t colorTable[FILM_COLOR_TABLE_SIZE];
} FrameBuffer;
static inline uint8_t get_high_nibble(uint8_t byte) {
return (byte >> 4) & 0x0F;
}
static inline uint8_t get_low_nibble(uint8_t byte) {
return byte & 0x0F;
}
FrameBuffer* film_decode_pixels(FilmFile* film) {
FrameBuffer* fb = (FrameBuffer*)malloc(sizeof(FrameBuffer));
if (!fb) return NULL;
fb->width = film->header.screenWidth;
fb->height = film->header.screenHeight;
fb->colorCount = film->header.colorCount;
memcpy(fb->colorTable, film->header.colorTable, FILM_COLOR_TABLE_SIZE);
fb->frameBuffer = (uint8_t*)malloc(fb->width * fb->height);
if (!fb->frameBuffer) {
free(fb);
return NULL;
}
uint32_t pixelIndex = 0;
uint32_t dataIndex = 0;
uint32_t totalPixels = fb->width * fb->height;
while (pixelIndex < totalPixels && dataIndex < film->header.fileSize) {
uint8_t byte = film->pixelData[dataIndex++];
fb->frameBuffer[pixelIndex++] = film->header.colorTable[get_high_nibble(byte)];
if (pixelIndex >= totalPixels) break;
fb->frameBuffer[pixelIndex++] = film->header.colorTable[get_low_nibble(byte)];
}
return fb;
}
适用于墨水屏逐行刷新场景:
void film_get_row_pixels(FilmFile* film, uint16_t rowIndex, uint8_t* rowBuffer, uint16_t bufferSize) {
uint16_t width = film->header.screenWidth;
if (rowIndex >= film->header.screenHeight || bufferSize < (width + 7) / 8) {
return;
}
uint32_t startByte = rowIndex * ((width + 1) / 2);
uint32_t byteIndex = 0;
uint16_t pixelIndex = 0;
while (pixelIndex < width && (startByte + byteIndex) < film->header.fileSize) {
uint8_t byte = film->pixelData[startByte + byteIndex++];
rowBuffer[pixelIndex++] = film->header.colorTable[get_high_nibble(byte)];
if (pixelIndex >= width) break;
rowBuffer[pixelIndex++] = film->header.colorTable[get_low_nibble(byte)];
}
}
void film_free(FilmFile* film) {
if (!film) return;
if (film->pixelData) {
free(film->pixelData);
}
free(film);
}
void framebuffer_free(FrameBuffer* fb) {
if (!fb) return;
if (fb->frameBuffer) {
free(fb->frameBuffer);
}
free(fb);
}
int main(void) {
FilmFile* film = film_load("example.film");
if (!film) {
printf("Failed to load film file\n");
return -1;
}
printf("Film Info:\n");
printf(" File Size: %u bytes\n", film->header.fileSize);
printf(" Screen: %ux%u\n", film->header.screenWidth, film->header.screenHeight);
printf(" Colors: %u\n", film->header.colorCount);
printf(" Color Table: ");
for (int i = 0; i < 16; i++) {
printf("%02X ", film->header.colorTable[i]);
}
printf("\n");
FrameBuffer* fb = film_decode_pixels(film);
if (fb) {
printf("Decoded %u pixels\n", fb->width * fb->height);
framebuffer_free(fb);
}
film_free(film);
return 0;
}
| 版本 | 日期 | 描述 |
|---|---|---|
| 1.0 | 2026-05-25 | 初始版本定义 |
| 2.0 | 2026-09-09 | 新增 v2:MonoFast / ColorQual / ColorFast 三种编码格式 |
v2 复用 32 字节文件头,通过 Format(偏移 0x09)区分编码方式。设计目标是把固件中已实现的三种 spectra 显示能力(hal_epd_display_mono / hal_epd_display_8bpp_mode)封装为可直接存储、传输的 .film 文件。
| Format | 名称 | 每像素位数 | 单帧主体大小 | 刷新相数 | 颜色来源 |
|---|---|---|---|---|---|
0x01 |
MonoFast | 1 bit | W×H/8 | 1(差分) | 黑白 |
0x02 |
ColorQual | 8 bit | W×H | 3 | 客户端 ColorQual 预览/测量色板,固件三相波形 |
0x03 |
ColorFast | 8 bit | W×H | 2 | 客户端 ColorFast 预览色板,固件两相波形 |
三种 v2 格式均面向 E6 3.70”(720×480,JD7601)48 色 spectra 面板。像素排列与 v1 一致:从左到右、从上到下;多字节整数均小端序。
ColorTable(偏移 0x10)不使用,填充 0x00。物理结果由驱动波形决定;客户端 RGB 表用于预览与选色,不是文件内携带的颜色定义。ColorCount(偏移 0x08)不使用,可填 0x00。FrameCount(偏移 0x0A)用于多帧动画,0 或 1 表示单帧。0x01)黑白快刷格式,1 个像素占 1 bit。
1 为黑,0 为白。W×H/8 字节(要求 W 为 8 的倍数)。刷新原理:驱动维护上一帧位图缓存 m_mono_prev,对每个像素按新旧值编码 2bit 跳变码:
transition = 2 * oldBit + newBit
再配合 mono_fast 波形,只驱动发生变化的像素,实现快速、无闪烁的差分刷新。端序、位序与上一帧状态必须一致,否则会出现花屏或伪影。
示例:720×480 的黑白帧主体为 720×480/8 = 43200 字节。
0x02)ColorQual 高质量格式,1 个像素占 1 字节的 8bpp 物理颜色索引。客户端可采用不同测量色板进行选色;Film 实测校色 复用本格式,不新增固件波形。
0-63;RGB参考表零初始化尾部不代表 64-255 的面板物理结果,不得使用这些索引。0-2 当作白底;当前 ColorQual 候选中的白色使用索引 15。文件不携带 RGB 色板。W×H 字节,每字节对应一个像素。驱动内部将相邻两个 8bpp 索引拆成高、低两个 3bit 平面(index >> 3 与 index & 7),每个平面按 4bpp 打包成 W×H/2 字节后再发送:
high = ((index0 >> 3) << 4) | (index1 >> 3);
low = ((index0 & 7) << 4) | (index1 & 7);
文件主体存储的是原始 8bpp 索引(每像素 1 字节),平面拆分由驱动完成,无需文件层预打包。
0x03)48 色快刷格式,编码方式与 ColorQual 相同,差异在于色板与刷新相数。
0-63;不得输出 64-255。tools/ForFilm/js/convert.js 为准。不要将占位索引 0-2 当作白底;同屏色卡使用索引 21 作为 ColorFast 白底。W×H 字节。48 色 spectra 采用8bpp物理索引。下表保留历史RGB参考值,不作为当前客户端候选列表或本块屏幕的实测报告;当前数据以 tools/ForFilm/js/convert.js 及其Flutter同步数据为准。编码数、候选数、不同RGB数和实屏可分辨色数应分别记录;64-255 不属于本格式使用范围。
| 色板 | 有效候选色数 |
|---|---|
rgb_fast(ColorFast,Format 0x03) |
55 |
rgb_qual(ColorQual,Format 0x02) |
46 |
| index | R | G | B | HEX | 有效 |
|---|---|---|---|---|---|
| 0 | 255 | 255 | 255 | #000000 |
Y |
| 1 | 255 | 255 | 255 | #000000 |
|
| 2 | 255 | 255 | 255 | #000000 |
|
| 3 | 30 | 25 | 47 | #1E192F |
Y |
| 4 | 62 | 37 | 56 | #3E2538 |
Y |
| 5 | 108 | 62 | 72 | #6C3E48 |
Y |
| 6 | 117 | 60 | 49 | #753C31 |
Y |
| 7 | 172 | 97 | 58 | #AC613A |
Y |
| 8 | 41 | 41 | 79 | #29294F |
Y |
| 9 | 58 | 62 | 91 | #3A3E5B |
Y |
| 10 | 69 | 70 | 100 | #454664 |
Y |
| 11 | 78 | 78 | 102 | #4E4E66 |
Y |
| 12 | 121 | 114 | 122 | #79727A |
Y |
| 13 | 150 | 131 | 127 | #96837F |
Y |
| 14 | 171 | 145 | 120 | #AB9178 |
Y |
| 15 | 198 | 170 | 130 | #C6AA82 |
Y |
| 16 | 45 | 56 | 112 | #2D3870 |
Y |
| 17 | 110 | 114 | 139 | #6E728B |
Y |
| 18 | 163 | 164 | 169 | #A3A4A9 |
Y |
| 19 | 197 | 197 | 187 | #C5C5BB |
Y |
| 20 | 255 | 255 | 255 | #000000 |
Y |
| 21 | 201 | 201 | 191 | #FFFFFF |
Y |
| 22 | 255 | 255 | 255 | #FFFFFF |
|
| 23 | 255 | 255 | 255 | #FFFFFF |
|
| 24 | 34 | 34 | 84 | #222254 |
Y |
| 25 | 37 | 37 | 87 | #252557 |
Y |
| 26 | 38 | 38 | 92 | #26265C |
Y |
| 27 | 40 | 39 | 96 | #282760 |
Y |
| 28 | 66 | 63 | 110 | #423F6E |
Y |
| 29 | 107 | 86 | 117 | #6B5675 |
Y |
| 30 | 133 | 108 | 112 | #856C70 |
Y |
| 31 | 182 | 146 | 132 | #B69284 |
Y |
| 32 | 21 | 60 | 135 | #153C87 |
Y |
| 33 | 32 | 84 | 144 | #205490 |
Y |
| 34 | 38 | 90 | 147 | #265A93 |
Y |
| 35 | 42 | 95 | 149 | #2A5F95 |
Y |
| 36 | 70 | 124 | 158 | #467C9E |
Y |
| 37 | 105 | 147 | 163 | #6993A3 |
Y |
| 38 | 129 | 162 | 153 | #81A299 |
Y |
| 39 | 173 | 184 | 152 | #ADB898 |
Y |
| 40 | 63 | 24 | 29 | #3F181D |
Y |
| 41 | 90 | 24 | 28 | #5A181C |
Y |
| 42 | 94 | 23 | 27 | #5E171B |
Y |
| 43 | 97 | 25 | 29 | #61191D |
Y |
| 44 | 128 | 30 | 31 | #801E1F |
Y |
| 45 | 149 | 37 | 35 | #952523 |
Y |
| 46 | 157 | 45 | 43 | #9D2D2B |
Y |
| 47 | 180 | 71 | 32 | #B44720 |
Y |
| 48 | 38 | 55 | 83 | #263753 |
Y |
| 49 | 65 | 101 | 101 | #416565 |
Y |
| 50 | 81 | 117 | 105 | #517569 |
Y |
| 51 | 94 | 127 | 106 | #5E7F6A |
Y |
| 52 | 123 | 149 | 112 | #7B9570 |
Y |
| 53 | 145 | 162 | 110 | #91A26E |
Y |
| 54 | 169 | 170 | 100 | #A9AA64 |
Y |
| 55 | 194 | 187 | 96 | #C2BB60 |
Y |
| 56 | 62 | 55 | 73 | #3E3749 |
Y |
| 57 | 145 | 132 | 77 | #91844D |
Y |
| 58 | 187 | 167 | 70 | #BBA746 |
Y |
| 59 | 211 | 185 | 62 | #D3B93E |
Y |
| 60 | 255 | 255 | 255 | #000000 |
|
| 61 | 255 | 255 | 255 | #000000 |
|
| 62 | 255 | 255 | 255 | #000000 |
|
| 63 | 219 | 198 | 57 | #DBC639 |
Y |
| index | R | G | B | HEX | 有效 |
|---|---|---|---|---|---|
| 0 | 255 | 255 | 255 | #000000 |
|
| 1 | 255 | 255 | 255 | #000000 |
|
| 2 | 255 | 255 | 255 | #000000 |
|
| 3 | 31 | 25 | 43 | #1F192B |
Y |
| 4 | 46 | 33 | 55 | #2E2137 |
Y |
| 5 | 64 | 46 | 66 | #402E42 |
Y |
| 6 | 84 | 51 | 55 | #543337 |
Y |
| 7 | 163 | 91 | 58 | #A35B3A |
Y |
| 8 | 46 | 97 | 188 | #2E61BC |
Y |
| 9 | 131 | 170 | 206 | #83AACE |
Y |
| 10 | 191 | 211 | 221 | #BFD3DD |
Y |
| 11 | 215 | 227 | 227 | #D7E3E3 |
Y |
| 12 | 255 | 255 | 255 | #FFFFFF |
|
| 13 | 255 | 255 | 255 | #FFFFFF |
|
| 14 | 255 | 255 | 255 | #FFFFFF |
|
| 15 | 255 | 255 | 255 | #FFFFFF |
Y |
| 16 | 27 | 52 | 146 | #1B3492 |
Y |
| 17 | 25 | 69 | 170 | #1945AA |
Y |
| 18 | 27 | 73 | 178 | #1B49B2 |
Y |
| 19 | 30 | 81 | 182 | #1E51B6 |
Y |
| 20 | 66 | 118 | 191 | #4276BF |
|
| 21 | 105 | 146 | 196 | #6992C4 |
Y |
| 22 | 133 | 166 | 190 | #85A6BE |
|
| 23 | 181 | 196 | 193 | #B5C4C1 |
|
| 24 | 24 | 69 | 145 | #184591 |
Y |
| 25 | 34 | 91 | 151 | #225B97 |
Y |
| 26 | 39 | 97 | 152 | #276198 |
Y |
| 27 | 45 | 106 | 155 | #2D6A9B |
Y |
| 28 | 72 | 131 | 163 | #4883A3 |
Y |
| 29 | 100 | 151 | 166 | #6497A6 |
Y |
| 30 | 128 | 169 | 152 | #80A998 |
Y |
| 31 | 170 | 191 | 143 | #AABF8F |
Y |
| 32 | 103 | 25 | 30 | #67191E |
Y |
| 33 | 130 | 19 | 27 | #82131B |
Y |
| 34 | 133 | 19 | 27 | #85131B |
Y |
| 35 | 136 | 21 | 30 | #88151E |
Y |
| 36 | 157 | 21 | 28 | #9D151C |
Y |
| 37 | 170 | 24 | 33 | #AA1821 |
Y |
| 38 | 175 | 24 | 30 | #AF181E |
Y |
| 39 | 196 | 46 | 33 | #C42E21 |
Y |
| 40 | 55 | 34 | 61 | #37223D |
Y |
| 41 | 67 | 37 | 64 | #432540 |
Y |
| 42 | 72 | 39 | 66 | #482742 |
Y |
| 43 | 76 | 42 | 69 | #4C2A45 |
Y |
| 44 | 115 | 57 | 79 | #73394F |
Y |
| 45 | 148 | 73 | 90 | #94495A |
Y |
| 46 | 166 | 88 | 96 | #A65860 |
Y |
| 47 | 197 | 136 | 124 | #C5887C |
Y |
| 48 | 28 | 67 | 69 | #1C4345 |
Y |
| 49 | 54 | 103 | 63 | #36673F |
Y |
| 50 | 72 | 121 | 66 | #487942 |
Y |
| 51 | 87 | 133 | 67 | #578543 |
Y |
| 52 | 108 | 145 | 55 | #6C9137 |
Y |
| 53 | 124 | 154 | 46 | #7C9A2E |
Y |
| 54 | 154 | 169 | 34 | #9AA922 |
Y |
| 55 | 188 | 187 | 19 | #BCBB13 |
Y |
| 56 | 58 | 70 | 78 | #3A464E |
Y |
| 57 | 151 | 155 | 48 | #979B30 |
Y |
| 58 | 205 | 185 | 15 | #CDB90F |
Y |
| 59 | 240 | 211 | 7 | #F0D307 |
Y |
| 60 | 243 | 214 | 7 | #000000 |
|
| 61 | 255 | 255 | 255 | #000000 |
|
| 62 | 255 | 255 | 255 | #000000 |
|
| 63 | 255 | 255 | 255 | #F3D607 |
Y |
索引
64-255全为#000000。上表”有效”列标注为Y的索引才是active_fast/active_qual中的候选色。
当 FrameCount > 1 时,文件主体按帧顺序连续拼接,每帧大小由 Format 决定:
FileSize = 单帧主体大小 × FrameCount
播放时按帧顺序依次提交给显示驱动;MonoFast 帧之间保留上一帧状态,天然支持差分局刷动画。
#include <stdint.h>
typedef struct {
uint32_t fileSize; // 0x00,小端
uint16_t screenWidth; // 0x04
uint16_t screenHeight;// 0x06
uint8_t colorCount; // 0x08
uint8_t format; // 0x09
uint16_t frameCount; // 0x0A,小端
uint8_t reserved[4]; // 0x0C
uint8_t colorTable[16]; // 0x10
} __attribute__((packed)) FilmHeaderV2;
// 根据 format 计算单帧主体字节数
static uint32_t film_frame_size(const FilmHeaderV2 *h) {
uint32_t px = (uint32_t)h->screenWidth * h->screenHeight;
switch (h->format) {
case 0x00: return px / 2; // v1 4bpp
case 0x01: return px / 8; // MonoFast 1bpp
case 0x02: // ColorQual 8bpp
case 0x03: return px; // ColorFast 8bpp
default: return 0; // 未知格式
}
}
// 解析并显示单帧;film 指向完整文件缓冲(含 32 字节头),body = film + 32
static void film_display_frame(const uint8_t *film, uint8_t format) {
const uint8_t *body = film + 32;
switch (format) {
case 0x00:
hal_epd_display_film(film); // v1:传入含文件头的完整缓冲
break;
case 0x01:
hal_epd_display_mono(body); // 1bpp 位图
break;
case 0x02:
hal_epd_display_8bpp_mode(body, 1); // ColorQual 三相
break;
case 0x03:
hal_epd_display_8bpp_mode(body, 0); // ColorFast 两相
break;
default:
break;
}
}
Ark 当前已实现 ColorQual/ColorFast,由 Format 分派到
hal_epd_display_8bpp_mode(body, 1/0);多帧动画也复用这些入口。测量说明见 EPD037A02。
Ark 的实验 512 色预览沿用 Format 0x7F,不属于上述正式 film 格式,也不分配公开 Format 0x04。CONFIG_ARK_EPD_ABC_PREVIEW=y 默认启用此播放路径;独立的 CONFIG_ARK_EPD_ABC_DIAG=y 仍可用于实验构建,并兼容同一容器。客户端必须先查询 BLE 0x59 bit5(0x00000020),不能根据固件显示版本号推断支持情况。
容器固定为 720×480、FrameCount=1;offset12..15 必须为 ASCII ABC9,FileSize 必须为 691200,总长严格为 691232 字节。主体为 720×480 个 little-endian uint16,值限于 0..511:bit8..6=A、bit5..3=B、bit2..0=C,其余高位必须全为0。错误标记、尺寸、长度、多帧或非零高位均拒绝;上传提交前检查完整主体,播放前再次检查高位。正常图片目录与按文件名打开接口沿用既有路径。
I006 是客户端的实验预览出图方案,容器只携带其生成的 A/B/C 码;该能力不承诺已校准出512种可辨认实色。驱动保持既有 full clear + 三次 ColorQual 刷新,电压与 LUT 不变。默认预览不会开放原生 Format2 的诊断标记或其他实验模式。具体合同、对照方式与实机边界见 同屏实验记录 和 工具说明。