Air 系列应用交流/轻绘|AirPaint · 鸿蒙 PC 适配全记录

轻绘|AirPaint · 鸿蒙 PC 适配全记录

【Air 系列 · 鸿蒙 PC 适配全记录】28 / 56

■ 应用:轻绘(AirPaint)
■ 定位:画板绘图:图层、调色板、历史记录
■ 上游:Krita master | 路线:Qt5 unified+QPA | 版本:1.2.0
■ 市场状态:已上架 AGC 应用页: https://appgallery.huawei.com/app/6917605142001854571
■ 官网(免费下载): https://www.shunidc.cn/

————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/

PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper

适配开源地址:(AtomGit 仓库建立后回填)

环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743

▍一、为什么要适配轻绘

轻绘(AirPaint)的这次重构,起点是一次诚实的工程复盘。旧版轻绘 v1.0.0 是一个 ArkTS 自绘绘画应用:ArkUI Canvas 自绘画布(PaintCanvas.ets 1506 行)加 native 层手抄的 Krita 算法(brushengine、pigment、tiles3 等五个目录 74 个文件),功能命名对齐 Krita 模块。问题在于,绘画应用的核心竞争力是经年累月打磨的笔刷引擎、色彩管理与资源系统,手抄算法层永远追赶不上真正投入了数年人力的上游。工程文档记录了裁决理由:"还是得基于 Krita 进行修改,人家是投入了数年人力进行设计,你不可能做出媲美的产品。"

据此,工程按方案 v1.2 路线 E 重构:Krita 原生 UI 全量保留,用 Krita 自有机制(工作区、主题、xmlgui 菜单、触屏配置)做形态级差异化。方案文档同时论证了两个约束:替代 shell 的开源项目历史上并不存在(Krita Sketch/Gemini 触摸壳早已从源码删除),换壳必须自研;而给 Krita 套 Photoshop 风格会与姊妹工程 AirPS(GIMP 3.3.1 + PhotoGIMP)撞车,必须避开。最终形态与蓝本 AirDraw 同宗但可辨识——桌面恢复 Default 工作区,手机切 Autodesk_Sketchbook 工作区,主题、图标尺寸、触控参数独立调校。

当前工程包名沿用 com.airpaint.app,版本 1.2.0,deviceTypes 覆盖 phone、tablet 与 2in1,声明 INTERNET、READ_PASTEBOARD、VIBRATE 三项权限与 general.image 的分享接收/打开方式入口。架构路线继承 AirDraw(Krita 5.4.0-prealpha Qt5 线全原生移植):ArkTS 薄壳 + XComponent + libairpaintbridge.so 薄桥,拓扑序 dlopen 154 个库后进入 libkrita_core.so,Qt 侧为 5.15.16 + KF5 5.116.0。PORTING.md 记录了 2026-09-28 首会话在 HUAWEI MateBook Pro(PC)上的部署验证:Krita 主窗口完整渲染(菜单栏、工具箱、画布、dockers、状态栏),多轮 8 到 40 分钟存活、faultlogger 零条目。

▍二、先划清边界:不把编译通过当成适配完成

这次改造的本质是引擎换代,边界的参照物因此有两条:对旧版自绘轻绘,边界是"手抄算法层全数让位";对 Krita 上游,边界是"引擎与 UI 全量继承,定制只走资源与配置层"。方案文档明确路线 E 的风险最低路径——kritaui 零结构改动,所有差异化通过 Krita 自带的机制表达,不做 widget 层魔改。这也意味着旧代码的去留必须说清楚:旧 ArkTS 自绘 UI 全量保存在 legacy/arkts-selfdrawn 分支(tag v1.0.0-arkts-final),手抄 cpp 层在 M4 之前仍留在树中待物理删除,不算已交付能力。

