ImageSource:动图超分前选帧确认【鸿蒙心迹】
图片增强入口收到一张旋转展示商品的 GIF,预览能显示,后续也拿到了 PixelMap。界面如果马上把按钮改成“开始增强”,一个决定就被悄悄做掉了:用户选中的十二帧动画,被入口解释成了一张静态图片。这类问题适合在模型调用之前解决。本文设计一个名为 FrameGate 的输入确认页,把帧数、当前选择和静态处理确认连在一起。示例只实现超分前的输入选择,增强能力沿用产品已有接入;代码依据官方接口说明编写,本文没有提供实机运行或模型效果测试结果。
## 一、入口先回答“这次要处理哪一张”FrameGate 的样例文件叫 turntable.gif,素材标识为 gif-018。为便于解释,约定它有十二帧,每帧画布为 640×640,内容是一只转动展示的青蓝色纸鸟。这组数字是演示输入约定,需要开发者准备对应测试素材,不能当作接口执行后的测量数据。页面文件使用 FrameConfirmPage.ets,图片读取和确认判断放在 FrameGate.ets。选择器取得访问授权并创建 ImageSource 后,先交给 inspectSource;确认页保留这个实例,之后选帧也使用它,避免将上一张图片的帧数配给下一张图片。华为的动图解码指导将读取帧数、获取时序信息和按索引解码列为不同步骤。这里采用其中的输入判断思路,ArkTS 方法签名另按 OpenHarmony 官方 ImageSource 参考核对,不将 C 接口名直接搬进 ArkTS。下面先建立与图片源绑定的记录。帧数读取失败时应把错误交给页面,让用户重新选择,不能在异常分支里填入一帧继续处理。tsimport { image } from '@kit.ImageKit';interface FrameSource { assetId: string; source: image.ImageSource; frameCount: number;}async function inspectSource(source: image.ImageSource, assetId: string): Promise<FrameSource> { const frameCount = await source.getFrameCount(); if (!Number.isInteger(frameCount) || frameCount < 1) { throw new Error('FRAME_COUNT_INVALID'); } console.info(`asset=${assetId} count=${frameCount}`); return { assetId, source, frameCount };}````getFrameCount()` 返回的是异步结果,`frameCount` 在成功返回之前没有业务含义。额外的正整数判断是应用自己的防御条件,`FRAME_COUNT_INVALID` 也是自定义错误标识,不是系统错误码。损坏文件、无法识别的内容应显示读取失败,不能被归类成普通静态图。文件名在这里只用于展示。产品可以先按支持范围筛选输入类型,但“有多少帧”仍以成功解析的结果为准。对本篇约定的 GIF、WebP 输入,帧数大于一便进入选帧确认;以后接入其他多图容器时,需要重新定义产品语义,不能直接把每一种多图文件都称为动画。文案也应在这一层分清楚。“支持导入动图”可能让用户理解成输出仍能播放,而当前产品实际提供的是从动图中选择一个画面。入口按钮可以写成“选择图片或动图帧”,识别到多帧来源后再补充说明。技术上成功获得一个对象,仍然需要产品语言解释这个对象代表的结果。帧数读取成功也不等于后续指定帧一定能解码。两步分别保留失败提示更容易定位问题:前一步失败说明尚未建立有效来源信息,后一步失败说明当前选择没有形成可用预览。不要把后一种错误吞掉后沿用上次选择,否则用户看到的是旧图片,提交记录却可能已经更新。## 二、界面上的第四帧,对应代码里的三帧号最容易在交互文字和接口参数之间错一位。[官方 DecodingOptions 定义](https://github.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis-image-kit/arkts-apis-image-i.md)说明,`index` 从零开始,默认值为零。当前样例选择第四帧,因此参数必须是 `index=3`;把页面文字里的四直接传入,就会读取第五帧。这段代码只解码用户正在查看的一个索引,并把选择结果作为独立对象返回。页内必须串行调用它;读取期间禁用上一帧、下一帧和重新导入按钮,防止同一个图片源并发解码。tsinterface FrameSelection { assetId: string; frameCount: number; index: number; pixelMap: image.PixelMap; width: number; height: number; confirmedIndex: number;}async function decodeSelection(record: FrameSource, index: number): Promise { if (!Number.isInteger(index) || index < 0 || index >= record.frameCount) { throw new Error(‘FRAME_INDEX_RANGE’); } const pixelMap = await record.source.createPixelMap({ index }); try { const info = await pixelMap.getImageInfo(); const item: FrameSelection = { assetId: record.assetId, frameCount: record.frameCount, index, pixelMap, width: info.size.width, height: info.size.height, confirmedIndex: record.frameCount === 1 ? index : -1 }; console.info(asset=${item.assetId} index=${index} + size=${item.width}x${item.height}); console.info(asset=${item.assetId} state= + (canSubmit(item) ? ‘READY’ : ‘WAIT_CONFIRM’)); return item; } catch (error) { await pixelMap.release().catch(() => { console.error(‘preview release failed’); }); throw error; }}createPixelMap({ index })` 的结果交给 `getImageInfo()` 获取实际像素尺寸,页面据此展示宽高。这里没有指定缩放尺寸,也没有为了填满预览框去改写输入像素。`confirmedIndex=-1` 表示多帧来源尚未确认;单帧图片则可以把当前索引直接作为已确认值。这个对象同时装着预览和索引。切换帧时创建新对象,旧确认不会自动带过去。图片源的并发限制及对象释放要求可参见前述 ImageSource 参考;[PixelMap 官方参考](https://github.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis-image-kit/arkts-apis-image-PixelMap.md)另规定了信息读取与释放接口。片段中的异常分支负责清理尚未交付的预览,正常返回后的对象由确认页持有。页内还要处理切换失败:保留上一帧画面时,也要继续显示上一帧号码;若要展示新号码,应先清空旧预览并保持不可提交。把新号码与旧图片短暂拼在一起,会让确认按钮替错误的画面作出决定。上一帧和下一帧采用有边界的选择方式:第一帧禁用上一帧,最后一帧禁用下一帧,避免自动回绕改变用户的空间判断。十二帧样例的可选索引为零到十一,页面号码则是一到十二。两套数值只在显示边界转换,内部记录始终存零基索引,不额外维护另一份可写的“显示帧号”。## 三、确认要跟着当前选择走“我知道会变成静态图片”不能做成一个全局开关。用户确认第四帧后,再切到第六帧,原来的确认已经失去对应对象。FrameGate 让确认值留在当前 `FrameSelection` 内,提交入口每次重新比较。下面三个方法属于应用层逻辑。确认按钮触发 `confirmStatic`,已有增强入口取图前调用 `requireReady`;尚未确认时,入口拿不到可提交的返回值。```tsfunction canSubmit(item: FrameSelection): boolean { return item.frameCount === 1 || item.confirmedIndex === item.index;}function requireReady(item: FrameSelection): image.PixelMap { if (!canSubmit(item)) { throw new Error('STATIC_CONFIRM_REQUIRED'); } return item.pixelMap;}function confirmStatic(item: FrameSelection): void { item.confirmedIndex = item.index; console.info(`asset=${item.assetId} state=READY index=${item.index}`);}export { FrameSource, FrameSelection, inspectSource, decodeSelection, requireReady, confirmStatic };requireReady 返回已有 PixelMap,没有复制图片。接入增强流程时,需要约定借用时段:处理结束前确认页不能释放它,也不能允许用户切换输入。页面离开时先阻止新操作,等待进行中的解码或消费结束,再解除显示引用,释放不再使用的 PixelMap 与 ImageSource。释放动作本身仍需捕获错误,避免清理异常覆盖原来的失败原因。FrameSelection应封装在页面控制逻辑内,外部只通过选帧和确认方法操作。这样confirmedIndex 才能代表一次明确的用户选择。ArkUI 页面另用自身状态变量刷新提示与按钮,不应指望普通接口对象的字段变化自动通知界面。确认框弹出期间应锁定当前预览,或者在用户点确认时再核对弹窗对应的选择对象仍是页面当前对象。不能让用户在弹窗背后换帧,然后用一次点击确认另一个画面。代码里的等值比较提供的是状态条件,交互层仍要保证这次点击确实针对眼前那张图片。*上图为 AI 生成的 DevEco Studio 开发界面演示,左侧工程为 FrameGate,中间突出确认判断,右侧显示确认页,底部日志用于说明状态关系,不是真实 IDE 截图。*## 四、调试时先盯住三条信息样例走到第四帧时,预期可核对三条日志:asset=gif-018 count=12、asset=gif-018 index=3 size=640x640、asset=gif-018 state=WAIT_CONFIRM。它们分别回答来源包含多少帧、当前真正解码了哪帧、这帧是否已经得到确认。界面这时显示“第4帧 / 共12帧”“待确认静态处理”,并写清楚“只增强第4帧,动画不会保留”。用户点“确认按静态图处理”后,预期出现 asset=gif-018 state=READY index=3`。READY 只表示输入选择就绪,超分是否成功还需要后续能力返回结果,不能把这两种状态合并显示。
*上图为 AI 生成的手机运行效果演示,展示确认前的页面。状态栏统一为 10:18、电量 86%;画面中的 640×640、十二帧和第四帧均使用本文演示约定,超分任务尚未启动。*实际验证时,可以围绕四个动作检查:未确认直接提交应被拦住;确认后换帧应重新等待;输入索引十二应在解码前被拒绝;帧数读取失败应进入错误提示。还需要使用内容明显不同的测试帧,核对第四帧画面确实对应索引三。只比较日志数字,发现不了素材本身装错的情况。验收素材还应包含单帧图片和两个不同的多帧文件。前者验证普通图片不会被多余确认阻塞,后者验证换来源后旧确认不会残留。记录时同时保留素材标识、内部索引和可见帧号,就能把错误定位到来源读取、索引转换或确认衔接,而不用从模型输出一路倒查。如果目标是保留完整动画,就应另行设计所有帧的处理、时序和输出编码流程。本篇的输入页用于用户主动选择某一画面的静态增强,确认文案也围绕这个结果展开。把选择说清楚,后续展示的每一张增强图片才有确定的来源。
更多推荐





所有评论(0)