Air 系列应用交流/AirDraw(from Krita)|AirDraw · 鸿蒙 PC 适配全记录

AirDraw(from Krita)|AirDraw · 鸿蒙 PC 适配全记录

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

■ 应用:AirDraw(from Krita)(AirDraw)
■ 定位:Krita 移植,数字绘画(支持笔压感应)
■ 上游:Krita | 路线:Qt5 5.15+KF5+QPA | 版本:1.7.0
■ 开源: https://gitcode.com/Andy88/Harmony-krita
■ 市场状态:已上架 AGC 应用页: https://appgallery.huawei.com/app/6917611796078931356
■ 官网(免费下载): https://www.shunidc.cn/

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

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

适配开源地址:https://gitcode.com/Andy88/Harmony-krita

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

▍一、为什么要适配 AirDraw

Krita 是开源数字绘画领域参考级的自由软件,围绕笔刷引擎、图层管理、色彩管理与压感输入构建了完整的创作流程,以 GPL-3.0-or-later 协议发布。对鸿蒙 PC 生态而言,绘画是最能检验系统输入与图形栈成熟度的应用类别之一:它要求指针事件具备真实的压力与倾角信息,要求窗口系统支撑多表面合成与弹层管理,也要求应用能在 2in1、平板、手机等多种形态上保持可用。把 Krita 带到鸿蒙,等于把这套最苛刻的输入与渲染要求整体放进工程里验证一遍。

本次适配的应用名为 AirDraw,工程代号 AirKrita。仓库中的应用版本为 1.7.0,包名 com.airdraw.app,deviceTypes 覆盖 tablet、2in1 与 phone,目标 Native ABI 为 arm64-v8a。上游取 Krita master 快照(5.4.0-prealpha,Qt5 构建线),GUI 栈为源码交叉编译的 Qt 5.15.16 加 KF5 5.116.0 的 12 个模块。与纯壳套 Web 的方案不同,这个工程把 Krita 的 C++ 核心、Qt、KF5 与自研 QPA 插件全部编进同一个 Native 产物,属于移植深度最高的一档。

与很多"编译通过即收工"的移植不同,本工程有明确的多轮真机回归记录:MateBook Pro(2in1,3120×2080@dpr1.9)、MatePad 11.5、MatePad Mini、Pura X 与 Mate60Pro 等设备上,主窗口、dockers、触摸作画、压感与颜色面板二级窗口均经过多会话闭环验证,PORTING.md 按会话完整登记了每一次修复的根因与验证证据。

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

Krita 桌面版运行在 xcb/X11 的 Qt 平台插件之上,窗口、对话框、光标、输入设备都由 Linux 桌面环境提供;发行版用户通过包管理器获得 Qt 与 KDE 库。把这些环节原样搬到鸿蒙并不存在现成通道:鸿蒙没有 X11,ArkUI 的窗口体系与 Qt 的窗口抽象是两套模型,el2 应用沙箱对文件写入的语义也与桌面 Linux 不同。

因此工程划定的边界是:Krita 的核心能力(画布渲染、笔刷引擎、图层、色彩面板)进入当前可运行产物,由自研 QPA 与 ArkTS 壳共同补齐平台层;平台强相关的部分(QSaveFile 原子提交、原生对话框窗口、发行版依赖分发)则按鸿蒙的实际情况替换实现。逐层对照如下。

层次 原项目实现(Krita 桌面版) 鸿蒙侧当前方案
应用入口 C++ main() 与 KoApplication 启动链 Stage 模型 EntryAbility + NAPI 桥进入 Krita_Init
窗口承载 xcb/X11 QPA 与桌面窗口管理器 自研 libqohos.so QPA + SURFACE 型 XComponent
事件循环 Qt 主线程 QApplication::exec() 独立 RenderThread 外部驱动,8ms 保底节拍 + 条件变量唤醒
输入设备 XInput 平板与鼠标事件 ArkTS 触摸回调聚合合成 QTouchEvent/QTabletEvent,M-Pencil 三信号判笔
多窗口与对话框 QDialog 原生顶级窗口 QPA 窗口注册表 + ArkTS DialogHost 子窗托管与等比缩放
文件保存 QSaveFile 原子提交 QTemporaryFile 加分块拷贝(el2 沙箱 commit 兼容路径)
依赖分发 发行版包管理提供 Qt/KDE Qt5/KF5 源码交叉编译,依赖栈随 HAP 携带并按 DT_NEEDED 拓扑序 dlopen