层次 旧版轻绘实现(v1.0.0 自绘线) 鸿蒙侧当前方案(路线 E)
画布与渲染 ArkUI Canvas 自绘 + 手抄 brushengine/pigment/tiles3(74 文件) Krita 真引擎全栈,libkrita_core.so,画布 raster 渲染于 XComponent
UI 栈 36 个 .ets 自绘 panel/dialog,无 XComponent Krita 原生 QMainWindow + dockers;菜单栏 ArkTS 化对照 krita5.xmlgui
桌面栈 无 Qt/KF5 依赖 Qt 5.15.16 + KF5 5.116.0(qt5_source/kf5_source symlink 只读复用)
事件输入 NAPI 直调自绘命中 XComponent → 薄桥 → QPA/Qt 事件链 + 触控种子(touchPainting/iconSize)
弹窗 ArkUI 自绘对话框 DialogHost 浮层 + SubWindowManager 真子窗双通路 + 自绘标题条
差异化手段 模块命名对齐 Krita 工作区(Default/Autodesk_Sketchbook)、主题(Electronic/Krita Blue)、iconSize 档位、xmlgui 菜单重组
包名与品牌 com.airpaint.app,轻绘 沿用不裂变;Qt 侧 ApplicationName/DisplayName=AirPaint

边界表里刻意保留了"沿用不裂变"一行:bundleName、签名与应用名跨引擎换代保持连续,用户视角的轻绘还是轻绘,换掉的是引擎与整套交互内核。

▍三、鸿蒙版的整体架构

运行链路与蓝本 AirDraw 同构:ArkTS 壳负责窗口、菜单栏、弹窗宿主与文件流转;libairpaintbridge.so 只做薄桥;surface 创建后按拓扑序 dlopen 154 个依赖库进入 libkrita_core.so,Qt 事件循环由渲染线程外驱;Krita 侧的全部定制收敛在 krita_patches/ 一个入口(krita_ohos_entry.cpp 与 overlay 子树),共享上游 krita-master 以 symlink 指向 Upstreams/krita-master,改动一律走本工程补丁。

text
EntryAbility / Index.ets(ArkTS 薄壳:自绘标题条、菜单栏桥、悬浮球、弹窗宿主)
├── XComponent + libairpaintbridge.so(薄桥)
│ └── 拓扑序 dlopen 154 库 → libkrita_core.so(Krita 主窗口全栈)
│ ├── Qt 5.15.16 + KF5 5.116.0(QPA 分离呈现,画布 OpenGLRenderer=none)
│ ├── krita_patches/krita_ohos_entry.cpp(触控种子/事件过滤/资源路径,唯一改动入口)
│ └── rawfile/krita(115M 数据:share 资源、color-schemes、icc、工作区 kws)
├── DialogHost / SubWindowManager(浮层与真子窗双通路,自绘标题条)
├── FileManager / ShareManager / ExportFormats(打开、分享接收、导出格式)
└── ImmersiveManager(窗口形态判定,驱动弹窗模式分流)


主要目录如下:

text
AirPaint/
├── AppScope/app.json5 # 包名 com.airpaint.app、应用名 轻绘、版本 1.2.0
├── PORTING.md # 移植状态与 v4–v10 迭代实录
├── docs/2026-09-28-Krita框架重构与UI架构替换方案-v1.md # 路线 E 论证(v1.2 现行)
├── krita-master -> Upstreams/krita-master # 共享上游 symlink(铁律 19)
├── entry/src/main/
│ ├── ets/
│ │ ├── pages/Index.ets # 壳主页面:菜单桥、悬浮球、弹窗接线
│ │ ├── components/DialogHost.ets # 浮层弹窗宿主(等比钳制 fitAspect)
│ │ ├── components/DialogTitleBar.ets # 自绘标题条
│ │ └── utils/SubWindowManager.ets # 真子窗宿主(clampKeepPos 同步等比)
│ └── cpp/
│ ├── krita_patches/ # 本工程全部 krita 侧改动(overlay 子树)
│ ├── ohos_deps/ # 313M 依赖安装前缀(独立副本)
│ ├── qt5_source / kf5_source / kf5_build # symlink 只读复用 AirDraw 实体
│ ├── ohos_toolchain.cmake # NDK 交叉工具链
│ ├── krita_core_build/ # 引擎构建树(全新 configure)
│ ├── deploy_krita_core.sh # 部署链(装机前校验 3 条 "OK: HAP")
│ └── entry libs:entry/libs/arm64-v8a/(193 个 .so 打包通道)
└── thirdparty_src / thirdparty_build # 第三方源码与构建产物


▍四、核心适配过程

### 1. M0/M1:换引擎不换身份的骨架搬运

