欢迎光临
我们一直在努力

【共创季稿事节】用 HarmonyOS 6.1 做一款老照片修复工坊:本地滤镜引擎 + AI 修复顾问实战

【共创季稿事节】用 HarmonyOS 6.1 做一款老照片修复工坊:本地滤镜引擎 + AI 修复顾问实战

在这里插入图片描述

一、为什么是老照片修复,为什么是 HarmonyOS 6.1

家里的老相册翻出来,总有那么几张照片——泛黄、开裂、划痕密布,甚至因为受潮而边缘卷曲发霉。它们记录的往往是最珍贵的瞬间:祖辈年轻时的合影、父母的婚礼照、自己幼年的第一张照片。想让这些照片"焕发新生",过去要么花钱找专业修复师,要么在电脑上用 Photoshop 折腾半天。

我想做一件更轻量的事:一个手机(或鸿蒙 PC)里随手就能用的老照片修复工坊——选一张照片,几个滤镜按钮点一点,就能大幅改善视觉效果;如果拿不准用什么滤镜,还能问问 AI。

而这次开发,我特意选择在最新的 HarmonyOS 6 环境下进行。打开 DevEco Studio 的设备选择器,能直接看到一整批 HarmonyOS 6.1.0(API 23)的模拟器和真机型号——Mate 80 Pro、Mate X7、Pura 90、MatePad Pro 13、MateBook Pro:

DevEco Studio 设备选择器,可见 HarmonyOS 6.1.0 全系列设备

从这张截图能直观感受到 HarmonyOS 6 生态的覆盖广度——手机、折叠屏、平板、鸿蒙 PC 一应俱全,SDK 版本统一在 6.1.0(23)。这意味着我用一套代码,就能面向这一整代设备做验证。本文最终选择 Mate 80 Pro(HarmonyOS 6.1.0,API 23) 作为主力调试设备。
在这里插入图片描述

先看成品效果。这是应用在 DevEco Studio 里跑起来的整体界面——左侧是工程结构,中间是编辑器,右侧模拟器里展示的是修复前后的对比效果:

DevEco Studio 中运行的时光胶片应用,修复前后对比一目了然

一张严重破损、泛黄、带折痕裂纹的老照片(五位女性在塔前的合影),经过修复后清晰度和色彩明显改善。下面我们完整拆解这个应用是怎么做出来的。

二、HarmonyOS 6.1 带来了什么

在正式讲代码之前,有必要说说这次选择 HarmonyOS 6.1(API 23)对这个项目的直接影响。相比早期版本,我在实际开发中感受到的变化主要有三点:

其一,ArkUI 的图像效果通用属性更完善。 brightness()、contrast()、saturate()、grayscale()、blur()、invert() 这套滤镜属性在新版本里覆盖了绝大多数常见调色需求,直接挂在 Image 组件上就能实时生效,不需要接入复杂的图像处理库或做离线渲染——这也是本文"本地实时滤镜引擎"能够如此轻量实现的基础。

其二,设备生态的进一步统一。 从设备选择器能看到,HarmonyOS 6 把手机、折叠屏、平板、PC(2in1)的 API 版本完全统一到了 6.1.0(23),意味着"一次开发、多端部署"的一致性比以前更强——不再需要为不同设备类型做太多 API 兼容判断。

其三,权限与媒体库访问更细粒度。 photoAccessHelper.PhotoViewPicker 这类系统选择器组件在新系统上响应更快、体验更流畅(本文后面会有一个关于它的踩坑记录,也和这套机制有关)。

基于这些能力,我把整个应用的技术路线定为:不依赖任何第三方图像处理库,纯用 ArkUI 原生能力做本地实时滤镜;AI 只负责"建议",不负责"渲染"。这个分工在后面会反复提到,是整个项目最重要的设计决策。

三、需求与架构

3.1 核心流程

应用的主线很简单:选片 → 预览对比 → 应用滤镜/听取 AI 建议 → 保存。围绕这条主线,我设计了三个 Tab:

  • 🎞️ 工坊:核心操作区,选片、对比、滤镜库、AI 顾问、保存
  • 🖼️ 相册:已保存的修复作品,网格浏览
  • 👤 我的:关于说明、AI 服务配置

