47 KiB
02 屏幕底层控制接口需求
本文描述点阵屏幕底层控制部分的需求。目标是把 HUB75 点阵屏控制抽象成稳定的显示接口,让后续网页端和各种显示功能只关心“生成一张 64x64 RGB 图像”,不关心 GPIO 时序、扫描方式、亮度 PWM 或硬件映射。
当前硬件范围固定为:
- 主控:
核桃派 ZeroW(Allwinner H618) - 屏幕:
RGB-Matrix-P3-64x64-F - 分辨率:
64 x 64 - 接口:
HUB75 - 扫描方式:
1/32 - 驱动:项目自带
walnutpi-h618-hub75C 原生驱动 - GPIO:H618
/dev/mem寄存器访问,14 根信号集中在PIbank - 屏幕数量:单块屏,不级联,不并行多链路
- 屏幕输入电压检测:
M5Stack Unit ADC v1.1(U013-V11,ADS1110,I²C 地址0x48)
0. 需求编号索引
本文件使用 DISPLAY-*、CONFIG-* 和 HW-* 编号描述显示边界、驱动、配置和硬件契约。网页/API 入口见 01_网页配置端需求.md,测试覆盖关系见 测试相关资料/如何测试/核桃派点阵屏控制服务测试流程.md。
| 编号 | 范围 | 当前阶段要求 |
|---|---|---|
HW-MATRIX |
当前屏幕、HUB75、GPIO、硬件映射 | 单块 RGB-Matrix-P3-64x64-F,64 x 64,1/32 扫描、ABCDE 行地址;信号固定映射到核桃派 PI bank。 |
HW-SCREEN-VOLTAGE |
屏幕输入端电压检测、Unit ADC 接线和 I²C 占用 | 使用 M5Stack Unit ADC v1.1 并联测量屏幕输入端电压,占用物理脚 3/5 的 PI8/SDA1、PI7/SCL1;独立监测组件向主服务提供缓存状态和每台设备的校准能力。 |
HW-CPUFREQ-AXP313A |
CPU 电源与 cpufreq | H618 板载 PMIC 按真实 x-powers,axp313a 绑定,DCDC2 作为 CPU 电源;保持既有 OPP 与 810000..1080000µV 约束,不超频、不提高电压。ALDO1 维持 1.8V、DLDO1 维持常开 3.3V、DCDC3 维持引导器实际设置的 1.10V,禁止因错误设备树把非 CPU 电源轨提高或关闭。 |
DISPLAY-FRAME |
项目内部显示边界 | 统一场景在上层合成,底层只接收最终 64x64 RGB 帧;透明 RGBA 层不得直接提交驱动。 |
DISPLAY-SERVICE |
DisplayService 对上层接口 |
提交最终场景或独占测试帧,并兼容清屏、纯色、旧文字、方向、亮度和状态能力。 |
DISPLAY-STARTUP-INDICATOR |
开机动态笑脸 | 白底粉色像素笑脸持续眨眼,固定使用 50% 有效亮度、继承用户方向,并在首个成功改屏指令后退出。 |
DISPLAY-WIFI-INDICATOR |
开机 WiFi 状态提示 | 等待窗口到期后以固定 50% 名义亮度滚动连接信息;断网时叠加静态 WiFi 图标,且不污染用户画面和模式。 |
DISPLAY-DEFAULT-CONTENT |
默认静态或动图输出 | 服务启动先显示配置引用的模板内容;提示覆盖结束后恢复,不把启动显示算作用户操作。 |
DISPLAY-CURRENT-CONTENT |
当前内容来源状态 | 显示服务随实际仲裁输出报告模板、动图、系统覆盖或未保存内容,供顶栏展示。 |
DISPLAY-DRIVER |
真实 walnutpi-h618-hub75 封装 |
C 双缓冲刷新线程直接控制 H618 PI bank,初始化、提交、调亮度、调刷新率、统计和异常关闭均可验证。 |
DISPLAY-DRIVER-STATUS |
原生线程运行状态 | 报告实际刷新率、完成帧、帧边界交换、截止丢失、CPU 绑定、实时优先级、内存锁定、governor 和最后错误。 |
DISPLAY-REALTIME |
刷新线程实时隔离 | CPU3 绑定、SCHED_FIFO 50、内存锁定和 RT throttling 管理均报告实际结果;性能模式是独立、默认关闭且可恢复的系统设置,不能把请求值冒充实际 governor。 |
DISPLAY-MOCK |
模拟驱动 | 无真实屏幕时仍能开发、测试 API 和保存最新帧。 |
DISPLAY-ORIENTATION |
方向变换 | 0/90/180/270 统一在底层处理;运行时切换方向重新提交当前逻辑帧且不清屏。 |
DISPLAY-BRIGHTNESS |
运行时亮度 | 修改亮度后按当前方向和行映射原样重提当前逻辑帧,使现有画面立即采用新亮度。 |
DISPLAY-LOW-VOLTAGE-PROTECTION |
低电压输出仲裁 | 在不改写用户帧、亮度和模式的前提下限制实际亮度,临界时以 35% 黑底红色低电图标覆盖输出。 |
DISPLAY-TEXT-FONTS |
多语言字体目录、解析与塑形 | 解析稳定字体 ID 与兼容路径,检查全文字形覆盖,优先指定字体并自动回退,使用 Pillow/Raqm 统一渲染常见现代语言。 |
CONFIG-PERSIST |
显示侧配置消费边界 | 配置模块向显示层提供已经迁移、校验的唯一当前格式;生产路径、schema 和更新保护统一由 CONFIG-DATA-LIFECYCLE 定义。 |
DISPLAY-ERRORS |
参数、驱动和显示错误 | 错误返回明确,服务不因显示失败退出。 |
DISPLAY-PERF |
性能和降载策略 | 缓存元素透明层,交互结束后精确渲染,驱动只接收最新最终整帧。 |
DISPLAY-ANIMATION |
scene 帧序列播放 | 预渲染不可变 RGB 帧快照,后台按帧时长循环,并继续服从方向、亮度和低电压输出仲裁。 |
DISPLAY-TEST-SESSION |
临时纯色测试覆盖层 | 以独立颜色和临时亮度覆盖当前输出,不改写用户帧、动画、模式或持久亮度,退出后恢复底层输出。 |
DISPLAY-OTA-INDICATOR |
软件更新覆盖层 | OTA 安装期间以 40% 名义亮度显示整体缩放至 90% 并居中的 OTA 图标、阶段和进度;主服务停启时由独立维护进程连续接管,完成或回滚后恢复用户内容。 |
1. 设计原则
1.1 固定帧接口(DISPLAY-FRAME)
项目内部提交到 DisplayService 和真实/模拟驱动时,统一使用 64x64 RGB 帧作为显示边界。网页场景中的透明文字等 RGBA 图层属于上层合成中间产物,必须先按顺序扁平化到 RGB,不能直接交给驱动。
上层功能只允许提交这些内容:
- 一张
PIL.Image,模式为RGB,尺寸为64 x 64。 - 或者一段长度为
64 * 64 * 3的 RGB888 字节数据。 - 或者能被底层转换成上述帧的兼容高层参数,例如纯色、诊断图案和旧文字参数。
上层功能不允许直接做这些事:
- 直接操作 GPIO。
- 直接调用 HUB75 行扫描逻辑。
- 在业务代码里处理
A/B/C/D/E、CLK、LAT、OE。 - 每个功能各自实现屏幕旋转。
- 把像素层、文字层或元素选择状态交给
DisplayService管理。 - 让普通新功能绕过统一场景,直接用自己的完整帧覆盖其他普通内容。
1.2 底层职责(DISPLAY-SERVICE)
底层显示接口负责:
- 初始化并管理
walnutpi-h618-hub75原生驱动。 - 管理屏幕尺寸、7-bit PWM、运行时亮度、完整刷新率上限和双缓冲帧边界交换。
- 接收标准帧。
- 统一应用屏幕方向变换。
- 把帧提交给物理屏幕。
- 保存最近一次未应用物理方向的最终逻辑 RGB 帧,用于方向或亮度调整时原内容重提。
- 统一仲裁默认/用户帧、兼容开机笑脸、WiFi 提示与临界低电图标,并把用户亮度、提示固定亮度、保护上限和临界固定亮度合成为唯一实际输出;任何显示入口不得绕过仲裁。
- 处理统一场景最终帧,以及清屏、纯色、诊断和旧文字等兼容/设备测试命令。
- 在驱动不可用时提供模拟/空实现,方便网页开发。
底层不得理解或修改统一场景的 elements、元素顺序、选中元素、文字透明层缓存和浏览器草稿。纯色、诊断与清屏作为设备测试可以独占提交完整帧,但不得反向清空上层场景。
1.3 上层职责(DISPLAY-FRAME)
网页场景控制器、业务功能和后续动画模块负责:
- 决定要显示什么。
- 维护固定在最底部的像素层与有序元素列表。
- 获取或生成单个元素图层,按列表顺序完成透明合成。
- 只在所有当前元素图层都已就绪时生成最终
64x64 RGB内容并通过DisplayService提交。 - 把未来普通显示功能接入统一场景;只有设备测试可以使用独占完整帧。
它们不需要知道:
- HUB75 是怎么扫描的。
- 核桃派物理脚、PI bank bit 与 HUB75 信号如何对应。
- 刷新率和 PWM 是怎么实现的。
2. 推荐模块结构(DISPLAY-SERVICE)
后续实现时,在 核桃派软件源代码/ 下按类似结构组织:
app/
main.py
api/
display_routes.py
config_routes.py
websocket_routes.py
display/
service.py
matrix_driver.py
native/
h618_hub75.c
h618_hub75.h
Makefile
mock_driver.py
transforms.py
text_renderer.py
config/
store.py
static/
index.html
app.js
style.css
data/
config.json
其中核心边界是:
service.py:对上层暴露DisplayService。matrix_driver.py:用ctypes封装项目自带的 H618 C 共享库,不导入rgbmatrix。native/:实现 PI bank 寄存器映射、bitplane、双缓冲、刷新线程、安全清屏和状态统计。mock_driver.py:没有核桃派或没有屏幕时使用,方便在电脑上开发网页。transforms.py:统一处理0/90/180/270度方向。text_renderer.py:用 Pillow 生成兼容整帧文字和无副作用的透明文字层;渲染函数本身不得调用驱动。
3. DisplayService 接口(DISPLAY-SERVICE)
3.1 接口形态
推荐暴露一个常驻对象:
class DisplayService:
width: int = 64
height: int = 64
def clear(self) -> None:
...
def fill(self, color: tuple[int, int, int]) -> None:
...
def show_image(self, image: Image.Image, mode: str = "image") -> None:
...
def show_rgb_bytes(self, data: bytes, mode: str = "canvas") -> None:
...
def show_text(self, options: TextOptions) -> None:
...
def set_orientation(self, value: int) -> None:
...
def set_brightness(self, value: int) -> None:
...
def get_current_frame(self) -> Image.Image:
...
def get_status(self) -> dict:
...
get_current_frame() 在线程锁内复制经过仲裁、当前正在显示的逻辑 RGB 帧:临界保护时返回低电图标,其次为 WiFi 提示、当前笑脸帧,否则返回缓存的用户逻辑帧。该快照固定为逻辑 0°,不得应用物理方向或底层行映射,也不得修改驱动、模式、配置或状态修订号。网页只读接口 GET /api/display/current-frame 使用该快照生成 64x64 RGB PNG。
3.1.1 开机动态笑脸(DISPLAY-STARTUP-INDICATOR、CONFIG-PERSIST)
- 逻辑帧固定为
64x64 RGB:全屏白色#FFFFFF,粉色#FF4FA3绘制两只眼睛和一个 U 形嘴巴;睁眼约1.3s、闭眼约0.2s循环。 - 动画使用用户保存的
orientation和既有行映射,因此实屏方向与用户设置一致;动画名义亮度为50,实际亮度仍受低电压上限约束,不得读取、覆盖或保存为用户亮度。 - 动画提交不得替换当前逻辑帧、
last_mode或状态修订号。状态接口必须同时区分用户亮度、当前有效亮度和动画是否活动。 - 纯色、清屏、文字、诊断、最终 RGB 帧、方向或亮度的首个成功修改原子退出动画;先恢复用户亮度,再提交用户结果,退出后后台线程不得再次写入动画帧。
- 改屏指令在渲染、驱动提交或配置保存阶段失败时保持或恢复笑脸动画。只读、预览、色板和模板操作没有显示副作用,不得退出。
- 使用 Linux boot ID 和独立的原子状态文件记录本次开机已经退出;同一次整机开机内服务重启不得重现,boot ID 改变后重新显示。状态文件缺失或损坏时按尚未退出处理。
3.1.1.1 开机 WiFi 提示(DISPLAY-WIFI-INDICATOR、CONFIG-WIFI)
- WiFi 提示是独立输出覆盖层,不写入当前用户帧、
last_mode、用户亮度、方向或用户状态修订号;提示结束后恢复最新用户动图帧或静态帧。 - 仲裁顺序固定为
临界低电图标 > WiFi 提示 > 开机笑脸 > 用户动图 > 用户静态画面。WiFi 提示开始时结束当前开机笑脸,但不把提示帧当作用户改屏。 - 连接成功时在黑底上连续左移
SSID: <实际SSID> IP: http://<IPv4>:8080/;断开时顶部为静态 WiFi 断开图标,底部连续左移SSID: <保存SSID> 密码: <保存密码>。未配置时显示明确无配置文案,不虚构密码。 - 连接成功和断开两种滚动文字都必须把字体栅格化结果转成全亮或全灭的单色像素,不保留小字号抗锯齿灰阶;字体覆盖到的每个像素必须完整保留,避免局域网 IP、SSID、密码提示在网页当前帧和实屏上出现断笔或缺块。
- WiFi 提示覆盖期间默认或用户动图线程继续运行;访问首页撤销提示后直接恢复最新底层帧。默认内容启动不得触发用户活动监听器或提前取消提示。
3.1.1.2 默认内容与来源(DISPLAY-DEFAULT-CONTENT、DISPLAY-CURRENT-CONTENT)
- 默认静态模板通过同一 scene 渲染器生成
64x64 RGB后提交;默认动图通过现有快照播放路径启动。二者继承用户方向和亮度,不使用旧笑脸固定亮度,也不修改用户修订号。 - 显示服务为底层用户输出保存可空来源
{category,id,name}。明确显示已保存静态模板或动图时分别记录template、animation;任何普通画板编辑、文字、填色、清屏或诊断显示清除来源并报告unsaved/未保存内容。 - 临界低电、WiFi 和临时纯色测试可见时,状态以
system和对应名称覆盖底层来源;覆盖结束后恢复底层来源。来源状态只描述实际画面,不进入持久配置或改变显示优先级。 - 速度目标为
16px/s、约20fps,循环尾部保留16px空隙;文字使用统一字体解析,帧固定为64x64 RGB并继承用户方向。 - 名义亮度为
50;低电limiting时使用min(50, 上限),critical时仍由 35% 临界低电图标完全覆盖。 - 网络状态改变时可以原地切换成功/失败滚动内容;结束、服务退出和驱动失败均须停止线程且不把密码写入日志。
3.1.2 低电压输出仲裁(DISPLAY-LOW-VOLTAGE-PROTECTION)
- 输出优先级固定为
临界低电图标 > WiFi 提示 > 开机笑脸 > 用户动图 > 用户画面。保护指令只改变当前输出约束,不替换缓存用户逻辑帧、不修改用户brightness、方向、last_mode或用户状态修订号。 - 限亮时实际亮度为用户亮度或笑脸名义
50与保护上限的较小值;临界时实际亮度固定为1,模式报告low_voltage_indicator。临界期间仍接受并保存画面、亮度和方向,方向变化立即旋转图标,恢复后输出最新用户内容。 - 低电图标是确定性
64x64 RGB帧:除图标外全为#000000,使用#FF0000绘制粗线空电池轮廓、小端子和低电量短条,并统一经过现有0/90/180/270方向变换。 - 收紧保护时先降低驱动亮度再重提仲裁帧;放宽时先以旧的较低亮度提交目标帧,再提高亮度并重提,避免高亮瞬态。真实驱动无法确认运行时亮度更新时必须返回错误,不能只记录 warning。
- 电压组件在自身锁和 I²C 锁外传入不可变保护指令。指令应用失败不得修改 ADC 样本状态,应保留保守输出、设置独立错误码并在后续周期重试;自动保护只增加保护修订号。
3.1.3 用户动图播放(DISPLAY-ANIMATION)
- 播放前必须完整校验动图元数据、全部独立 scene v1 和对应
64x64 RGBPNG 缓存;空动图或任一帧失败时保持当前输出不变。播放快照固定帧顺序、时长和内容摘要,但只把当前帧和下一帧解码到内存,不能按总帧数线性占用 RAM。 - 活动快照引用的摘要 PNG 在播放结束前不得被编辑清理;修改动图生成新摘要,旧修订继续播放,停止后再按当前记录协调孤立缓存。长动图详情允许分页读取,网页固定每页渲染 100 帧;跨页顺序调整使用按帧 ID 和目标位置移动接口,不把当前页误当成完整帧序列。播放顺序始终使用完整的已校验元数据快照。
- 每帧时长统一使用整数毫秒,允许
50..604800000ms;七天上限是防误输与浏览器/线程计时安全边界,不改变 scene、方向、亮度或帧提交格式。服务关闭、替换内容或停止事件必须能立即中断长时长等待。 - 输出优先级为
临界低电图标 > 用户动图帧 > 用户静态帧;启动动图会成功退出开机笑脸。低电限亮和临界覆盖不得停止播放线程或改写动图数据。 - 自动换帧不得替换缓存用户静态帧、持久
last_mode或逐帧增加用户状态修订号。开始播放增加一次修订;方向和亮度重提当前动图帧但不中止。 - 清屏、纯色、诊断、最终组合帧或另一动图只有在新输出成功后才停止旧动图;失败时旧播放继续。服务关闭时有界停止线程,服务重启不自动恢复播放。
- 真实驱动的动画帧和静态帧都提交完整 RGB888 back buffer,并由 C 刷新线程在完整帧边界交换;每个内容帧间隔从成功提交后开始计算。
3.2 clear()
行为:
- 生成全黑帧。
- 提交到屏幕。
- 更新当前模式为
clear。
要求:
- 服务退出前也应该调用清屏,避免屏幕停在不可预期状态。
- 如果底层驱动支持
matrix.Clear(),可以直接调用;为了保持状态一致,也可以走全黑帧路径。
3.3 fill(color)
行为:
- 接收
(r, g, b),每个通道范围0..255。 - 生成
64x64 RGB纯色帧。 - 提交到屏幕。
- 更新当前模式为
fill。
要求:
- 参数超出范围时抛出明确的参数错误。
- 全白测试建议结合亮度限制,不鼓励长时间满亮度运行。
- 兼容
fill(color)仍属于普通改屏操作,并会结束活动测试会话。网页纯色测试使用DISPLAY-TEST-SESSION:首次启动以50%临时亮度提交纯色帧,同一会话换色保留临时亮度,亮度范围为1..100。 - 测试帧和临时亮度只存在于内存,不更新用户逻辑帧、用户亮度、
last_mode或动画快照;退出后重新选择当前底层用户输出。 - 输出优先级固定为临界低电图标、低电压亮度上限、临时纯色测试、WiFi 提示、开机笑脸、用户动画或静态用户帧。普通成功改屏负责撤销测试;方向和用户持久亮度仍可在测试期间更新,退出后显示更新后的底层状态。
3.4 show_image(image, mode="image")
行为:
- 接收
PIL.Image。 - 转换为
RGB。 - 尺寸必须是
64 x 64;如后续允许其他尺寸,必须由明确策略缩放或裁切,不能静默拉伸。 - 应用当前方向。
- 提交到真实驱动。
- 更新当前模式,统一场景由调用方指定为
composition,兼容或设备测试模式使用各自名称。
要求:
- 这是所有复杂显示内容的主入口。
- 统一场景合成帧、设备测试帧、后续动画帧和图片导入最终都应该走这个入口。
- 该接口只能看到最终帧,不能拆解或重新排序上层元素。
3.5 show_rgb_bytes(data, mode="canvas")
行为:
- 接收 RGB888 字节数据。
- 长度必须正好是
12288。 - 按行优先顺序转换为
64x64 RGB图像。 - 调用
show_image(image, mode=mode);WebSocket 新消息传入composition,旧消息沿用默认canvas。
用途:
- 主要给 WebSocket 统一场景最终帧及旧画布帧使用;
source:"composition"或旧消息的兼容判断由 WebSocket/API 层完成。
3.6 show_text(options)(DISPLAY-TEXT-FONTS)
行为:
- 接收文字显示参数。
- 调用
text_renderer.py用 Pillow 生成64x64 RGB图像。 - 调用
show_image()。 - 该路径只为现有
POST /api/display/text和POST /api/preview/text兼容保留,新网页中的文字元素不得调用它直接覆盖统一场景。
TextOptions 建议字段:
@dataclass
class TextOptions:
text: str
font: str = "default"
size: int = 12
x: int = 0
y: int = 0
align: str = "left"
color: tuple[int, int, int] = (255, 255, 255)
background: tuple[int, int, int] = (0, 0, 0)
要求:
- 字体加载失败时使用自动多语言默认字体;如果候选字体均不能覆盖全文,返回包含缺失 Unicode 码位的明确错误。
- 字体覆盖由 FontTools cmap 判断并缓存;不得通过渲染结果猜测
.notdef方框。 - 自定义字体覆盖全文时优先使用;不覆盖全文时选择一张覆盖全文的后备字体,避免把同一个复杂文字序列拆成破坏塑形的多个字体片段。
- 文字渲染入口通过同一个字体目录解析
system:<digest>和user:<digest>:<face-index>;预览、兼容整帧文字、模板缩略图和显示服务不得各自维护字体 ID 映射。 - 系统字体从 Fontconfig、显式
MATRIX_FONT_PATHS和已登记默认候选发现,只接受 Pillow 可加载的 TrueType/OpenType 文件。Fontconfig 的完整 FreeType face index 用于 Pillow,FontTools 覆盖检查使用对应基础字体面,不能把可变字体命名实例误判为缺字。 - 用户字体只能解析到当前持久根的
fonts/<SHA-256>.font,不能由 ID 拼接任意路径。旧请求中的直接路径继续兼容;未知 ID、丢失路径或指定字体缺字时保持现有整段回退语义。 - 导入字体目录刷新后,新字体必须立即可用于透明预览、后端缩略图和兼容文字显示;服务重启后同一内容和 face index 得到同一字体 ID。
- Linux 部署通过 Fontconfig 解析 Noto Core/CJK 字体文件和 TTC face index;Pillow 使用 Raqm 完成复杂塑形及双向排版。
- 保证中日韩、拉丁/西里尔/希腊、阿拉伯/希伯来、常见南亚文字和泰文;Emoji 和历史文字不属于当前范围。
- 超出边界的文字允许被裁切。
同一个 text_renderer.py 还必须提供透明文字层渲染能力,供 POST /api/preview/text-layer 调用:
- 输入为
text、font、size、x、y、align和color,不包含background。 - 输出固定为
64x64 RGBA;空白区域 alpha 为0,文字区域保留有效颜色与 alpha。 - 复用兼容整帧文字的字体加载、排版、对齐和裁切逻辑,不能维护第二套文字度量规则。
- 渲染透明层不得调用
show_image()、修改当前逻辑帧/模式、访问真实或模拟驱动或写配置。
3.7 set_orientation(value)(DISPLAY-ORIENTATION、CONFIG-PERSIST)
行为:
- 只允许
0/90/180/270。 - 更新运行时方向。
- 调用配置模块保存方向。
- 保存最近一次未应用方向和行映射的最终逻辑
64x64 RGB帧;该帧可能是组合场景,也可能是独占设备测试。方向变化时使用新方向重新转换并提交该帧。 - 临界低电图标活动时仍保存新的用户方向,并用新方向立即重提图标;被覆盖的用户逻辑帧保持不变,恢复后按最新方向显示。
- 重新提交时不得清屏、替换内容或修改
last_mode。 - 后续所有帧统一应用该方向。
要求:
- 方向逻辑只在显示服务里做一次。
- 不允许画布、文字、动画各自实现一套旋转逻辑。
3.8 set_brightness(value)(DISPLAY-BRIGHTNESS、CONFIG-PERSIST)
行为:
- 更新并保存的是用户亮度;保护活动时实际驱动亮度由输出仲裁器计算,不能把保护上限或临界
35%回写配置。 - 保存配置。
- 真实驱动的运行时亮度只影响之后写入的像素;设置新亮度后,必须按当前方向和行映射重新转换并提交缓存的当前逻辑帧,使当前画面立即采用新亮度。
- 调整亮度时不得清屏、重建或替换逻辑帧、修改
last_mode,也不得要求上层再次发送画面。 - 亮度、原帧重提和配置保存作为一次状态变更;失败时恢复旧亮度并以旧亮度重提当前帧,配置、状态修订号和
last_mode保持原值。 - 临界期间调整用户亮度仍保存成功但继续输出
35%图标;限亮期间滑块仍允许1..100,实际亮度不超过保护上限。
4. 真实驱动封装(DISPLAY-DRIVER、HW-MATRIX)
4.1 H618 原生驱动固定参数
walnutpi-h618-hub75 固定使用:
width=64, height=64, scan_rows=32, row_address_bits=5
pwm_bits=7, rgb_sequence=RGB, brightness=40
limit_refresh_rate_hz=100
pio_base=0x0300B000, pi_bank_offset=0x120, data_offset=0x10
pwm_base=0x0300A000, oe_pwm_channel=4, oe_pwm_tick_ns=125
driver_id=walnutpi-h618-hub75
hardware_mapping=walnutpi-pi-bank-pwm-oe-v2
- 物理尺寸、扫描方式、bitplane 数和 GPIO 映射不是运行时可变探测项;改变面板或接线必须先更新
HW-*、测试和驱动常量。 - C 层按树莓派参考实现把 RGB888 与有效亮度共同映射为 11-bit CIE1931 亮度值,只输出内部 bitplane
4..10的七个高位;禁止再用所有像素共享的 OE 脉冲裁剪或跨扫描固定点抖动表示亮度。1..100的通道映射必须非递减,白色在 100% 映射到2047。上半屏像素y=0..31与下半屏像素y=32..63同时移位,ABCDE 选择当前行对。 - RGB、地址、CLK 和 LAT 在 PI bank 的同一个 32-bit data register 上批量写入;OE 固定复用
PI14/PWM4,由 H618 PWM 硬件结束低有效点亮脉冲。不得逐针调用官方 socket 服务或 Python GPIO API,也不得回退到 CPU 忙等拉高 OE。 - 启动时先把
PI14作为 GPIO 拉高 OE,再初始化 PWM4;PWM5 活动时只允许其共享的 4/5 时钟字段已经与 PWM4 所需的24MHz/div1完全兼容,并必须逐次保留 PWM5 enable、bypass、PCR5 和 PPR5。共享时钟不兼容、PWM5 状态或自有寄存器漂移、寄存器回读不一致、PWM4 已占用或硬件映射 ABI 不匹配时必须保持全黑并拒绝真实驱动启动。所有数据、地址、CLK 和 LAT 清零后才启动刷新。 - 异常、关闭、重建和进程退出均先禁用 PWM4、把
PI14切回 GPIO 并强制 OE 高,再清零输出、恢复 PWM 寄存器和 GPIO 安全输入。systemd 必须用独立hub75_safeoff在ExecStartPre与ExecStopPost覆盖主进程崩溃路径;辅助程序失败必须导致启动失败或在停止日志中明确报错。 - 上游寄存器和排针依据为核桃派官方 MIT
walnutpi/gpioc,项目必须保留许可证、来源 URL 与精确 commit,不能把其来源误写为本项目原创。
DISPLAY-REFRESH-LIMIT 规定:
limit_refresh_rate_hz只允许15、20、30、45、60、80、100,生产默认100;它同时限制逻辑帧边界、back/front buffer 交换和完整面板扫描频率。每个逻辑帧只执行一次 32 行 × 7 bitplane 物理扫描,禁止按亮度切换两/三次扫描而改变时序形态。- 100 Hz 是上限而非必须伪装成 100.000 Hz 的目标;该档允许在每个逻辑周期增加固定
0.25msMMIO/调度保护间隔,名义目标约为97.56 Hz。不得通过放宽 deadline 判定掩盖超时;参考树莓派在约 80 Hz 已稳定,本实现不得把提高扫描率当作修复闪烁的替代方案。 - 刷新线程在帧边界读取新的刷新率和亮度,不重建
/dev/mem映射,也不撕裂当前帧;配置原子写入失败时恢复旧值。 - 亮度范围为
1..100,通过提交帧时的 11-bit CIE1931 通道映射改变 bitplane 数据;OE 的七个物理位权在所有亮度下固定不变。设置亮度后上层必须立即以新亮度重新提交当前帧,不能用同步省略脉冲或共享余数制造整屏时间抖动。 - 每个 row/bitplane 在 LAT 完成后重新装载 PWM4 的周期和有效计数。PWM4 必须使用低有效极性、
125ns计数分辨率及 H618PCR4.PWM_MODE=1单脉冲模式;七个输出位平面对应 11-bit 内部位4..10,固定脉宽为2/4/8/16/32/64/128us。每次令总周期等于有效周期,再用写一清零的PCR4.PWM_PUL_START触发一次脉冲,并等待PWM_PERIOD_RDY/PWM_PUL_START回到可重装状态。不得用PER反复启停连续 PWM 模拟 one-shot,也不得依赖只读PCNTR4自动归零。单脉冲结束后硬件保持 OE 高,因此线程延迟只能延长黑屏时间,不能延长或重复当次点亮脉冲。 - 逻辑刷新率是上限而非伪造的固定值;状态必须返回测量窗口内的
actual_refresh_rate_hz、completed_frames、deadline_misses与buffer_swaps,并另外返回真实的panel_scan_rate_hz、completed_scans和当前scans_per_frame=1。还必须返回oe_timing_backend=h618-pwm4、oe_pulse_faults、oe_forced_blanks、max_programmed_oe_ns和 PWM4 初始化、占用或安全关闭错误;生产不得用软件忙等后端或逻辑帧率冒充硬件状态。
DISPLAY-REALTIME 规定:
- 原生线程启动后尝试绑定 CPU3、设置
SCHED_FIFO 50实时优先级并执行mlockall。在启用CONFIG_RT_GROUP_SCHED的 cgroup v2 板端,显示服务启动时保存并临时关闭全局 RT throttling,使固定在 CPU3 的刷新线程能够进入实时调度;服务退出时恢复原始内核值,设置失败必须阻止服务启动。 - governor 不再由部署脚本永久强制。服务首次启动时记录每个实际 cpufreq policy 的原 governor;配置请求开启时把全部 policy 事务切换为
performance,关闭或服务停止时恢复记录值。systemd 异常重启沿用同一份记录,不得把遗留的performance误记为原值。 /api/status必须分别报告性能模式的请求状态、可用性、实际生效状态、当前/恢复 governor 和最后错误;任一 policy 写入失败时回滚已写 policy,配置不得显示为已开启。- 每项优化都必须在状态中单独报告是否生效及失败原因。权限或内核不支持时允许继续做无屏基准,但不能谎报成功;接屏门槛必须结合实际刷新率和截止丢失判断。
- 时序使用
CLOCK_MONOTONIC_RAW或等价单调时钟。HUB75 输出锁存与移位寄存器隔离,当前 bitplane 的硬件单脉冲点亮期间必须并行移入下一 bitplane,OE 自动回高后才改变行地址并 LAT;位平面固定保持内部二进制权重,截止已过时立即记录 miss,不允许负数睡眠或无限追赶。 - 无屏基准在逻辑 100 Hz、64×64、7 个输出 bitplane 下分别以亮度 5 和 40 连续至少 60 秒:两档都必须为
scans_per_frame=1,逻辑与物理扫描率均不得低于 95 Hz,最大编程 OE 脉宽必须为128000ns;截止丢失率不得高于 0.1%,RSS 不得持续增长,停止后 GPIO 必须回到安全状态。任一档未达标不得部署到实屏。 MATRIX_MAINTENANCE_BLACK=1只用于接屏诊断或部署维护:服务初始化真实驱动后必须保持 C 层初始全黑 front buffer,不恢复默认内容,也不启动开机 WiFi 覆盖层。该变量不得写入生产 unit、配置或持久数据,移除后恢复正常启动行为。
DISPLAY-OTA-INDICATOR 规定:
- 包校验完成并接受任务后,显示服务立即以 40% 名义亮度显示黑底 OTA 图标、阶段文字和进度条;全部元素必须作为同一个 64×64 合成帧整体缩放至 58×58(约 90%),使用保留所有点亮源像素的覆盖缩小方式放在
(3,3)居中,四边保留黑色空白,不单独改变元素间距。OTA 与百分比文字必须先二值化为完整像素字形,百分比不得在缩放前越出画布;方向沿用当前持久方向,不改写用户帧、动画、模式或亮度。 - 临界低电压图标优先于 OTA 覆盖;限亮状态对 40% 继续取较低值。主服务停止后,OTA oneshot 必须等待
hub75_safeoff完成,再由独立维护显示进程读取同一进度文件接管真实驱动,两个进程不得同时访问/dev/mem。 - 新服务健康检查期间继续显示 OTA 覆盖,不提前恢复默认内容。工作服务写入成功或回滚结果后才恢复默认内容;维护显示进程任何退出路径都必须清屏、禁用 OE 并释放映射。
4.2 提交帧方式
- Python 只把完整、已应用方向的
64×64 RGB888数据一次提交给 C 层,不执行逐像素 GPIO 操作。 - C 层持有 front/back 两个 RGB 帧和预计算 bitplane;提交只写 back buffer 并发布待交换标记,刷新线程只在完整帧边界交换。
- 连续多次提交允许覆盖尚未展示的 back buffer,只展示最新完整帧;不得出现半帧、行内交换或积压无界队列。
- 静态图、画布和动画统一走同一帧提交接口;动画时钟仍由
DisplayService管理,底层扫描时钟不随内容帧率改变。
4.3 诊断图案
底层服务必须提供诊断图案入口,用于区分软件参数、HUB75 行地址线和接触不良问题。诊断图案仍然生成标准 64x64 RGB 帧,并走 DisplayService.show_image(),不能绕过方向、mock 或真实驱动边界。诊断帧是独占设备测试,不与统一场景叠加,也不得销毁上层场景草稿。
第一版诊断模式:
corners_lines:四角颜色、y=0/31/32/63横线、x=0/63竖线。row_bands:按固定行带显示不同颜色,观察是否固定间隔复制。address_check:按A/B/C/D/E行地址敏感的行号/条纹模式显示,观察行顺序和E线。text_ok123:显示OK123,复现网页文字路径。clear:清屏。
如果全色正常但诊断图固定出现 32 行重复或上下半屏镜像,优先判断为 E 线未接、接错或接触不良;如果行顺序乱,优先检查 A/B/C/D/E。固定面板不提供映射试错覆盖,软件不得用错误常量掩盖已确认的硬件接线问题。
4.4 权限
真实驱动需要打开 /dev/mem、设置实时调度和锁定内存。生产 systemd 单元使用受控的 root 服务进程,限制可写路径为状态目录和运行目录,并通过 CapabilityBoundingSet、设备白名单和文件系统保护缩小权限。网页只监听局域网,任何上传和路径输入仍按 WEB-SECURITY 校验。
如果后续拆成特权刷新 helper 与非特权 Web 进程,必须先定义消息认证、共享内存生命周期、崩溃清屏和升级兼容边界;当前阶段不实现未经测试的半套降权方案。
5. 模拟驱动(DISPLAY-MOCK)
为了不依赖真实核桃派和屏幕开发网页,必须准备 MockDisplayDriver。
模拟驱动行为:
- 接收所有和真实驱动相同的帧。
- 不访问 GPIO。
- 可以把最新最终帧保存为
data/last_frame.png;组合场景和独占测试使用与真实驱动相同的帧边界。 - 可以在日志中记录当前模式、方向和亮度。
用途:
- Windows 开发机上开发前端页面。
- CI 或普通单元测试中验证 API。
- 屏幕硬件未接好时仍能测试网页逻辑。
驱动选择策略:
- 如果环境变量
MATRIX_DRIVER=mock,强制使用模拟驱动。 - 如果 C 共享库缺失、架构不匹配或
/dev/mem初始化失败,auto模式可以退回模拟驱动,但必须在状态接口标记driver_available=false并保留精确错误;生产健康检查不能把该回退视为真实驱动成功。 - 在核桃派部署时默认尝试
walnutpi-h618-hub75,显式MATRIX_DRIVER=mock只用于本机和阶段化无屏业务验证。
6. 方向变换(DISPLAY-ORIENTATION)
方向变换在底层显示接口里统一处理。
规则:
0:不旋转。90:顺时针旋转 90 度。180:旋转 180 度。270:顺时针旋转 270 度。
使用 Pillow 时可以通过 Image.transpose() 或 Image.rotate() 实现,但必须确认旋转后尺寸仍为 64 x 64。
方向变换测试图建议:
- 点亮四角:
(0,0)红(63,0)绿(0,63)蓝(63,63)白
- 画水平线:
y=0y=31y=32y=63
- 显示文字
UP,确认安装方向。
验收标准:
- 修改方向后,所有显示模式使用同一结果。
- 显示非对称画面后依次切换方向,物理帧立即旋转且内容和当前模式保持。
- 保存方向后重启服务仍生效。
- 不同业务模块不出现互相矛盾的坐标规则。
7. 配置持久化(CONFIG-PERSIST)
配置由独立模块管理,不写死在显示服务里。生产数据根、运行根、落盘 schema_version、单向迁移、耐久原子写入和更新保护均以 01_网页配置端需求.md 的 CONFIG-DATA-LIFECYCLE、CONFIG-BASE 为唯一来源,本节只定义显示侧消费职责。
显示层取得的当前配置形态:
{
"orientation": 0,
"brightness": 40,
"default_font": "default",
"default_text_size": 12,
"last_mode": "clear",
"low_voltage_protection_enabled": false
}
要求:
- 配置模块必须在初始化显示服务前完成缺失文件创建,或完成全部 legacy 迁移和当前 schema 校验;
DisplayService只消费去掉落盘元数据后的当前业务字段。 - 文件缺失时由配置模块创建默认值;现有文件损坏、含未知字段、版本过高或迁移失败时,配置模块保留原件并阻止服务启动,显示层不得自行回落默认值。
- API 层和
DisplayService都通过同一配置模块读写,不各自维护文件,不感知生产路径,不解析旧 schema,也不自行创建.bak。 - 方向、亮度、默认字体和模式的业务默认值与接口语义保持不变;落盘
schema_version不进入显示状态或 API 响应。 low_voltage_protection_enabled由当前配置 v3 以严格布尔值提供,显示层只消费当前值,不负责迁移或把自动保护状态写回配置。
8. 错误处理(DISPLAY-ERRORS)
8.1 参数错误
必须明确报错:
- 颜色格式错误。
- RGB 通道不在
0..255。 - 方向不是
0/90/180/270。 - 画布帧尺寸不是
64x64。 - RGB 字节长度不是
12288。 - 兼容整帧文字或透明文字层请求的字号不在
1..64。
8.2 驱动错误
真实驱动初始化失败时:
- 服务仍然启动。
- 自动进入模拟驱动或不可用状态。
GET /api/status必须能反映错误。- 前端显示“屏幕驱动不可用”,但页面不能白屏。
8.3 显示错误
显示操作失败时:
- API 返回错误 JSON。
- 日志记录异常堆栈。
- 不让异常导致整个 FastAPI 服务退出。
9. 性能要求(DISPLAY-PERF)
9.1 基本策略
- 避免 Python 层逐点调用 GPIO 或 C ABI。
- 尽量在内存中生成完整
PIL.Image。 - 使用一次 RGB888 整帧提交,并由 C 层预计算 bitplane。
- 统一场景 WebSocket 和旧画布 WebSocket 都只保留最新帧,允许丢弃过时帧。
- 浏览器按元素
id + revision缓存透明层;旧修订响应必须丢弃,不能触发回退合成。 - 拖动或缩放过程中复用缓存层做视觉变换,结束后再请求一次 Pillow 精确层,不能在每个 Pointer Event 上发起渲染请求。
- 任一当前元素层仍待定或失败时不提交最终帧,避免用缺层画面覆盖实屏。
- 复杂动画优先预渲染为逐帧无损 PNG 缓存;播放按不可变摘要路径懒加载,不得把长视频全部展开为 PIL 图像列表。
9.2 核桃派 ZeroW 降载策略
如果出现明显闪烁、刷新慢或 CPU 占用过高:
- 7-bit PWM 是当前面板的固定验收值;不能为了掩盖驱动未达标而静默降低。只有新增明确配置、状态和实屏对比测试后才允许提供 6-bit 模式。
- 先区分逻辑帧率与
panel_scan_rate_hz;默认 100 Hz 逻辑档在有效亮度不高于 20% 时必须达到约 300 Hz 物理扫描,较高亮度保持约 200 Hz。摄像头验证必须在同一次连续连接内等待自动曝光稳定,再检查固定裁剪区是否出现整屏同步黑场、整体周期性亮度调制、固定坏行或固定错色。25 fps 等滚动快门可能把面板扫描拍成随帧移动的窄亮带或暗带;当条带数量随物理扫描率变化、稳定段整屏均值没有同步跳变、低亮度panel_scan_rate_hz >= 285(较高亮度不低于 190)且截止丢失不增长时,这类拍频条带不能单独判为面板闪烁。只有摄像头拍频无法消除歧义时才暂停,请用户直接观察一次是否仍有肉眼可见闪烁。 - 降低
brightness。 - 优先确认 CPU3 绑定、实时优先级、内存锁定和 performance governor 是否实际生效。
- 检查批量 PI register 写、bitplane 预计算和帧边界交换是否引入多余锁或系统调用。
- 减少 WebSocket 帧率。
- 增大文字层请求防抖间隔并复用未变更元素缓存。
- 动画从实时计算改为预渲染。
- 高频显示循环必须从一开始就在 C 中;Python 降载不能替代 C 无负载基准门槛。
10. 与 HUB75 接线资料的一致性(HW-MATRIX)
底层真实驱动必须与本地接线资料一致:64×64、1/32 扫描、ABCDE 行地址、单块不级联。全部 14 根信号集中在 H618 PI bank;其中 13 根由同一个 32-bit data register 批量写入,OE 在同 bank 的 PI14 上复用 PWM4。
| HUB75 信号 | 核桃派物理脚 | H618 GPIO | PI data bit |
|---|---|---|---|
R1 |
29 | PI0 |
0 |
G1 |
31 | PI1 |
1 |
B1 |
33 | PI2 |
2 |
R2 |
35 | PI3 |
3 |
G2 |
37 | PI4 |
4 |
B2 |
8 | PI5 |
5 |
A |
10 | PI6 |
6 |
B |
28 | PI9 |
9 |
C |
27 | PI10 |
10 |
D |
15 | PI11 |
11 |
E |
16 | PI12 |
12 |
CLK |
38 | PI13 |
13 |
OE |
40 | PI14/PWM4 |
14 |
LAT |
36 | PI15/GPIO |
15 |
物理脚 3/5 的 PI8/PI7 专供 /dev/i2c-1 ADC,物理脚 32 的 PI16 保留,不得被显示驱动改为输出。
该接线只允许与 walnutpi-pi-bank-pwm-oe-v2 驱动配套。旧版 LAT=PI14/OE=PI15 接线不得启动新驱动;以后再次改线也不能只改物理接线,必须同步升级硬件映射标识、原生 ABI、测试和本项目文档。
10.1 屏幕输入电压检测边界(HW-SCREEN-VOLTAGE)
屏幕输入端电压使用 M5Stack Unit ADC v1.1(SKU U013-V11、芯片 ADS1110)检测。当前阶段固定边界如下:
- 整个系统已经提供可靠、共地的
5V/GND;本需求不设计稳压、开关、保险、供电时序或由核桃派控制模块供电。 - Unit ADC 的红线接系统
5V,黑线接系统GND,黄线SDA接核桃派物理脚 3 /PI8,白线SCL接核桃派物理脚 5 /PI7。 - 测量端
VIN+并联到屏幕输入端预留的5V测试点,VIN-/GND接同一输入端的系统GND;屏幕负载电流不得经过 Unit ADC 或测量线。 - 该模块只提供屏幕输入端电压数据,不能直接证明屏幕实际亮度、画面是否正常或负载电流大小。
- FastAPI 进程内的独立电压监测组件负责持续读取和缓存,并在锁外把保护指令交给显示输出仲裁器;
DisplayService不直接访问 ADC。ADC 故障不得终止显示或驱动生命周期,取得过成功样本后须保持最后保护而不是误解除。 - 每台设备通过
CONFIG-SCREEN-VOLTAGE独立保存一次比例校准;校准只补偿当前 ADC 与测量点的比例误差,不能替代接线、共地和型号检查。 - 当前实现
DISPLAY-LOW-VOLTAGE-PROTECTION:校准后电压在4.5..4.8V线性限亮,严格低于4.5V覆盖 35% 低电图标,严格高于4.8V经恢复确认撤销。它不实现过压保护、物理关断、BMS 或核桃派关机。 - 完整接线、ADS1110 协议、换算公式和官方资料见
硬件相关资料和硬件的连接/M5Stack Unit ADC v1.1相关内容/;真实硬件验证映射到TEST-ADC-HARDWARE。
11. 后续实现验收清单
底层接口完成后,需要验证:
clear()能清屏。fill((255,0,0))、fill((0,255,0))、fill((0,0,255))显示颜色正确。show_rgb_bytes()能显示最终 RGB 帧,并由调用层区分、保存composition与旧canvas模式。show_text()继续兼容整帧静态文字;透明文字层渲染为 RGBA 且不产生显示副作用。- 像素底层与至少两组透明文字合成后只向
DisplayService提交一张最终 RGB 帧,底层不感知元素结构。 - 纯色、诊断和清屏能独占覆盖当前组合帧,之后重新提交统一场景即可完整恢复。
set_orientation()对纯色以外所有内容生效。- 开机笑脸继承用户方向但不继承用户亮度,动画期间不污染逻辑帧、模式、配置或修订号,退出后恢复用户亮度。
- WiFi 提示能以连续可读速度滚动成功 URL 或断网凭据,断网图标位置稳定;成功/失败切换、取消和关闭均恢复最新用户内容,密码不进入日志。
- 低电压仲裁优先于开机笑脸和用户帧,临界图标为纯黑底红色图形并以 35% 亮度继承方向;限亮、临界和恢复均不污染用户设置或用户修订号。35% 是覆盖实屏保护膜后仍能可靠辨识红色的验收值。
- 同一 boot ID 成功退出后重启服务不重现,下一次整机开机重新显示;无摄像头实屏验收时停下等待用户确认。
- 重启服务后方向和亮度配置仍存在。
- 没有真实屏幕时模拟驱动能运行。
- 真实驱动异常不会导致网页服务无法启动。
- 高频画布更新不会导致帧积压或内存持续上涨。
- Unit ADC 独立脚本的离线自测能验证 ADS1110 配置、符号转换和标称电压换算;未连接实物时不得把硬件读取标记为通过。
- ADC 断开或返回异常时网页服务和
DisplayService继续工作,状态值为空而不是伪造0V;恢复连接后监测组件自动重试。
12. 未来扩展入口
以下内容只定义扩展落点和底层边界,不代表当前阶段必须实现。新增能力时先确认是否仍然服从 DISPLAY-FRAME,再决定是否需要新的驱动或配置编号。
- 滚动文字、动画、时钟、天气、传感器数据等普通内容:优先作为上层元素或图层提供者参与统一合成,底层只负责提交最新最终
64x64 RGB帧;只有出现刷新率或同步问题时才扩展 VSync/动画路径。 - 图片导入、缩放、裁切、调色板和抖动:属于帧生成策略,必须明确缩放/裁切规则,不能在
show_image()中静默拉伸未知尺寸。 - 字体管理和中文字体:扩展
show_text()或text_renderer.py,必须保持字体加载失败可解释,并通过配置模块保存默认字体。 - 启动默认画面、预设场景、亮度曲线:归入
CONFIG-PERSIST和CONFIG-DATA-LIFECYCLE,必须说明默认值、持久数据类别、唯一当前 schema、单向迁移和失败保全。 - 多块屏、不同分辨率、链路级联或新硬件映射:先更新
HW-*和硬件资料,再决定DISPLAY-FRAME是否从固定64x64扩展为可配置尺寸;网页层不得直接处理 GPIO 细节。 - 权限收紧、capabilities、非 root 运行、Docker 或反向代理:属于
DEPLOY-*,但必须验证不会破坏真实驱动初始化、配置读写和字体资源访问。
13. 参考资料
- 本地接线资料:
硬件相关资料和硬件的连接/点阵屏幕相关资料/手动接线说明/核桃派ZeroW_HUB75_接线与首次上电检查.md - 本地资料:
硬件相关资料和硬件的连接/M5Stack Unit ADC v1.1相关内容/README.md - 本地资料:
硬件相关资料和硬件的连接/M5Stack Unit ADC v1.1相关内容/02_ADS1110使用与开发约定_给Codex.md - 核桃派官方
gpioc(MIT):https://github.com/walnutpi/gpioc - 核桃派 ZeroW 参数:https://wiki.walnutpi.com/docs/walnutpi_1/intro/hw-parameter/
- H616/H618 PIO 寄存器依据:板端 device tree
allwinner,sun50i-h616-pinctrl与项目内上游来源记录 - Pillow ImageDraw:https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html