Harmony os 技术实战|拼豆制图27:用单字符编码承载 50 张 70×70 图纸
标签:Harmony os、ArkTS、资源编码、数据校验、像素图纸
一张 70×70 拼豆图有 4900 个位置。如果把每个位置直接写成包含row、col、colorId、colorCode、hex和isEmpty的对象,资源文件会迅速膨胀,人工审查也几乎不可能。工程采用了更紧凑的形式:每行是一段字符串,每个字符代表空格或色板索引,加载时再展开为完整BeadCell[]。
当前资源层包含 50 张图,每张都有 70 行施工图、16 行预览和最多 16 个颜色。仅施工图就有245,000 个字符位置。单字符编码解决了“源码如何保存”的问题,却没有自动解决运行时内存、非法字符、行宽不一致和色板越界。本篇将编码表、解码过程与资源校验放在同一条链路中分析。
一、资源对象只保存三类信息
每张资源图由颜色数组、完整图纸行和预览行组成:
exportinterfaceAssetChart{colors:string[];chartRows:string[];previewRows:string[];}当前每个chartRows含 70 个长度为 70 的字符串,previewRows含 16 个长度为 16 的字符串。以 50 张图计算:
完整图纸字符数 = 50 × 70 × 70 = 245,000 预览字符数 = 50 × 16 × 16 = 12,800 合计位置数 = 257,800这些数字还没有计入引号、逗号和换行,但已经能说明编码规模。若每个位置都写成对象,同一个colorId、行列坐标和字段名会重复数十万次;字符串行则把重复信息推迟到加载阶段生成。
二、一个字符刚好覆盖 16 色与空格
解码表采用三段规则:
| 字符 | 含义 | 返回索引 |
|---|---|---|
. | 空格 | -1 |
0—9 | 色板前 10 色 | 0—9 |
A—F | 色板后 6 色 | 10—15 |
对应实现保持为纯整数映射:
privatestaticassetColorIndex(token:string):number{if(token==='.'){return-1;}constdigitIndex='0123456789'.indexOf(token);if(digitIndex>=0){returndigitIndex;}constletterIndex='ABCDEF'.indexOf(token);if(letterIndex>=0){return10+letterIndex;}return-1;}因此0不是“无颜色”,而是第 0 个颜色;只有.表示空格。小写a、字母G、空格字符和其他符号都会返回 -1。这个约定让一个 UTF-16 代码单元就能表达一个格子,也把色板上限明确固定为 16。
三、色板顺序就是文件协议的一部分
资源文件只保存十六进制颜色,没有保存色号。仓库解码时按固定顺序补上业务色号:
constcodes:string[]=['H7','G1','D9','A12','F1','D11','H6','H2','H5','E1','M12','H14','F16','F17','M5','G18'];于是字符与色号的关系由数组位置决定:0 -> H7,9 -> E1,A -> M12,F -> G18。如果只调整colors顺序,却没有同步重新编码所有行,图形轮廓仍然完整,颜色却会整体错位。这类错误比解析失败更隐蔽,因为页面仍然能正常渲染。
可以把三者看作一个不可拆分的版本:
字符 token -> palette index -> color code / hex任何一段改变,都应重新核对固定像素样本,而不是只验证数组长度。
四、解码时才创建完整 BeadCell
createCellsFromRows()按行和列遍历字符串,取出一个字符,解析色板索引,再生成业务格子:
privatestaticcreateCellsFromRows(id:string,palette:PaletteColor[],rows:string[]):BeadCell[]{constcells:BeadCell[]=[];for(letrow=0;row<rows.length;row++){constsourceRow=rows[row];for(letcol=0;col<sourceRow.length;col++){consttoken=sourceRow.substring(col,col+1);constcolorIndex=PatternRepository.assetColorIndex(token);constcolor=colorIndex<0||colorIndex>=palette.length?null:palette[colorIndex];cells.push(PatternRepository.createCell(id,row,col,color));}}returncells;}展开后的每个格子拥有稳定坐标和完整颜色信息,页面、统计服务与 PNG 导出服务都不再关心资源字符。这是很有价值的边界:紧凑格式只存在于资源层,业务层始终处理统一模型。
五、非法字符被当作空格,容错也可能掩盖错误
当前实现把两种情况都转换为null颜色:
- 字符无法识别,例如
G或小写a。 - 字符能得到索引,但索引超出当前色板长度。
最终生成的格子会是isEmpty=true。运行时因此不会崩溃,但资源录入错误会表现为图案缺一颗豆,而不是明确的异常。若缺口位于大面积背景中,人工预览很难发现。
更适合资源构建阶段的策略是“严格校验,运行时保底”:
interfaceAssetIssue{row:number;col:number;token:string;reason:string;}开发时扫描全部行并报告精确坐标;正式运行时仍可保留空格回退,避免单个坏字符让整个图库无法打开。两层策略各自解决不同问题。
六、行宽不一致会让二维坐标悄悄错位
图纸宽度当前取第一行长度:
privatestaticrowWidth(rows:string[]):number{if(rows.length===0){return0;}returnrows[0].length;}解码循环却按每一行自己的sourceRow.length追加格子。假设宽度记录为 70,而第 20 行只有 69 个字符,从该行之后,扁平数组按width=70重新分组时就会整体左移;若某行多了一个字符,后续又会整体右移。
这不是单行少一格,而是从错误点开始污染余下所有行。因此资源校验至少要满足:
chartRows.length == 70 every chartRows[row].length == 70 previewRows.length == 16 every previewRows[row].length == 16 colors.length <= 16宽高如果未来不固定,也应先从元数据得到期望值,再验证每一行,而不是默认相信第一行。
七、紧凑资源不等于低运行时内存
一次PatternRepository.getPatterns()会创建全部 50 张完整图和预览图:
每张格子对象数 = 70 × 70 + 16 × 16 = 5,156 50 张对象数 = 5,156 × 50 = 257,800资源文件确实紧凑,但调用后仍会展开为 257,800 个BeadCell对象。每个对象还携带字符串 ID、色号和颜色值,真实堆占用远高于字符数量。
这说明“存储格式”和“加载策略”是两个独立维度:
- 单字符行减少源码体积与资源维护成本。
- 按需解码或缓存最近访问项,才减少首屏对象分配。
若首页只展示缩略图,可以先解码 16×16 预览;进入编号页时再解码对应 70×70 图纸。这样仍然复用同一套资源协议,却不必在应用初始化时展开所有完整矩阵。
八、50 个 if 分支可以继续收敛,但先保留静态类型
当前PatternAssetCharts.get(id)通过 50 个明确的 ID 分支返回资源。优点是结构直观、构建时可获得类型约束;缺点是文件超过五千行,查找、合并和人工校对成本高。
可演进为按分类拆分,再用显式映射汇总:
constanimeCharts:Record<string,AssetChart>={/* ... */};constgameCharts:Record<string,AssetChart>={/* ... */};staticget(id:string):AssetChart|null{returnanimeCharts[id]??gameCharts[id]??null;}重点不是换一种语法,而是让每个资源仍然经过同一校验入口。若直接读取外部 JSON,还需要处理字段缺失、字符编码和类型收窄,不能因为文件更短就省略边界验证。
九、资源扫描应输出可复算摘要
对 50 张图执行资源扫描时,建议每张至少输出以下摘要:
id=anime-1 palette=16 chart=70x70 preview=16x16 invalidTokens=0 raggedRows=0 usedColorIndexes=0..15总览再核对:
- 图案 ID 是否与仓库中的 50 个种子一一对应。
- 是否存在资源但没有种子,或种子没有资源。
- 字符索引是否超出色板。
- 空格比例是否异常突变。
- 预览与完整图的主色分布是否相近。
这些摘要比逐行阅读 70 个长字符串更有效,也能在资源更新后快速发现结构变化。
十、编码上限变化时必须显式升级协议
0-9A-F的优势是一个字符覆盖 16 色。如果未来需要 20 色,不能随意把G-J接在后面而不记录版本,因为旧解码器会把新字符当作空格。可以选择:
- 明确扩展字母表并增加资源版本。
- 改用两字符定长索引。
- 使用调色板压缩后的二进制资源。
对当前 16 色业务,单字符方案足够简单且可读。真正需要提前设计的是版本识别和失败方式,而不是为了假设中的规模立即引入复杂格式。
小结
Harmony os 应用中的图纸资源可以很紧凑:用.表示空格、0-9A-F表示 16 个色板索引,50 张 70×70 图与 16×16 预览共承载 257,800 个字符位置;加载后再统一展开为BeadCell[],让页面、统计和导出服务不感知底层编码。
这套方案真正的工程边界有三条:色板顺序属于协议,不能单独调整;每行宽度必须在解码前验证;紧凑存储不能替代按需加载。把资源扫描、运行时回退和对象展开分开设计,单字符编码才能同时获得可维护性、可定位性与稳定渲染结果。
