Harmony os 技术实战|拼豆制图50:不打开相册也能验证高清 PNG 导出契约
高清编号图的导出链路包含位图绘制、PNG 编码、临时文件写入和系统相册弹窗。若每次验证都必须手动打开相册不仅反馈慢而且很难定位问题究竟发生在像素渲染、编码还是系统交互。本文为PatternExportService划出可测试边界先在本地验证 PNG 字节、尺寸和关键像素再用少量集成用例覆盖临时文件与相册保存。这样既保留真实系统流程也能让大部分回归在几秒内暴露。一、导出不是一个不可拆分的黑盒当前流程可以概括为Pattern → BitmapCanvas → PixelMap → ImagePacker.packToData() → cacheDir 临时文件 → showAssetsCreationDialog() → 清理临时文件前四步是确定性较强的内容生成后两步依赖系统上下文和用户交互。若它们全部藏在savePatternToGallery()中单元测试只能绕过整个服务或驱动完整系统弹窗。二、先定义可验证的输出契约不要把“调用没有抛异常”当作成功。对于本项目的编号图至少需要守住这些结果字节以 PNG 签名开头IHDR 中的宽高符合Pattern和布局常量计算画布不是空白或全透明网格内代表色与图纸格子颜色一致坐标轴、图例和底部区域被包含在画布尺寸中相册边界收到以.png结尾的临时文件。契约应围绕用户可观察结果建立而不是绑定某个私有方法名。将来即使BitmapCanvas换成其他绘制实现只要签名、尺寸、像素语义与保存边界继续成立测试就不应因为内部重构而全部失效。这些条件组合在一起才叫“导出了一张可用编号图”。三、把 PNG 渲染暴露为独立能力renderPng()若是完全私有测试只能绕远路。可以把渲染职责提取到独立类exportinterfaceRenderedPng{bytes:ArrayBuffer width:numberheight:number}exportclassPatternPngRenderer{staticasyncrender(pattern:Pattern):PromiseRenderedPng{constsizePatternLayout.calculateSize(pattern)constcanvasPatternCanvasPainter.draw(pattern,size)constbytesawaitPatternPngPacker.pack(canvas)return{bytes,width:size.width,height:size.height}}}保存服务只消费RenderedPng。这不是为了测试而复制实现而是让“生成内容”和“把内容交给系统”形成真实的职责边界。四、八个字节就能确认 PNG 签名PNG 文件固定以八个字节开头。无需解码整张图片即可完成第一层断言constPNG_SIGNATURE:number[][137,80,78,71,13,10,26,10]exportfunctionhasPngSignature(buffer:ArrayBuffer):boolean{constbytesnewUint8Array(buffer)if(bytes.lengthPNG_SIGNATURE.length){returnfalse}for(leti0;iPNG_SIGNATURE.length;i1){if(bytes[i]!PNG_SIGNATURE[i]){returnfalse}}returntrue}这条用例能拦住空缓冲区、错误格式和编码器返回值接错等问题。五、读取 IHDR 验证真实宽高PNG 签名后第一个数据块通常是 IHDR宽高各占四个字节并使用大端序。测试辅助函数可以直接读取exportinterfacePngSize{width:numberheight:number}functionreadUint32BE(bytes:Uint8Array,offset:number):number{return((bytes[offset]24)0)(bytes[offset1]16)(bytes[offset2]8)bytes[offset3]}exportfunctionreadPngSize(buffer:ArrayBuffer):PngSize{constbytesnewUint8Array(buffer)return{width:readUint32BE(bytes,16),height:readUint32BE(bytes,20)}}测试时将这里读出的值与PatternLayout.calculateSize(pattern)对比能发现图例行数、边距或网格尺寸修改后编码输出没有同步的问题。六、尺寸断言要覆盖不同图纸只测一张方形小图容易遗漏公式错误。至少准备三组固定夹具图纸目的关键关系1×1、1 色最小边界所有固定边距仍存在8×5、3 色非方形宽高不能互换70×70、多色业务上界缓冲区和图例高度可控尺寸公式应单独可测constexpectedWidthmarginLeftpattern.width*cellSizemarginRightconstlegendRowsMath.ceil(pattern.colorStats.length/legendColumns)constexpectedHeightmarginToppattern.height*cellSizeaxisBottomlegendToplegendRows*legendRowHeightwatermarkHeight非方形用例尤其重要它能快速暴露把width误用于纵向计算的错误。七、关键像素抽样比整图快照更稳定若每次都比较整张 PNG 字节编码器元数据或压缩策略变化也可能导致失败。更稳定的做法是把BitmapCanvas的像素缓冲暴露给绘制层测试抽样少数有语义的位置exportinterfacePixelProbe{x:numbery:numberexpected:RgbaColor}exportfunctionpixelAt(pixels:Uint8Array,width:number,x:number,y:number):RgbaColor{constoffset(y*widthx)*4return{red:pixels[offset],green:pixels[offset1],blue:pixels[offset2],alpha:pixels[offset3]}}推荐抽样纸张背景、首个格子中心、主网格线和图例色块中心。它们分别覆盖背景、数据色、坐标结构和统计区。八、不要用单个像素判断文字是否存在像素字形包含空洞某个固定点可能恰好落在空白处。验证坐标数字或水印时可以统计一个小区域内非背景像素数量exportfunctioncountDifferentPixels(pixels:Uint8Array,canvasWidth:number,area:Rect,background:RgbaColor):number{letcount0for(letyarea.top;yarea.toparea.height;y1){for(letxarea.left;xarea.leftarea.width;x1){if(!sameColor(pixelAt(pixels,canvasWidth,x,y),background)){count1}}}returncount}断言区域内像素数大于一个小阈值比依赖某个字形笔画位置更抗变化。九、相册能力通过网关隔离系统弹窗仍然要验证但可以让保存服务依赖一个窄接口exportinterfaceGalleryGateway{savePng(tempFilePath:string):Promisevoid}exportclassPatternExportService{constructor(privategallery:GalleryGateway){}asyncexport(context:common.UIAbilityContext,pattern:Pattern):Promisevoid{constrenderedawaitPatternPngRenderer.render(pattern)consttempPath${context.cacheDir}/${SafeFileName.forPattern(pattern)}.pngtry{awaitTempFileWriter.write(tempPath,rendered.bytes)awaitthis.gallery.savePng(tempPath)}finally{awaitTempFileWriter.removeIfExists(tempPath)}}}单元测试传入记录路径的假网关设备集成用例再使用photoAccessHelper实现职责非常清楚。十、临时文件必须验证成功与失败两条路径保存成功后清理临时文件很常见但系统弹窗拒绝或抛错时更容易遗漏。应分别验证写入成功、相册保存成功、最终文件不存在写入成功、相册保存失败、最终文件仍被清理写入失败时不调用相册网关清理失败不覆盖更早的主要错误信息。try/finally是实现基础调用次数和路径顺序则需要测试来固定。十一、真机或模拟器只承担系统边界验证本地测试已经覆盖字节和布局后系统侧只需关注少量问题保存弹窗能否打开用户确认后图片是否进入相册用户取消是否返回可理解状态应用切后台再返回时导出锁是否释放70×70 图纸的等待时间是否可接受。不要在系统用例里重新逐像素证明绘制算法那会让失败定位变慢。十二、落地检查清单PNG 渲染可脱离相册调用单独执行输出通过八字节签名断言IHDR 宽高与布局公式一致最小、非方形和业务上界图纸均有用例关键像素覆盖背景、格子、网格和图例相册交互被窄接口隔离成功与失败路径都清理临时文件系统用例只验证真实平台边界。每次调整格子尺寸、页边距、图例列数或水印区域都应先更新布局期望再运行最小、非方形和业务上界三组用例。若只验证常用的 70×70 图纸固定边距错误和宽高互换仍可能被对称尺寸掩盖。总结“导出成功”不是一个按钮提示而是一组可以逐层证明的契约。把绘制、编码、临时文件和相册网关拆开后大部分问题无需打开相册就能快速定位少量系统用例则继续保证真实保存体验。Harmony os 图片导出链路因此既可验证又不会脱离平台实际能力。标签Harmony os、ArkTS、PNG、ImageKit、自动化测试