M0 先立基线:git 基线 54b0b270、tag v1.0.0-arkts-final,旧自绘 UI 全量进 legacy/arkts-selfdrawn 分支,新工作在 engine/krita 分支展开;AirDraw 在共享 krita-master 树上的 11 个未提交修改落为 in-tree commit,AirPaint 直接继承其中 Q_OS_OHOS 守卫的通用适配(压感恒等映射、平滑窗、输入管理器)。M1 骨架按"symlink 只读复用 + 独立副本"切分:qt5_source/kf5_source/kf5_build 指回 AirDraw 实体(AirFx 同款先例),而 ohos_deps(313M)、krita_patches(416K)、rawfile/krita(115M)、entry/libs(193 个 .so 通道)为独立副本;薄桥改名 libkritabridge 为 libairpaintbridge,ets 壳整树移植后品牌化 AirPaint。这一轮结束时 hvigor 构建已通(薄桥 + ets + HAP 签名一次通过)。

### 2. M2:构建链重建与路径消毒

引擎构建树不复用 AirDraw 的 CMakeCache(烧死绝对路径),按 cache 机学习的 UNINITIALIZED 参数全新 configure,工具链为 cpp/ohos_toolchain.cmake(OHOS_ARCH=arm64-v8a、OHOS_STL=c++_shared),Boost 经 airdraw_boost_bootstrap.cmake 强制模块模式。消毒是这一步的主戏:ohos_deps 内嵌的 143 个 cmake/pc 配置文件烧着旧工程 /AirKrita/... 绝对路径,全新 configure 即现形(Boost version.hpp 读取失败、版本误判 0.0.0),已全量 sed 为 AirPaint 路径;build_krita_ohos.sh 明确不可整跑——阶段 2 会在共享源码树内原地污染 AirDraw 的 qt5_source,实际只需 configure krita_patches 工程加 ninja 加 deploy 脚本。部署链另有纪律:deploy_krita_core.sh 存在偶发静默失败,装机前必须校验 3 条 OK: HAP,装机后对 .so 做行为签名串校验。

### 3. 差异化种子与双主题体系