这条边界的含义是:当前产物是能在多种真机上安装、作画、保存的 Krita 子集,而不是全功能桌面的等价替换;尚未收敛的输入细节与窗口几何问题在 PORTING.md 的遗留清单中如实登记。

▍三、鸿蒙版的整体架构

架构上分为四层。ArkTS 壳负责 Ability 生命周期、页面导航、图库选择与对话框宿主;NAPI 薄壳 libkritabridge.so 在 OnSurfaceCreated 时按 DT_NEEDED 拓扑序 dlopen 150 余个依赖库并完成符号绑定;libkrita_core.so 是重型核心,聚合 Qt5、KF5 与 Krita 本体,对外只导出 Krita_Init/Krita_Step/Krita_Exit 三个 C API;Qt 事件循环由独立 RenderThread 驱动,底层的 libqohos.so QPA 插件把 QWidget 光栅结果搬上 EGLSurface。

text
ArkTS 壳(EntryAbility / Index.ets / DialogHost)
│ NAPI
└── libkritabridge.so
│ 按 DT_NEEDED 拓扑序 dlopen 150+ 依赖 → dlsym
└── libkrita_core.so(Qt5 + KF5 + Krita)
├── C API:Krita_Init / Krita_Step / Krita_Exit
├── RenderThread 驱动 Qt 事件循环
│ drainDirtyWidgets → sendWindowSystemEvents → processEvents
├── krita_ohos_entry.cpp:事件分发 / POPUP-GUARD 弹层守卫 / 插件加载
└── libqohos.so(ohos QPA,QT_QPA_PLATFORM=ohos)
QOhosIntegration / Screen / Window / BackingStore / GLContext
raster QImage → GLES 纹理 + blit shader → 单共享 EGLSurface


主要目录如下(krita-master/ 为 Krita 鸿蒙化源码树,随仓库发布于独立 git 仓):

text
AirDraw/
├── AppScope/app.json5 # 包名 com.airdraw.app、版本 1.7.0
├── PORTING.md # 按会话登记的移植状态与真机验证记录
├── ailog/ # 黑屏审计/渲染管线/输入链路等深度报告
├── krita-master/ # Krita 源码树(5.4.0-prealpha 快照)
└── entry/src/main/
├── ets/
│ ├── pages/Index.ets # 主界面:画布宿主、图库导入、悬浮色钮
│ ├── pages/DialogPage.ets # 新建图像等对话框页
│ ├── components/DialogHost.ets # 子窗托管与等比缩放
│ └── managers/ImmersiveManager.ets # 沉浸式窗口管理
├── resources/rawfile/krita/ # Krita 数据文件(图标主题、笔刷预设等)
└── cpp/
├── napi_init.cpp # NAPI 薄壳与拓扑序 dlopen 加载器
├── qt_platform/ohos/ # ohos QPA 插件与可独立热重建脚本
├── krita_patches/ # 0001-ohos-platform.patch 与 krita_ohos_entry
├── build_krita_ohos.sh # 743 行主构建脚本,六步全栈编译
├── ohos_toolchain.cmake # 自研交叉编译工具链
└── unibreak_utf16_bridge.c # 文本断行 UTF-16 桥


▍四、核心适配过程

### 1. 依赖栈的拓扑序 dlopen 加载

鸿蒙 HAP 的 native 库加载不提供桌面 Linux 的 rpath 搜索语义,而 Qt 加 KF5 加 Krita 的依赖图有 150 余个节点。工程没有把全部符号静态链进一个巨型 so,而是让 napi_init.cpp 在 OnSurfaceCreated 时按 DT_NEEDED 拓扑序逐个 dlopen,再 dlsym 进入 Krita_Init。配套的 patch_qohos_needed.py 负责修正依赖项里的版本号后缀,deploy_krita_core.sh 在每次重编后回拷三库并做 HAP 字节级校验,保证设备上运行的确实是新构建。

### 2. 自研 ohos QPA 的渲染管线

qt_platform/ohos/ 下的 qohosintegration.cpp 实现了完整的 Qt 平台插件:QOhosIntegration、Screen、Window、BackingStore 与 GLContext。QWidget 的光栅结果先落在 QImage(RGBA8888_Premultiplied),再作为 GLES 纹理经 blit shader 绘制到唯一共享的 EGLSurface,由 eglSwapBuffers 提交。QPA 对外导出 QOhos_SetNativeWindow、QOhos_SetDisplayMetrics、QOhos_FirstSwapShown 等入口,由 NAPI 层把 XComponent 的表面与屏幕度量接入 Qt 世界。这套管线让 Krita 无感知地运行在非 X11 环境中。