3.2 工程结构

entry/src/main/ets/
├── entryability/EntryAbility.ets 入口:初始化、沉浸式全屏、安全区
├── common/
│ ├── Theme.ets 暖褐胶片怀旧风主题
│ ├── AIConfig.ets 模型配置 + 修复顾问提示词
│ ├── FilterPresets.ets 滤镜参数与 7 种预设风格库
│ ├── FilterImage.ets 应用滤镜的图片组件
│ └── CompareSlider.ets 前后对比拖拽组件(备用)
├── model/
│ ├── PhotoStore.ets 修复作品数据模型 + 持久化
│ ├── PhotoSession.ets 当前选中照片会话态
│ └── RestoreAI.ets AI 修复顾问服务 + JSON 解析
└── pages/
├── Index.ets 三 Tab 主壳
├── WorkshopTab.ets 工坊首页
├── GalleryTab.ets 我的相册
└── ProfileTab.ets 我的/设置

分层原则和我之前几个项目一致:UI 层不碰网络,AI 交互收敛在 RestoreAI,滤镜参数与渲染收敛在 common 层。这样即使后续要换一套滤镜算法或换模型,改动范围都可控。

四、本地实时滤镜引擎:不依赖任何第三方库

这是整个应用最核心的技术决策——修复效果不靠服务器渲染、不靠离线图像处理,纯用 ArkUI 原生的图像效果通用属性实时完成。

4.1 滤镜参数模型

先定义一套滤镜参数结构,覆盖亮度、对比度、饱和度、灰度、模糊、反色六个维度:

export interface FilterParams {
brightness: number; // 1 为不变,0.5~1.6
contrast: number; // 1 为不变,0.5~1.8
saturate: number; // 1 为不变,0~2
grayscale: number; // 0 为不变,0~1
blur: number; // 0 为不变(去噪柔化),0~3
invert: number; // 0 为不变,0~1(特效用,默认不开)
}

export function defaultParams(): FilterParams {
return { brightness: 1, contrast: 1, saturate: 1, grayscale: 0, blur: 0, invert: 0 };
}

4.2 七种预设风格

围绕老照片修复的常见场景,我调了 7 组预设参数:

export const PRESETS: FilterPreset[] = [
{ id: 'original', name: '原图', icon: '⚪️', desc: '不做任何处理',
params: { brightness: 1, contrast: 1, saturate: 1, grayscale: 0, blur: 0, invert: 0 } },
{ id: 'warm', name: '暖阳怀旧', icon: '🌇', desc: '提亮泛黄照片,找回暖意',
params: { brightness: 1.15, contrast: 1.1, saturate: 1.25, grayscale: 0, blur: 0, invert: 0 } },
{ id: 'bw', name: '黑白经典', icon: '🎞️', desc: '褪去岁月色彩,只留光影',
params: { brightness: 1.05, contrast: 1.2, saturate: 0, grayscale: 1, blur: 0, invert: 0 } },
{ id: 'cool', name: '冷调蓝调', icon: '🌙', desc: '低饱和冷色,静谧复古',
params: { brightness: 1.0, contrast: 1.15, saturate: 0.7, grayscale: 0, blur: 0, invert: 0 } },
{ id: 'soft', name: '柔光修复', icon: '🕊️', desc: '柔化划痕颗粒,提亮暗部',
params: { brightness: 1.2, contrast: 0.95, saturate: 1.05, grayscale: 0, blur: 0.6, invert: 0 } },
{ id: 'high', name: '高对比', icon: '⚡️', desc: '找回清晰轮廓与层次',
params: { brightness: 1.05, contrast: 1.4, saturate: 1.15, grayscale: 0, blur: 0, invert: 0 } },
{ id: 'film', name: '复古胶片', icon: '📷', desc: '模拟老式胶片的颗粒质感',
params: { brightness: 1.1, contrast: 1.25, saturate: 1.3, grayscale: 0, blur: 0.3, invert: 0 } }
];

每一组参数背后都有具体的修复思路,比如「柔光修复」专门针对划痕多的老照片——用轻微的 blur(0.6)柔化颗粒噪点,同时提亮暗部(brightness: 1.2)、略降对比度(contrast: 0.95)避免划痕在高对比下更显眼。

