
本文详解如何在 canvas 中准确生成适用于嵌入式显示的像素字体 hex 数据,重点解决宽度 >8px 时因扫描方向错误导致字形上下颠倒的问题,并提供可直接运行的列优先、低位在前(lsb-first)编码实现。
在为单色点阵屏(如 OLED、LED 矩阵或定制 LCD)生成像素字体时,数据格式的精确性至关重要。常见规范要求:按列(x 方向)遍历,每列从上到下读取像素(y=0 到 y=height−1),每 8 个像素打包为一个字节(低位在前,即第 0 行对应 bit 0),不足 8 位时补零并立即输出该字节。而原代码中内层循环使用 y = actualHeight - 1; y >= 0; y--(从底向上),导致位序反转——这在 5×7 等小尺寸下因字形对称性可能偶然“蒙混过关”,但在 10×14 等真实尺寸下会严重错位(如字母 A 的下半部被误置到高位字节),最终导出的 HEX 序列与硬件预期完全不符。
正确做法是将内层 y 循环改为 升序遍历(y = 0; y ,确保每一列的顶部像素始终作为字节的最低有效位(LSB)。同时需注意:当列高不为 8 的整数倍时,必须在每满 8 位或到达列末时立即提交当前字节(即使未填满),避免跨列累积——这正是原逻辑中 if (bit === 8 || (y === 0 && bit > 0)) 的误用根源(y === 0 应为 y === actualHeight - 1,且条件逻辑混乱)。
以下是修正后的完整实现(兼容任意宽高,支持 Canvas 实时采样或数组模拟):
// 示例:使用预定义二维数组模拟 Canvas 像素(1=on, 0=off)
const arr = [
[0,0,0,1,1,1,1,0,0,0], // row 0 → bit 0 of byte 0,1,2...
[0,0,0,1,1,1,1,0,0,0],
[0,0,0,1,1,1,1,0,0,0],
[0,0,1,1,0,0,1,1,0,0],
// ... 共 14 行(actualHeight = 14)
];
const actualWidth = arr[0].length; // 10
const actualHeight = arr.length; // 14
const pxlArray = [];
for (let x = 0; x < actualWidth; x++) {
let byte = 0;
let bitPos = 0; // 当前字节内位索引(0~7),对应 LSB-first
for (let y = 0; y < actualHeight; y++) {
const isOn = arr[y][x]; // ✅ 从顶行(y=0)开始读取
byte |= (isOn ? 1 : 0) << bitPos; // 置位到当前 bitPos
bitPos++;
// 每满 8 位 或 到达列末,输出字节并重置
if (bitPos === 8 || y === actualHeight - 1) {
pxlArray.push(byte);
byte = 0;
bitPos = 0;
}
}
}
console.log(pxlArray);
// 输出:[0, 56, 192, 63, 248, 15, 62, 3, 6, 3, 6, 3, 62, 3, 248, 15, 192, 63, 0, 56]若需对接真实 Canvas,仅需替换 const isOn = arr[y][x] 为 Canvas 像素采样逻辑:
// 替换上述 isOn 获取方式: const tempData = tempContext.getImageData(x, y, 1, 1).data; const isOn = tempData[0] === 0 && tempData[1] === 0 && tempData[2] === 0 && tempData[3] === 255;
关键注意事项:
- ✅ 扫描顺序决定字形正确性:必须 x 外层(列)、y 内层升序(行顶→底);
- ✅ 位序严格 LSB-first:第 0 行像素 → bit 0,第 1 行 → bit 1,依此类推;
- ✅ 字节边界即时提交:不可跨列累积,每列独立分组,末尾不足 8 位需补零并输出;
- ⚠️ 避免使用 getImageData 在大画布上高频调用(性能瓶颈),建议预渲染到位图数组再批量处理;
- ? 导出前务必用已知标准字模(如题目中的 10×14 字母 A)验证输出 HEX 是否与硬件文档一致。
通过此方案,您可稳定生成符合嵌入式设备固件要求的像素字体数据,彻底规避因坐标系理解偏差导致的字形翻转问题。