### 3. Qt5 与 KF5 的源码交叉编译

build_krita_ohos.sh 用六步完成全栈构建:基础第三方依赖、Qt5 加 QPA 插件、KF5、应用 Krita 平台补丁、编 libkrita_core.so、收集数据与插件。Qt 5.15.16 使用自研 linux-ohos-clang mkspec,KF5 5.116.0 编译 12 个模块加 ECM;ohos_toolchain.cmake 与 ohos_meson_cross.txt 分别服务 CMake 与 Meson 项目,qt_private_stubs.cpp 补齐 Qt 私有符号,unibreak_utf16_bridge.c 为 Krita 的文本断行接入 UTF-16 桥。这一步的本质是把发行版的整个 KDE 运行时在鸿蒙工具链下重建一遍。

### 4. Krita 侧入口与弹层守卫

krita_patches/krita_ohos_entry.cpp 是 Krita 与平台之间的边界层:它实现 C API 的三个入口,把 NAPI 层送来的触摸、笔与键盘事件分发进 Qt,并承担弹窗治理。POPUP-GUARD 机制在 Qt::Popup 展开后吞掉开弹点击自身的释放事件,避免下拉框秒关;后续又移植了基于事件过滤器的完整版(按 press/show 的曼哈顿距离与时间窗判定),并以 AIRDRAW_NO_POPUP_GUARD=1 作为逃逸门。0001-ohos-platform.patch 则记录了对 Krita 源码本身的平台化改动。

### 5. ArkTS 壳的功能承接

Index.ets 承担 Krita 之外的设备侧功能:系统图库选图导入画布(含跨设备分享回图)、右下角可拖动的悬浮色钮(PanGesture 驱动、位置存 AppStorage)、格式选择与图像信息对话框页。DialogHost.ets 把 Qt 对话框映射为 ArkUI 子窗,并在窗口尺寸变化时做等比缩放与位置钳制;逻辑宽不超过 900 的手机形态下,applyPhoneUiAdaptation 会放大字体、把右列 docker 折叠为页签,保证触控热区不低于 44px。

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

### 难点1:下拉框弹层的"三连根"缺陷

混合模式下拉框点击后约 100ms 闪没,逐层剥开后是三个叠加缺陷:合成 release 落在弹窗外导致 Qt 立即关弹层(POPUP-GUARD 修复);取消后的对话框隐藏但其分离 EGLSurface 仍在窗口注册表,弹层归属兜底把内容画到死表面(归属判定补上 isVisible() 约束);popup 的 devicePixelRatio() 异常值使 physRect 越界被防御性钳成全表面,弹层被整幅拉伸变形(改用屏幕级 g_nativeState.dpr 真值加 intersected 裁剪)。三处分别位于事件守卫、QPA 归属判定与合成尾段,任何一处单独修都得不到可用体验。

### 难点2:双指触控的画笔风暴

Mate60Pro 上双指触画布会同时出现两道笔画。取证发现该设备触摸回调按逐触点派发,每个事件 numPoints==1,旧代码 numPoints >= 2 的手势分支永不成立,两根手指各自走了单指鼠标路径。解法是在 napi_init.cpp 建立触点追踪表:活动触点数达到 2 即进手势模式,聚合发出多点 QTouchEvent,本事件触点携带真实状态、其余标 moved、抬起触点出表,超过 5 秒无更新的触点强制剔除防止漏 UP 卡死。

### 难点3:保存 0B 与文件损坏的三重根因

手机端保存出现 0B 文件、未保存标记与损坏文件并存。三个根因各自成立:线程模型重构后 QApplication 位于 RenderThread,旧"GUI 线程禁入"防御把每次正常的选择器会话按取消返回;QSaveFile 在 el2 沙箱的 rename 提交恒败,与上游 Android/macOS 沙箱结论一致,改为 QTemporaryFile 加分块拷贝;ArkTS 拷贝循环把 writeSync 的 offset 当缓冲区偏移,实际它是文件写入位置,大于 1MB 的文件每块都写回 0 处被截断。三处修复后,保存链路可以完整走通 pick → save-done → 拷贝 → Download/Pictures.kra。

### 难点4:M-Pencil 的笔判定失效