4.3 应用滤镜的图片组件

有了参数,渲染就非常直接——把参数一一对应到 Image 组件的图像效果属性上:

@Component
export struct FilterImage {
@Prop source: ResourceStr = '';
@Prop params: FilterParams = { brightness: 1, contrast: 1, saturate: 1, grayscale: 0, blur: 0, invert: 0 };
imgWidth: Length = '100%';
imgHeight: Length = 260;
imgRadius: number = 16;

build() {
Image(this.source)
.width(this.imgWidth).height(this.imgHeight)
.borderRadius(this.imgRadius)
.objectFit(ImageFit.Cover)
.brightness(this.params.brightness)
.contrast(this.params.contrast)
.saturate(this.params.saturate)
.grayscale(this.params.grayscale)
.blur(this.params.blur)
.invert(this.params.invert)
}
}

这套写法有个巨大的优势:参数变化时,界面是零延迟实时刷新的,因为它本质上是 GPU 侧的图层滤镜渲染,不涉及像素级重新编解码。用户点一下滤镜库里的按钮,效果立刻呈现,完全没有"加载中"的等待感。这也是为什么我说这是"本地实时滤镜引擎"——没有网络请求,没有算法计算延迟,一切都在渲染管线里完成。

下图是应用运行时,选择"原图"预设、看 AI 修复顾问推荐结果的界面:

滤镜库与照片预览细节

可以看到,界面上方是修复前后对比区(上:修复前,下:修复后),下方是横向滚动的滤镜库,当前选中"原图"(橙色高亮边框)。这个交互设计让用户一眼就能看到自己在哪种滤镜下的效果,切换成本几乎为零。

4.4 一个关于 @Prop 的重要踩坑

这里必须重点记录一个真实踩过的坑,因为它直接导致了一次"看起来完全没反应"的诡异 bug。

我最初写 FilterImage 组件时,把 source 声明成了普通成员变量:

// 错误写法
@Component
export struct FilterImage {
source: ResourceStr = ''; // 漏掉了 @Prop!
@Prop params: FilterParams = defaultParams();
// …
}

结果是:用户从相册选完照片后,父组件里 this.photoSrc 这个 @State 变量确实变了,但由于 source 没有用 @Prop 装饰,ArkUI 不会把父组件状态的变化同步传递给子组件——子组件只在初次创建时读取了一次 source 的初始值,之后就再也不会更新。所以界面上无论怎么选照片,显示的永远是最初那张示例图,看起来就是"选了但没反应"。

修复很简单——把 source 也加上 @Prop:

@Component
export struct FilterImage {
@Prop source: ResourceStr = ''; // 加上 @Prop 后父子状态才能同步
@Prop params: FilterParams = defaultParams();
// …
}

这是 ArkUI 状态管理里最容易踩、也最隐蔽的坑之一:@Prop/@State/@Link 这些装饰器不是可选的装饰,而是数据流同步机制的开关。任何要接收父组件动态数据的子组件属性,都必须显式加装饰器,否则子组件拿到的只是"创建那一刻的快照"。这个坑我在这次项目里付出了不小的调试代价,也算是给同样在学 ArkTS 的朋友提个醒。

五、AI 修复顾问:一次关于"诚实"的设计决策

这是我认为整个项目里最值得讲的一部分,不是技术上多难,而是产品诚信上的一次自我纠偏。

5.1 一个容易被忽略的能力边界问题

做"AI 照片修复"类应用时,很容易给用户一种错觉:AI 分析了你的照片像素,帮你识别出了哪里该修。但事实是,我接入的百炼大模型(qwen-plus)是一个纯文本模型,它根本看不到照片本身。如果 UI 上写"AI 智能分析你的照片",这就是一种误导性宣传。

所以我做了一个明确的设计取舍:AI 顾问只基于用户对照片的文字描述给建议,而不是分析图像像素。这个边界,我在系统提示词的注释里写清楚,也在应用的「我的」页面里向用户明确说明。

5.2 提示词设计:让文字描述转化为可执行参数