krita_patches/krita_ohos_entry.cpp 是本工程与 AirDraw 的正式分叉点。触控侧种子三项:touchPainting=1(TOUCH_PAINTING_ENABLED,手指直接作画默认开,kritarc 根组与 KSharedConfig 双写)、工具箱图标尺寸按屏宽分流(2in1/平板 32,手机 ≤900 档 22 并收窄净宽、藏滚动箭头)、工作区恢复——桌面维持 Krita 原生 Default 工作区机制,手机形态切 Autodesk_Sketchbook(BKL 包 8 套工作区入 rawfile,stateLen=5939 锚点验证)。主题侧落地两套:Electronic(David Revoy 深蓝底配亮绿字,share/color-schemes 随包)与 Krita Blue(Cyber Blue,#2B2E43/#01D6FF),ArkTS 菜单栏以 MENUBAR_BG/MENUBAR_TEXT 手动同步主题真色。

### 4. 弹层存活治理与悬浮球交互

注入链上的弹层问题分两层修复。对话框内 combo 的"点击立即收起":press 注入打开弹层后,105ms 后同点 release 被重路由投进列表被当作选择,桌面 Qt 靠 maybeIgnoreMouseButtonRelease 兜底而注入链命中不了——v9 以应用级事件过滤器武装一次性开关,吞掉"弹层内、与开层 press 同点(曼哈顿距离小于 40 逻辑 px)且小于 2 秒"的首个 release(POPUP-SWALLOW)。主窗 combo 的同类问题机制不同(release 落弹层外主窗),v10 从 AirDraw 移植注入层守卫:popup 开启后 400ms 内的 UP 不注入(POPUP-GUARD,env 闸可关)。悬浮球侧沉淀了一套 ArkUI 手势终态:GestureGroup(Parallel, TapGesture+PanGesture) 组合手势加球本体 hitTestBehavior(Block) 阻断穿透,位置经 preferences 持久化(context 必须取 ensureFileManagerContext(),否则首版从未落盘),交互质感用 curves.springMotion 弹性缩放与 haptic.effect.soft 轻振(VIBRATE 权限,系统日志实锤马达触发)。

### 5. 色彩管线补全:icc 特性文件发现通道

v10 定案的三症同根问题:切 CMYK 后特性文件空白、摘要停旧值、创建按钮无反应。诊断确定数据面问题——KoResourcePaths 的 icc 发现三通道在鸿蒙上全断:data 通道指向的 AppDataLocation 与桌面布局 share/color/icc 不符,getInstallationPrefix() 在 HAP 里指向 libs 目录,EXTRA_RESOURCE_DIRS 未设。后果是 111 个 icc 特性文件(含上游 cmyk.icm 在内)从未加载,RGB 一直靠 lcms 内建 profile 兜底所以从未暴露。修法在 krita_ohos_entry.cpp 挂载绝对目录:addAssetDir("icc_profiles", <dataPath>/share/color/icc) 与 /share/krita/profiles,cmyk.icm(Chemical proof)入 rawfile。验证记录为:CMYK 下特性文件显示 Chemical proof(默认)、摘要精确联动"CMYK/透明度 40 位 41.5MiB"、成功建档 41.9MiB 加颜料图层。

▍五、适配中遇到的几个难点

### 难点一:在 Krita 内部状态恢复链上叠加编排层,三连败

为了让 UI 与 AirDraw 拉开差距,第一会话做了三轮布局实验,全部回退:Minimal 工作区在跨分辨率恢复时状态 blob 错位;程序化 PS 布局一次性应用被文档激活时的 dockerStateBeforeHiding 回滚吃掉;每轮重放又与 Krita 内部恢复拉锯出"UI 完全错乱"。用户裁决定案:布局回到 Krita 原生 Default 工作区机制(tryRestoreDefaultWorkspace() 恢复 Default.kws,stateLen=5991 实证),applyPhotoshopLayout() 搁置为零调用点并保留教训注释。这条难点的价值在方法论:改 Krita 行为前必须先读懂 hideToolbarsOnce 的全部收敛起点与内部状态恢复链,差异化要走 Krita 自有机制而不是外挂编排。

### 难点二:主题被上游 ThemeManager 静默重置

接入 Electronic 主题后发现配色总会弹回 Krita dark。根因是 Krita 自家 ThemeManager 在主窗创建链里会把 palette 重置回其 currentThemeName 兜底值(ICONPROBE 探针实证底色值 71)。工程以"每拍重钉"对策:在 hideToolbarsOnce 内比对 Window 底色,偏离即重新应用主题,实测仅触发 1 次重钉、无拉锯。另一面是 ArkTS 菜单栏不吃 Qt palette——它渲染在 Qt 内容区之外,必须以 MENUBAR_BG=#FF191141/MENUBAR_TEXT=#FF77FF7E 这类常量手动同步,两栈颜色一致性要靠人工对账。

### 难点三:弹层秒关与"看不见的活弹层"

下拉弹层被 release 误杀的修复最终是双保险(v9 事件过滤器吞弹层内 release、v10 注入层守卫吞弹层外 400ms 内 UP),但 v10 同时暴露一个更深的坑:主窗弹层功能性存活后像素却不上屏——PIL 全图 diff 弹层区 0 像素,而对话框内弹层正常。定性为 QPA 主表面内联合成路径的渲染问题,此前被"秒关"现象掩盖。修复需要独立的 libqohos.so 交叉编译链(qt_platform/ohos 的 CMake 工程与 ohos_deps/qt5),已登记为下轮专项,当前文档如实记录"弹层可选、视觉不可见"为已知边界。

### 难点四:子窗口被各向异性压扁

手机上 QColorDialog 被钳成 1896×957(纵向压 71.7%),色块比例肉眼可见失真。三层根因链:QPA 分离呈现把对话框图像拉伸铺满卡片 surface;DialogHost 的卡片钳制两轴独立(94%×88% 各自取 min)破坏宽高比;Qt 侧 v4 的 prop-fit resize 又被 KisOpenPane 布局最小宽吃掉。正解落在 ArkTS 卡片侧:fitAspect() 单系数 k=min(maxW/w, maxH/h, 1),create/geometry/placeCenter 三处入口全走,真子窗路径 clampKeepPos 同步修改。验证是定量式的:颜色卡恢复 453×319(等于 Qt 源比例 1.42)、QPA surface 的 kx/ky 精确等比 1.395、PIL 精测色块比例与预测值双吻合,取消按钮坐标映射无损。

### 难点五:电测假阴性污染判断

v7 修工具箱拖动滚动时,v6 的全部探针零输出曾把排查引向错误方向,最终定案是触摸喂给了别的应用——uinput 注入前未确认应用前台(window list Focused=true)。工程由此固化两条纪律:注入探针必须带行为串(TBSCROLL-PROBE 的 press/move 输出)自证命中;滚动类问题可用定量像素对比(PIL crop 加 diff,工具箱区域差异 27.2%、画布 0.00% 无泄漏)离线替代视觉验证。部署链的静默失败同理:多次实锤后,装机前校验 3 条 OK: HAP 成为硬性步骤。

▍六、编译、安装与启动

引擎侧如需重编,按 PORTING.md 的 M2 链路:以 cpp/ohos_toolchain.cmake 与 bootstrap 文件全新 configure krita_core_build(参数从 cache 机学习生成,含 KRITA_SOURCE_DIR、CMAKE_PREFIX_PATH=cpp/ohos_deps 与自定义 CMAKE_MODULE_PATH),然后 ninja -C krita_core_build 全量构建,最后 ./deploy_krita_core.sh install 部署(装机前校验 3 条 OK: HAP)。应用侧使用标准 hvigorw 流程:

bash
export JAVA_HOME=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk

hvigorw --mode module -p module=entry assembleHap --no-daemon


产物位于 entry/build/default/outputs/default/entry-default-signed.hap。模块构建配置带 nativeLib.strip:false 与 compressNativeLibs:false,rawfile 含 115M Krita 数据;签名沿用轻绘 profile,换机部署时用 devecocli signature generate --product default --force 重生成调试签名。连接设备后安装并启动:

bash
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.airpaint.app


两条例行注意随文档登记:rawfile 新增文件必须卸载重装(install -r 不部署该类数据);重装后首启弹剪贴板权限对话框,需在弹窗内点"始终允许"。

▍七、当前功能边界

当前版本已覆盖的能力:

- Krita 引擎全栈运行:主窗口(菜单栏/工具箱/画布/dockers/状态栏)在 PC 与手机形态真机渲染,多轮 8–40 分钟存活、faultlogger 零条目(PORTING.md 验证记录);
- 触控作画种子(touchPainting 默认开)、按屏宽分流的工具箱图标尺寸与拖动滚动;
- 双主题体系(Electronic/Krita Blue)与每拍重钉保持;手机形态 Autodesk_Sketchbook 工作区;
- 弹窗双通路(浮层 + 真子窗)等比呈现、自绘标题条、多实例可并发;
- 悬浮球:可拖动、位置持久化、悬停/握持质感与触觉反馈;
- 下拉弹层双层存活守卫(POPUP-SWALLOW + POPUP-GUARD);
- CMYK 色彩管线(icc 特性文件 111 个加载、Chemical proof 建档验证);
- 图片分享接收与"打开方式"入口(general.image)、应用名轻绘全端品牌化。

尚未覆盖或已登记未决的能力:主窗弹层像素不上屏(QPA 内联合成问题,待 libqohos.so 独立编译链专项);M3 中的菜单 xmlgui 变体重组、iconSize 档位体系与启动画面/关于页品牌化尚未全部落地;M4 的旧手抄 cpp 层物理删除、LICENSE 落 GPL-2.0-or-later 与 S1~S14 验收在待办清单上;版本号 2.0.0 的引擎换代语义调整待定;Qt 对话框标题条仍中性灰未跟主题(v4–v7 遗留小项)。因此当前版本更准确的定位是"Krita 引擎换代后的差异化基础版":引擎与 UI 内核已真实切换并稳定运行,差异化的深化与收尾工作沿 M3/M4 里程碑推进。

▍八、总结

轻绘的重构记录了一次少见的自我否定:放弃 74 个手抄算法文件换来的"自主感",承认成熟上游的价值,把工程量从重造引擎转向驾驭引擎。路线 E 的执行也证明了这条路的性价比——AirDraw 踩平的 QPA、dlopen 拓扑、输入桥与打包难题被整体继承,AirPaint 的增量工作集中在真正的产品差异上:触控参数、工作区、主题、弹层行为与色彩管线补全。最终保住的资产是身份连续性:同一个包名、同一个应用名,内核从自绘玩具换成了 Krita 全栈。

后续工作在 PORTING.md 待办与 v10 已知深坑中有清晰坐标:主窗弹层的合成修复、M3 差异化收尾、M4 的旧代码清理与 GPL-2.0-or-later 许可证落地、S1~S14 验收与 2.0.0 版本语义确认。每一步延续同一方法——行为串探针自证、像素级定量验证、真机多轮存活观测,让"跑在鸿蒙上的 Krita"继续向可交付状态收敛。
楼主 8 天前
想参与讨论?登录 或 30 秒注册。