MatePad Mini 上用户实测"画笔无效",笔画可见但粗细深浅完全均一。沙箱诊断日志定案:M-Pencil 全程被判成手指,ToolType 主路查询失败、垂直握笔倾角为 0,压力恒为 1.0。修复采用三信号并联判笔——ToolType 笔族、倾角非零、force 大于 0 且接触面积极小,任一命中即按笔处理;随后针对 M-Pencil 力域偏宽的问题,参照上游 Android 分支对同类手写笔的手法,在 kis_config.cc 把默认压感曲线改为 0,0;0.5,1(50% 力封顶),并把平滑窗从 50px 收到 15px 消除跟笔延迟。

### 难点5:patch_needed 的"假成功"

第十会话发现修复从未真正进入设备:补丁检查脚本以文件存在性为判据,构建树中的旧副本让检查通过,实际部署链上 Krita 侧修复缺失。这类"脚本报成功、真机无效果"的问题比直接报错更昂贵,工程随后在部署脚本中加入三库的字节级校验与 md5 比对,并把"验证必须落在真机行为"写入方法学记录。

▍六、编译、安装与启动

工程在仓库中内置了 Qt5、KF5 的源码树与预编译依赖栈;全新环境下先执行全栈构建脚本生成 libkrita_core.so,再走标准 hvigor 打包:

bash
bash entry/src/main/cpp/build_krita_ohos.sh

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


只改 ArkTS 层或 Krita 侧源码时,可用 deploy_krita_core.sh 走增量链路(ninja 重编三库、回拷、NEEDED 补丁、HAP 校验),无需全量重跑。签名方面需要注意:debug Profile 内含开发机 UDID,向 MateBook 等 2in1 设备部署时需将 build-profile.json5 的 signingConfig 临时切到 default,装完再恢复。产物位于 entry/build/default/outputs/default/entry-default-signed.hap,连接设备后安装并启动:

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


▍七、当前功能边界

按 PORTING.md 的多轮真机验证记录,当前版本已覆盖的能力包括:

- 可安装的 HAP:Stage 模型 Ability、SURFACE 型 XComponent 与 QPA Native 窗口;
- Krita 主窗口与 dockers 体系:笔刷预设、图层、工具选项、拾色器等面板在触控下可用;
- 触摸作画与 M-Pencil 压感:三信号判笔、压感曲线与线平滑参数调优后支持轻重笔画;
- 颜色面板二级窗口、新建图像对话框与配置对话框(模态子窗化后可完整交互);
- 系统图库选图导入画布,以及通过分享面板的跨设备回图;
- 文档保存为 .kra 到图库或下载目录,标题栏未保存状态同步;
- 剪贴板图片双向流转(向系统剪贴板输出 pixelMap、PNG 与 HTML 内嵌图三记录);
- 手机形态等比适配(字体放大、docker 页签化、悬浮色钮可拖动、工具箱单列滚动)。

尚未收敛或明确登记为遗留的能力包括:MateBook 上鼠标左键拖拽不出笔画(触摸可画,事件链缺陷待 WMS 层取证);真实两台设备贴屏的碰一碰互通(代码与权限声明完整,待物理操作验证);折叠/旋转等跨形态切换的对话框几何重算(当前语义为关闭重开);超时对话框的 post-relayout 二次居中(现残余约 4% 屏宽的偏移);以及 manage_bundles/manage_resources 等资源管理入口维持拦截。

因此,当前版本更准确的定位是"Krita 鸿蒙触控创作基础版":绘画主链路(选笔、作画、压感、存图、跨应用取图)已经真实可用,桌面级外围能力仍按会话逐步收口。

▍八、总结

AirDraw 的价值在于验证了一条最重的移植路线是否走得通:不为 Krita 另造界面,而是把 Qt5、KF5 与 Krita 本体在鸿蒙工具链下完整重建,用一个自研 QPA 插件承接窗口、输入与渲染的全部边界。工程留下了一套可复用的资产——拓扑序 dlopen 加载器、ohos QPA 实现、全栈交叉编译脚本,以及一份按会话登记根因与证据的移植日志。

后续工作的重心也由这份日志决定:收敛鼠标拖拽与窗口几何等输入细节,完成碰一碰的真实双机验证,把对话框居中等残余问题按 post-relayout 通道归零。每一步继续以真机上的实际作画行为作为验收依据,而不是构建退出码。
楼主 8 天前
想参与讨论?登录 或 30 秒注册。