系统提示词里,我把这个能力边界作为注释写在代码里,提醒未来维护者(包括我自己)不要在 UI 文案上越界:

/**
* 说明:当前模型为纯文本模型,不具备图像识别能力,
* 因此"AI 修复建议"基于用户对照片的文字描述给出滤镜与参数建议,
* 而非直接分析像素——这一点在 UI 文案中会明确说明,不做误导性宣传。
*/

export class AIConfig {
static readonly systemPrompt: string =
'你是一位专业的老照片修复与调色顾问。用户会用文字描述一张老照片的年代、状况和场景,' +
'你需要给出修复建议。必须严格只输出 JSON,不要输出任何多余文字、解释或 markdown 代码块标记。' +
'JSON 格式如下:' +
'{"filterName":"推荐的滤镜风格名称(如:暖阳怀旧/黑白经典/冷调蓝调/柔光/高对比/复古胶片之一)",' +
'"reason":"推荐理由,50字以内",' +
'"brightness":0到1之间的小数(建议亮度增益,1为不变,可给0.9-1.3),' +
'"saturate":0到2之间的小数(建议饱和度,1为不变),' +
'"contrast":0到2之间的小数(建议对比度,1为不变),' +
'"tips":["修复小贴士1","修复小贴士2"]' +
'}';
}

这段提示词延续了我在其他项目里验证过的"结构化输出"方法论——给出完整 JSON schema、强制只输出 JSON、约束数值范围。这次多了一个细节:要求推荐的 filterName 尽量对应到本地已有的预设风格名称,这样返回结果可以直接映射到本地滤镜库,形成"AI 建议 → 一键应用"的闭环,而不是让用户拿着 AI 给的参数不知道怎么用。

5.3 实际效果

下图是我输入"这是一张80年代的黑白合影,边缘有破损,整体偏灰"之后,AI 顾问给出的建议:

AI 修复顾问给出结构化修复建议

可以看到,AI 推荐了"暖阳怀旧"风格,理由是"增强年代感与温情,弥补褪色导致的冷灰倾向",还给出了三条具体贴士:优先修复划痕与噪点再调色、保留原始颗粒感避免过度平滑、适当提升暗部细节但不破坏阴影层次。这些建议既专业又可执行,点击"应用此建议"就能直接把对应参数套用到当前照片上。

5.4 解析容错

和我在其他项目里的经验一致,大模型的输出不能 100%信任,解析层要做好容错:

private static parseAdvice(content: string): AdviceResult | null {
let text = content.trim();
const start = text.indexOf('{');
const end = text.lastIndexOf('}');
if (start >= 0 && end > start) { text = text.substring(start, end + 1); } // 剥离 markdown 包裹
try {
const obj = JSON.parse(text) as Record<string, Object>;
const params: FilterParams = {
brightness: RestoreAI.clamp(Number(obj['brightness'] ?? 1.1), 0.5, 1.6), // 数值范围兜底
contrast: RestoreAI.clamp(Number(obj['contrast'] ?? 1.1), 0.5, 1.8),
saturate: RestoreAI.clamp(Number(obj['saturate'] ?? 1.1), 0, 2),
grayscale: 0, blur: 0, invert: 0
};
// …
} catch (e) { return null; }
}

clamp 函数确保即使模型返回的数值超出合理范围(比如亮度给了 5),也会被拉回到 UI 能正常展示的区间,不会出现过曝或全黑的极端效果。

六、选片与系统权限:PhotoViewPicker 实践

选片功能看似简单,实际涉及鸿蒙的媒体库访问权限体系,这里也有值得说明的设计考量。

6.1 用 Picker 而非直接读取媒体库

鸿蒙有两种从相册取图的方式:一种是用 photoAccessHelper 直接查询媒体库资源(需要 ohos.permission.READ_IMAGEVIDEO 权限,涉及隐私合规审查);另一种是用系统提供的 PhotoViewPicker 选择器组件,用户在系统 UI 里选完照片后,只把选中项的临时授权 uri 交给应用,应用不需要相册的完整读权限。

对这类"只需要用户选一张图"的场景,PhotoViewPicker 显然是更合适、更轻量、也更容易通过应用市场审核的方案:

async pickPhoto(): Promise<void> {
try {
const picker = new photoAccessHelper.PhotoViewPicker();
const options = new photoAccessHelper.PhotoSelectOptions();
options.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
options.maxSelectNumber = 1;
const result = await picker.select(options);
if (result.photoUris.length > 0) {
this.photoSrc = result.photoUris[0];
this.isDemo = false;
}
} catch (e) {
const err = e as BusinessError;
promptAction.showToast({ message: '选择失败:' + err.message, duration: 2000 });
}
}

这也是为什么应用的 module.json5 里最终没有申请 READ_IMAGEVIDEO 权限,只保留了 INTERNET(供 AI 顾问联网)——权限声明越少,用户安装时的顾虑就越少,这是移动应用设计里一个常被忽视的小细节。

6.2 内置示例照片:无需授权也能完整体验

考虑到很多用户第一次打开应用时未必愿意立刻授权相册访问,我在 resources/rawfile 里内置了一张真实的老照片作为示例(一张带明显破损、折痕、泛黄的黑白合影),配合一张预制的"修复后"效果图,让用户不点任何授权按钮,也能完整体验"选片-对比-滤镜-AI建议"的全流程:

aboutToAppear(): void {
// rawfile 中的示例老照片,供未授权相册权限时也能完整体验
this.photoSrc = $rawfile('demo_photo.png');
}

对示例照片,"修复后"直接展示预置的真实修复成片,而不是走本地滤镜实时渲染——因为这张示例图本身经过精心挑选,展示的是一个有代表性的、效果显著的修复案例,比任何默认滤镜参数都更有说服力:

if (this.isDemo) {
// 示例照片使用真实修复完成的成片,效果更真实直观
Image($rawfile('demo_photo_after.png'))
.width('100%').height(220).objectFit(ImageFit.Cover).borderRadius(14)
} else {
FilterImage({ source: this.photoSrc, params: this.params, imgHeight: 220, imgRadius: 14 })
}

而一旦用户从相册选了自己的照片(isDemo 变为 false),"修复后"区域就切回真正的 FilterImage 实时滤镜渲染——因为用户自己的照片没有现成的"修复后"成片,只能靠滤镜引擎实时生成。这是一个"演示效果优先,真实功能兜底"的设计权衡:示例场景用最佳效果打动用户,实际使用场景用真实能力服务用户,两者互不冲突。

七、修复前后对比:布局设计的一次调整

修复效果怎么呈现,直接影响用户对"值不值得用"的第一判断。这里我实际上经历了一次设计上的返工,值得记录。

7.1 最初方案:左右拖拽对比

我最初做了一个"抽帘式"对比组件 CompareSlider——用户左右拖动一条分隔线,左边显示原图,右边显示滤镜效果,这是很多修图 App 的经典交互:

@Component
export struct CompareSlider {
@Prop source: ResourceStr = '';
@Prop params: FilterParams = defaultParams();
compareHeight: number = 300;
@State ratio: number = 0.5;

build() {
Stack({ alignContent: Alignment.TopStart }) {
Image(this.source).width('100%').height(this.compareHeight) // 底层原图
Row() {
Blank().width((1 this.ratio) * 100 + '%')
Image(this.source)
.brightness(this.params.brightness) /* …滤镜属性… */
.layoutWeight(1)
}.clip(true) // 上层滤镜效果,按比例裁切
// … 分隔线手势
}
}
}

这个组件技术上是可行的,但在实测中发现两个问题:一是拖拽手势的比例换算依赖一个近似的容器宽度常量,在不同屏幕尺寸下会有偏差;二是在窄屏手机上,左右分屏对比时每一半的可视宽度太小,很难看清细节差异,体验不如预期。

7.2 调整为上下布局

综合考虑后,我把对比布局改成了上下结构:上方固定展示"修复前"原图,下方展示"修复后"效果(应用当前滤镜或 AI 推荐参数),并在下方标题上实时显示当前滤镜的名称:

// 对比预览:上方修复前,下方修复后
Column({ space: 10 }) {
Column({ space: 6 }) {
Text('修复前').fontSize(12).fontColor(C.textDim).width('100%')
Image(this.photoSrc)
.width('100%').height(220).objectFit(ImageFit.Cover).borderRadius(14)
}.width('100%').alignItems(HorizontalAlign.Start)

Column({ space: 6 }) {
Text('修复后 · ' + this.presetName()).fontSize(12).fontColor(C.primary).width('100%')
// … 根据是否示例图,展示预置成片或实时滤镜
}.width('100%').alignItems(HorizontalAlign.Start)
}.width('100%')

上下布局的好处很直接:每张图都能占满屏幕宽度,细节清晰可辨;同时下方标题直接标注当前应用的滤镜名,用户切换滤镜时能清楚知道自己在对比什么。这次调整让我意识到,同一个"对比"需求,交互形式的选择要服从实际的屏幕尺寸约束,不能照搬别处的经典设计而不做验证。

八、我的相册:作品管理与持久化

修复好的照片需要能保存、能回看。我用 Preferences 实现了一套轻量的作品记录持久化。

8.1 数据模型

一条"修复作品"记录,需要保存原图引用、应用的滤镜、以及具体参数(防止预设风格库未来调整导致历史记录效果变化):

export interface PhotoWork {
id: string;
uri: string; // 原图 uri(示例图记为 'demo',真实相册图为实际 uri)
presetId: string;
presetName: string;
params: FilterParams; // 保存具体参数快照,而非仅保存 presetId
time: number;
}

这里有个小细节:我同时保存了 presetId(预设标识)和 params(具体参数快照)。虽然目前渲染时只用到 params,但保留 presetId 是为了未来可扩展——比如做"预设风格库升级后,用户可以选择保留旧效果或应用新参数"这类功能。

8.2 相册页:网格展示

相册页把所有已保存作品用网格铺开,每张卡片实时按保存的参数渲染:

Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Start }) {
ForEach(this.works, (w: PhotoWork) => {
Column({ space: 6 }) {
FilterImage({ source: this.srcOf(w), params: w.params, imgHeight: 140, imgRadius: 12 })
Row() {
Text(w.presetName).fontSize(11).fontColor(C.textSub).layoutWeight(1)
Text('✕').fontSize(12).fontColor(C.danger)
.onClick(() => { this.onDelete(w.id); })
}.width('100%')
}.width('47%').margin({ right: 10, bottom: 14 })
}, (w: PhotoWork) => w.id)
}

这里复用了前面提到的 FilterImage 组件——同一份参数结构,从工坊的实时预览到相册的历史回看,都能保持一致的渲染逻辑,这正是把"滤镜渲染"独立封装成组件带来的复用价值。

下图是保存后的相册页效果,展示了一张已保存的"原图"作品:

我的相册:已保存的修复作品

九、诚实的产品文案:我的/设置页

前面提到的"AI 顾问能力边界"说明,最终落地在「我的」页面的一个专门区块里:

Column({ space: 10 }) {
Text('ℹ️ 关于 AI 修复顾问').fontSize(14).fontWeight(FontWeight.Bold).fontColor(C.text).width('100%')
Text('AI 顾问基于你的文字描述给出滤镜风格与参数建议,并非直接分析照片像素。')
.fontSize(12).fontColor(C.textSub).width('100%')
Text('实际的修复效果由本地滤镜引擎实时渲染,所有处理均在本机完成,不上传照片。')
.fontSize(12).fontColor(C.textSub).width('100%')
}

下图是「我的」页面的完整展示,包括应用介绍卡片、AI 顾问能力说明、以及模型服务配置区:

我的/设置页:诚实说明 AI 能力边界与隐私处理方式

我认为这几句朴素的说明文字,价值不亚于任何一个技术亮点。"照片不会上传"这一点对老照片修复类应用尤其重要——用户拿出来修复的往往是家庭隐私影像,明确告知"所有滤镜处理均在本机完成",是建立用户信任的关键一步。这也呼应了第四章讲的技术选型:正因为滤镜是纯本地渲染、AI 只处理文字,"不上传照片"才不是一句空话,而是架构层面就决定了的事实。

十、HarmonyOS 6.1 环境下的踩坑记录

