一、先算清一张图真正占多少内存图像处理最危险的误判是只看文件大小。一个几百 KB 的压缩图片解码成 RGBA 后内存约为宽 × 高 × 4字节四千像素长边的图片可能瞬间占用数十 MB。视频抽出多帧、字幕再创建一张临时画布、量化阶段保留 RGB 副本时峰值会继续叠加。本设计关注PixelMap从创建、读取、转换到释放的所有权不讨论具体滤镜视觉效果。目标是让图片序列、视频帧和 GIF 解码帧遵守同一种资源协议并用预算决定能否继续而不是等进程接近 OOM 才被动失败。资源估算方式所有者最晚释放点原始 PixelMapwidth × height × 4解码/抽帧阶段RGB 转换完成ImageSource实现相关创建它的局部作用域PixelMap 创建完成后RGB 工作帧width × height × 3帧处理阶段索引帧生成后字幕 PixelMapwidth × height × 4叠层构建函数像素读回后索引帧width × heightGIF 编码阶段文件写入完成二、所有权协议比 release 调用数量更重要每个资源在任意时刻只能有一个明确所有者。函数返回PixelMap表示所有权转移给调用方函数只读取但不接管时参数名和接口说明必须标记 borrowed。数组中存放多帧并不会自动说明谁释放必须把协议写进类型或封装。export interface OwnedPixelMap { pixelMap: PixelMap owner: decoder | processor | overlay released: boolean } export async function releaseOwned(resource: OwnedPixelMap): Promisevoid { if (resource.released) return resource.released true await resource.pixelMap.release() } export interface PixelMapConsumerT { consume(resource: OwnedPixelMap): PromiseT }released只用于开发期防止重复释放不能替代正确的作用域设计。生产代码更推荐将资源封装在一次性对象中并减少把原始PixelMap暴露给多个模块的机会。UI 如果需要预览应接收独立缩略图或稳定 URI而不是借用正在处理的原始帧。三、单图路径采用最短资源作用域图片序列应逐张解码、转换为较小的 RGB 工作帧然后立即释放原图。不要先把所有 URI 解码成数组再处理。ImageSource与PixelMap都放入try/finally任何转换、读取或取消异常都不会跳过清理。export async function decodeToRgbFrame( uri: string, options: FrameOptions, signal: ExportSignal ): PromiseRgbFrame { const source image.createImageSource(uri) let pixelMap: PixelMap | null null try { signal.checkCancelled() pixelMap await source.createPixelMap({ desiredPixelFormat: image.PixelMapFormat.RGBA_8888 }) return await copyToScaledRgb(pixelMap, options, signal) } finally { if (pixelMap ! null) { await pixelMap.release().catch(() {}) } await source.release().catch(() {}) } }工作帧只保存 RGB 字节、输出尺寸和帧延迟。完成这次复制后后续裁剪、滤镜与量化都不再依赖原始 PixelMap。这样资源边界清楚也能把多张原图的峰值压缩为“一张原图 已缩小 RGB 帧集合”。四、视频帧使用消费即释放的交接视频抽帧器取得帧后交给处理器处理器把像素复制到目标尺寸并在finally中释放。若取消发生在获取之后、消费之前抽帧器仍是所有者交接成功后释放责任转到处理器。这个转换点需要有明确代码而不是靠注释约定。export async function processVideoFrame( frame: PixelMap, options: FrameOptions, signal: ExportSignal ): PromiseRgbFrame { try { signal.checkCancelled() const info await frame.getImageInfo() const budget estimateRgbaBytes(info.size.width, info.size.height) signal.assertMemoryBudget(budget) return await copyToScaledRgb(frame, options, signal) } finally { await frame.release().catch(() {}) } } export async function consumeFrames(frames: AsyncIterablePixelMap): Promisevoid { for await (const frame of frames) { const rgb await processVideoFrame(frame, currentOptions(), exportSignal) rgbQueue.push(rgb) await yieldToUi() } }如果平台接口一次返回帧数组调用方要在任何中途失败时释放“当前帧以及尚未处理的剩余帧”。更理想的改造是让抽帧器提供异步迭代器从源头限制同时存活的 PixelMap 数量。五、内存预算在创建前做决策预算器根据源帧、目标 RGB、字幕叠层和索引帧估算本阶段峰值。估算不追求和系统分配完全相等而是用于做确定性决策降低长边、减少帧数、关闭字幕预览或拒绝继续。所有降级都要反馈给用户不能静默输出低于选择档位的结果。export interface MemoryPlan { sourceBytes: number rgbBytes: number overlayBytes: number indexedBytes: number estimatedPeakBytes: number action: accept | downscale | reduce_frames | reject } export function planMemory(input: MemoryInput, limitBytes: number): MemoryPlan { const sourceBytes input.sourceWidth * input.sourceHeight * 4 const rgbBytes input.frameCount * input.targetWidth * input.targetHeight * 3 const overlayBytes input.hasSubtitle ? input.targetWidth * input.targetHeight * 4 : 0 const indexedBytes input.frameCount * input.targetWidth * input.targetHeight const estimatedPeakBytes sourceBytes rgbBytes overlayBytes indexedBytes return { sourceBytes, rgbBytes, overlayBytes, indexedBytes, estimatedPeakBytes, action: chooseMemoryAction(estimatedPeakBytes, limitBytes) } }预算器还应记录实际目标尺寸、帧数和采取的降级动作供测试与故障定位使用。日志不能包含用户文件路径只记录匿名任务标识、数值和阶段。六、临时画布也必须进入清理清单字幕、绘制和颜色转换常会创建临时 PixelMap。因为它们生命周期很短反而容易在异常分支中遗漏。构建叠层时先创建、绘制、读回再释放绘制失败可以返回null并跳过字幕但绝不能带着未释放画布继续编码。异常位置仍可能存活的资源清理动作结果策略createPixelMap 失败ImageSource释放 Source终止当前图片readPixels 失败PixelMap、Source两者都释放报告坏帧字幕绘制失败临时 PixelMap释放临时画布可降级为无字幕调色板量化取消RGB 帧集合清空大数组引用任务取消GIF 写入失败索引帧、文件句柄释放并关闭文件保留编辑参数七、数组和缓存在阶段结束时主动瘦身JavaScript 引用仍存在时即使原始 PixelMap 已释放RGB 与索引数组也会继续占用内存。每帧完成索引化后把对应 RGB 字段替换为空数组编码完成后清空索引帧列表和调色板引用。不要依赖函数结束后某个不确定时间点的垃圾回收。缓存只保存可复用的小对象例如尺寸计算、颜色索引映射和参数归一化结果。任何以任务 ID 缓存整帧像素的方案都必须设置容量、过期和取消清理。预览缩略图与导出原帧使用不同资源关闭预览时释放缩略图不影响导出任务。八、验证要观察峰值和清理结果实施顺序建议为标注现有所有权给每个创建点补齐finally改成逐帧消费加入内存预算最后处理字幕和编码阶段的大数组清理。每完成一层都运行同一组素材比较峰值而不是只看是否导出成功。验收矩阵包含单张 4K 图片、五十张图片、十秒 1080p 视频、取消任务、坏帧和字幕绘制失败。真机证据需要记录处理前、峰值、结束后三个内存点连续执行三轮后内存应回落到稳定区间同时验证取消后不再报告进度、文件可重新选择、下一次任务可正常启动。PixelMap 操作能力可参考华为 HarmonyOS 开发文档。九、总结PixelMap 内存治理不是在代码末尾多写几个release()而是建立可追踪的所有权、尽量缩短原图作用域、用背压限制并发存活帧并在创建前执行预算。只有资源和字节数组都按阶段交接与清理长视频和多图任务才有机会在真实设备上稳定运行。