除了前面提到的 @Prop 装饰器坑,这次在 HarmonyOS 6.1 环境下开发还遇到几个值得记录的问题:

坑一:预览器(Previewer)不支持系统选择器。 在 DevEco Studio 的 Previewer 预览模式下点击"从相册选择照片",PhotoViewPicker.select() 会一直悬空、没有任何响应,甚至连异常都不会抛出。原因是 Previewer 只做 UI 渲染,不具备拉起系统级选择器 UI 的能力(这依赖跨进程/跨 Ability 的系统服务)。这类涉及系统服务的功能,必须在真机或模拟器上验证,Previewer 只适合看纯 UI 布局。

坑二:组件自定义属性与内置属性方法同名冲突。 我在 CompareSlider 组件里最初声明了一个 height: number 成员变量,结果编译报错:

Property 'height' in type 'CompareSlider' is not assignable to the same property in base type 'CustomComponent'.

因为 height 与 ArkUI 内置的 .height() 通用属性方法签名冲突了。解决方式很简单——给自定义属性换个不冲突的名字(比如 compareHeight),避免和框架保留的属性/方法名撞车。这是写自定义组件时的一个通用注意点:任何要作为 @Component 属性传参的字段,最好避开 width/height/padding 这类 ArkUI 通用属性同名词。

坑三:跨文件相对路径导入错误。 model/PhotoStore.ets 需要引用 common/FilterPresets.ets 里的类型,我最初误写成 './FilterPresets'(当前目录),实际应为 '../common/FilterPresets'(上一级再进 common 目录)。ArkTS 的模块路径规则和普通 TypeScript 一致,但项目目录层级稍复杂时很容易写错——多层级项目里,导入路径最好用 IDE 的自动补全,而不是手写猜测。

坑四:日志排查系统选择器结果的正确方式。 遇到"选片没反应"这类疑难问题时,hdc shell hilog 抓日志是排查系统交互类问题最可靠的手段。比如这次问题排查中,日志里能清楚看到选择器已经正确回传了结果:

[picker] result: {"resultCode":0,"uris":["file://media/Photo/1/IMG_…"]}

这条日志证明"选择器交互"和"业务回调"本身没有问题,问题出在 UI 没有正确响应数据变化——这就把排查范围从"网络/系统权限"精确收窄到了"组件状态同步",最终定位到前面说的 @Prop 遗漏。掌握 hdc shell hilog 抓日志、配合关键字过滤,是鸿蒙开发调试系统级交互问题的必备技能。

十一、总结:一个关于克制的技术故事

回顾这个项目,它其实不是一个技术上多么复杂的应用——没有复杂的图像处理算法,没有多轮对话,甚至没有花哨的动效。但它教会我的,是克制:

  • 克制地选择技术方案:不为了炫技去接入复杂的图像处理库,而是发现 ArkUI 原生的图像效果属性已经足够优雅地解决问题,用最简单的方式达成"本地实时"这个核心体验目标。
  • 克制地宣传 AI 能力:明确知道模型看不到照片像素,就不在文案上夸大其词,宁可老实说"基于文字描述给建议",也不用"智能分析你的照片"这类容易误导用户的表述。
  • 克制地申请权限:能用系统 Picker 解决的,就不去申请媒体库的完整读权限;用户的信任,往往就是在这些"少要一点权限"的细节里攒起来的。

在 HarmonyOS 6.1 这套更完善的开发生态下,做这样一款轻量却诚实的工具类应用,体验是很顺畅的——图像效果属性覆盖了绝大部分需求、系统选择器交互流畅、多端适配开箱即用。如果你也想做一款类似的工具应用,我最想说的一句话是:技术选型上,先看框架原生能力能覆盖多少,而不是先想着引入外部依赖;产品文案上,先想清楚系统的能力边界在哪,再决定怎么说。

希望这篇实战记录,能帮你在 HarmonyOS 6.1 上少踩几个我踩过的坑。如果你也在用鸿蒙做类似的图像类应用,欢迎交流。

赞(0)
未经允许不得转载:171主机测评 » 【共创季稿事节】用 HarmonyOS 6.1 做一款老照片修复工坊:本地滤镜引擎 + AI 修复顾问实战
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址