开源移植实战 把 PhotoGIMP 移植到鸿蒙 PC
AirPS 鸿蒙 PC 适配全记录:完整移植 PhotoGIMP 3.3.1,自研 GTK OHOS 后端承载 Photoshop 风格工作区
欢迎加入开源鸿蒙 PC 社区:https://harmonypc.csdn.net/
欢迎在 PC 社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:https://gitcode.com/Andy88/Harmony-Gimp-OHOS
一、为什么要适配 AirPS
专业图像编辑器是桌面生产力工具里不可缺席的一档。修图、合成、批处理、格式转换,这些工作对图层、选区、蒙版和高位深色彩处理有硬要求,浏览器里的轻量工具替代不了。在开源世界里,GIMP 是这一档的事实标准:它的图层与选区体系完整,图像处理建立在 GEGL 图像处理图上,支持高位深与线性光工作流,格式覆盖面广。
选择 GIMP 做鸿蒙 PC 适配,难度和价值都远超一般的桌面应用移植。GIMP 3.x 是一个完整的 GTK 3 大型应用,界面、对话框、菜单、文本输入全部建在 GTK 之上;显示层直接依赖 X11/Wayland 的 GDK 后端;国际化走 gettext;插件体系依靠 fork + exec 的独立插件进程。这四条在鸿蒙上都不成立。把它带过来,等于同时回答四个问题:GTK 3 应用如何在一个没有传统显示服务器、没有窗口管理器参与应用窗口管理的系统上渲染和接收输入;没有 exec 的系统如何承载一个插件驱动的应用;Photoshop 用户的工作习惯如何低成本迁移;GPL-3.0 大型项目如何在移动操作系统上合规分发。
AirPS 的定位因此很明确:GIMP 3.3.1 引擎 + PhotoGIMP 布局配置,深度模仿 Adobe Photoshop 工作区——PS 的菜单栏结构、工具顺序、快捷键与工具手感、右侧双层面板。它与"原版 GIMP 工作区"的 AirPhoto 是同一引擎的两个发布形态:日常开发在 AirGimp 仓库进行,发布为 AirPhoto(原版 GIMP 布局)与 AirPS(Photoshop 风格布局)两个独立应用,各自有独立包名与配置取向。
当前鸿蒙工程的应用版本为 1.3.0(versionCode 1300000),包名为 com.airps.app,支持 phone、tablet 与 2in1 三种设备形态,目标 ABI 为 arm64-v8a,compatible SDK 与 target SDK 均为 6.1.0(23)。应用申请的唯一用户授权权限是剪贴板读取(READ_PASTEBOARD,用于粘贴图片入口;2in1 形态免申请),并通过 ohos.want.action.viewData / ohos.want.action.sendData 注册了 general.image 类型的图片打开与分享接收。
需要说明的是商标与合规边界:本项目与 Adobe/Photoshop、GIMP/PhotoGIMP 项目均无关联,"Photoshop 风格"仅指工作区交互布局的模仿,应用图标与启动画面为原创绘制;整个项目遵循 GPL-3.0-or-later 开源。
二、先划清边界:不是"编译通过",而是换掉显示服务与进程模型
GIMP 上游对桌面 Linux 环境的依赖是结构性的。如果只以"消除编译报错"为目标,即使把全部源码编出动态库,得到的也只是一个无法显示、无法交互的静态引擎。因此本项目的验收口径从一开始就是:真机上可安装、可启动、可完成"打开图像—编辑—导出"的完整闭环,菜单可操作,中文界面可用,对话框行为正确。
| 层次 | 上游 GIMP 实现 | 鸿蒙侧当前方案 |
|---|---|---|
| 显示服务器 | GDK 的 X11 / Wayland 后端 | 自研 GDK OHOS 后端,XComponent 提供 OHNativeWindow |
| 应用入口 | app/main.c 的 main() | Stage 模型 EntryAbility + gimp_app.cpp 引导 |
| 主窗口 | GtkWindow + WM 窗口管理 | ArkTS XComponent(TEXTURE 型)+ ArkUI 原生菜单栏 |
| 对话框 | GtkDialog,依赖 WM 模态 | 2in1 真子窗口 / 手机平板浮层双模式 + 自绘标题条 |
| 菜单 | GTK 菜单栏 | ArkUI 原生菜单 ⇄ 真实 GimpAction 双通道 |
| 插件模型 | fork + exec 独立插件进程 | -Dplugins=disabled,GEGL 算子以 .so 模块随包加载 |
| 国际化 | gettext + 系统 locale | 自带真 gettext 实现,.mo 随包部署 |
| 文本输入 | GTK IME 模块 | 双宿主:交互留在 GTK,键盘焦点在 ArkTS |
| 工作区布局 | GIMP 默认布局 | PhotoGIMP 配置种子(快捷键/工具手感/导出默认值) |
这个边界意味着:当前 AirPS 是完整的 GIMP 3.3.1 引擎在鸿蒙上的原生运行,而不是功能子集的重新实现;但上游依赖独立插件进程的插件生态、脚本扩展等能力,按阶段保留在边界之外(详见第八节)。
三、鸿蒙版的整体架构
整体架构遵循"ArkTS 薄壳 + 原生重型核心"的双层模式。ArkTS 层只负责 Ability 生命周期、菜单栏渲染、对话框管理和输入法桥接;一个只链接系统库的薄 NAPI 桥(libgimpbridge.so)注册 XComponent 回调;真正的重量——GTK 主线程与 GIMP 引擎——全部装在 libairgimp_core.so 里,运行时按依赖拓扑顺序 dlopen 加载:
ArkTS 壳(entry/src/main/ets)
│ napi:libgimpbridge.so(薄壳,仅链系统库,注册 XComponent 回调)
▼ OnSurfaceCreated 时按序 dlopen 依赖(RTLD_GLOBAL)→ dlsym 核心 API
libairgimp_core.so(GTK 线程宿主)
├── libgtk-3.so / libgdk-3.so(含自研 OHOS GDK 后端)
├── GIMP 3.3.1 libapp 静态库组(--start-group 链接)
└── libgimp{base,color,config,math,module,thumb,widgets}-3.0.so
三条运行时链路是整个移植的骨架:
- 渲染:GDK 的 cairo 后备 surface 按 z 序合成各 toplevel,整帧 blit 进
OHNativeWindow; - 事件:
XComponentNDK 回调 →airgimp_core_inject_*→gdk_ohos_display_inject_*→_gdk_windowing_got_event(GTK 标准事件注入入口); - 菜单:ArkUI 原生菜单栏 ⇄ GIMP 真实
GimpAction,经getMenubarJson导出菜单树、activateMenuAction回传执行。
版本侧的事实:GIMP 3.3.1、GTK 3.24.53、babl 0.1.118、GEGL 0.4.71、GLib 2.80.4,外加 libmypaint 1.6.1、json-c 0.17、json-glib 等依赖,全部从源码交叉编译或以预编译产物入库分发。主要目录如下:
AirPS/
├── AppScope/app.json5 # com.airps.app,1.3.0
├── entry/src/main/
│ ├── ets/ # Ability、菜单栏、对话框管理、输入法桥
│ ├── cpp/
│ │ ├── napi_init.cpp # 薄 NAPI 壳:按序 dlopen 与 XComponent 回调
│ │ ├── airgimp_core.cpp # 核心桥接
│ │ ├── gimp_app.cpp # GIMP 引导(对齐上游 app/main.c)
│ │ ├── elf_edit.py / patch_libs.py # ELF 原位编辑工具链
│ │ └── libs/arm64-v8a/ # 链接用预编译 .so(100 余个)
│ ├── libs/arm64-v8a/ # 随 HAP 分发的运行时模块(60 余个)
│ └── resources/rawfile/gimp-share/ # 39 MB:GIMP 数据、中文 .mo、PhotoGIMP 种子
└── native/
├── sources/ # gimp-master / gtk3 / babl / gegl 源码树
├── patches/gtk3-ohos/ # OHOS GDK 后端权威副本
├── scripts/ # env.sh 与 build_*.sh 交叉编译链
├── cross/ # meson cross file 与 CMake 工具链
└── install/ # 交叉编译 PREFIX(入库分发)
GIMP 的数据资源(笔刷、渐变、图案、主题、菜单定义、字体配置)共约 39 MB,位于 HAP rawfile/gimp-share/。首次启动时由 EntryAbility 提取到应用沙箱,用版本戳文件保证幂等——只有版本号或构建时间变化才全量重拷。GIMP3_DATADIR 指向数据根目录而非 share 根目录,这是实测得出的细节:指错一级目录会让 g_file_get_contents 在沙箱里挂死。
四、核心工作流在真机上的验证
AirPS 的目标设备是华为 MateBook Pro(2in1)与 MatePad 平板;最近的阶段记录(P29–P32)在 HUAWEI Pura X 上补齐了手机形态的适配。下列工作流均以真机上的阶段记录为准(详见仓库 ai_log/),配图对应这些工作流。
1. 从启动直达 PS 风格主界面
应用启动后直达 GIMP 主界面:顶部是 ArkUI 渲染的深灰色(#FF323232)菜单栏,菜单顺序按 Photoshop 习惯排列——文件、编辑、选择、视图、图像、图层、颜色、工具、滤镜、窗口;左侧单色工具箱,右侧双层面板,中央画布。与 PS 一致,启动就绪后会自动弹出"新建图像"对话框,让用户第一时间开始工作。

2. 中文菜单不是换语言包,而是菜单双通道
菜单栏由 ArkUI 原生渲染,但每一项都对应 GIMP 内部真实的 GimpAction:getMenubarJson 把 GIMP 菜单树导出给 ArkTS,用户点击后经 activateMenuAction 回传 native 执行。这意味着菜单不是界面仿品——过滤器、色彩命令背后都是原版 GIMP 处理链。

3. 对话框:2in1 真子窗口与手机浮层双模式
GIMP 的图像处理几乎全部通过对话框完成(新建图像、曲线、色阶、导出……)。鸿蒙版把 GTK 对话框路由到两种承载模式:2in1 自由窗口下创建系统真子窗口,可拖拽、多实例、互不遮挡;手机和平板沉浸形态下在主窗口内以浮层卡片承载,带遮罩与居中钳制。两种模式共用同一套自绘标题条(标题 + ✕ 关闭),保证观感一致。

4. 触摸文本输入:键盘在 ArkTS,交互在 GTK
对话框里的数值输入框(宽度、高度、分辨率……)在纯触摸设备上支持:点按定位光标、双击全选、软键盘直挂输入、数值在会话结束时提交。输入法物理上只能由 ArkTS 组件承载焦点,因此链路是双宿主的——这部分是整个工程里打磨最久的交互,细节见第五节和第六节。

5. 打开、导出与外部图片入口
文件打开/导出闭环已在真机验证。外部图片有四条统一入口:系统分享(want)、碰一碰/隔空投送、拖放、剪贴板粘贴(含 uri 型记录),全部汇入同一条"拷入沙箱 → 待打开队列 → native 打开"链路。打开语义按画板类应用惯例分流:无文档时新建图像打开,有文档时作为图层加入当前画板。
五、核心适配过程
1. 自研 GDK OHOS 后端:替掉 X11/Wayland
这是整个移植的地基。GTK 的窗口、事件、光标、按键体系都在 GDK 层抽象,因此适配点不在 GIMP,而在给 GTK 3 写一个新后端:gdkdisplay-ohos.c、gdkeventsource-ohos.c、gdkdevice-ohos.c、gdkkeys-ohos.c、gdkcursor-ohos.c 等,在 gdkdisplaymanager.c 注册 "ohos" 显示后端。后端代码以 native/patches/gtk3-ohos/ 为权威副本入库,构建脚本在编译 GTK 前把它 rsync 进源码树——改后端只动 patches 目录,重跑 build_gtk3.sh 即可,不污染上游源码树。
渲染上,后端用 cairo 后备 surface 承接 GTK 的绘制,按 z 序合成后整帧 blit 进 OHNativeWindow;事件上,所有输入经 _gdk_windowing_got_event 这个 GTK 标准注入入口进入主循环——绕过这个入口自造事件分发,是触摸失灵类问题的常见根源。
2. 薄壳 dlopen 与打包纪律
hvigor 只编译两个 CMake 产物:薄 NAPI 桥和核心库。其余上百个预编译 .so 走 entry/libs/arm64-v8a/ 整体打包通道,运行时由薄壳按依赖拓扑排序 dlopen——GLib 家族、Pango/Cairo、GTK、babl/GEGL、libgimp 系列,最后加载 libairgimp_core.so 并 dlsym 解析核心入口。这条纪律是踩出来的:hvigor 的链接闭包会静默丢弃它不认识的预编译库,native 库的 strip 也必须关闭,否则对齐被破坏的 ELF 在设备上 dlopen 即崩且无日志。
3. GIMP 引导与插件模型的替代
GIMP 不再经 main() 启动,由 gimp_app.cpp 对齐上游 app/main.c 的启动序列引导,i18n、配置目录、数据目录逐项初始化。插件进程模型被整体禁用(-Dplugins=disabled)——鸿蒙无多进程 exec,插件进程在设备上永远跑不起来,编译它们纯属浪费;GEGL 算子改为 .so 模块随包部署、运行时 dlopen。文件装载则补了一条进程内 GEGL 装载链兜底:按文件魔数嗅探 → GEGL load → 建图或加层,与"文件→打开"路径同构。
4. PhotoGIMP 布局的两种落地方式
PS 手感分两层落地。第一层是用户级配置种子(rawfile/gimp-share/ps-seed/):PS 快捷键表(M 矩形选择、C 裁剪、W 魔棒、Alt+BackSpace 填充前景色、Ctrl+E 合并图层……)、22 个工具选项预置、滤镜与导出默认值(如 JPEG 导出质量)、PS 风格启动画面。首次启动时幂等写入用户配置目录——所有条目只在缺失时写入,不覆盖用户后续修改。第二层是 ArkTS 菜单栏的 PS 化:顶级菜单顺序重排为 PS 习惯、深灰配色、剔除与画板工作流无关的菜单项。
5. 中文化:musl 上"假成功"的 gettext
鸿蒙的 musl libc 没有 gettext。麻烦在于 glib 会回退到 proxy-libintl——所有 g_libintl_* 调用都能"成功"返回,但内容原样透传不翻译,造成".mo 文件都齐了界面还是英文"的假象。对策是自带真 gettext 实现(libintl_real.c 编译成 libintl.so 覆盖 proxy),配合 LANGUAGE=zh_CN、GIMP3_LOCALEDIR 指向随包 .mo,并在 gtk_init 之后重绑 gtk30 域。随包部署 8 个翻译域:gimp30、gimp30-libgimp、gimp30-std-plug-ins、gtk30、gtk30-properties、json-glib-1.0 等,从 GIMP 的 zh_CN.po 用 msgfmt 现编。
6. 菜单双通道与输入法桥
菜单双通道(getMenubarJson / activateMenuAction)之外,文本输入采用双宿主桥:GDK 事件钩子识别 GtkEntry 系控件的触摸命中,用 pango 自行计算光标位置;命中后通知 ArkTS 聚焦一个隐形 TextArea 拉起软键盘;键盘输入经 onChange 差分(去抖 + 硬上限)回注 GtkEditable 光标处;会话结束(点到文本框之外)时执行 gtk_spin_button_update 提交数值并收起键盘。combo 下拉则不拦截,触摸直通 GTK 原生弹层,由弹层独立承载面显示。
六、适配中遇到的几个难点
难点一:没有窗口管理器,一切"新顶层窗口"都是危险区
桌面 Linux 上对话框的位置、尺寸、模态、焦点全靠 WM 协调;鸿蒙的应用窗口体系里这些都要自理。终态方案是双模式路由:以"启动时是否处于自由多窗形态"的永续标记 ∪ 实时窗口状态为谓词,每个新对话框独立判定走真子窗口还是浮层;已开实例在形态切换后惰性不迁移(迁移等于销毁重建,闪烁且丢 GTK 状态)。真子窗口自绘 40vp 标题条,拖拽走 startMoving()(系统接管,必须在 Touch Down 回调内同步调用)+ PanGesture 兜底双通道。两个反复出现的坑:GDK 对同一对话框可能双发 show,ArkTS 侧必须幂等,否则触发重建风暴;系统 X 销毁子窗口时 GTK 模态还在,形成"窗没了、菜单锁死"的僵尸态,destroy 联动必须幂等补取消。
难点二:浮层不回写位置,就是给主窗口埋触摸死区
无 WM 下 GTK 不知道浮层被摆到了屏幕哪里。如果浮层的实际屏幕位不回写给 GDK,GTK 的窗口记账里就永远有一个挂在 root(0,0) 的隐形矩形——它会截走主窗口左上区域的触摸命中,表象是"工具箱上部点不了"。这类问题日志里一切正常,只有把"命中了哪个 widget"的探针打出来才能实锤。终态:浮层的初始布局、拖拽、键盘抬升全部经同一条 moveDialog 通道回写 GDK。
难点三:hilog 流控与安全扫描,逼出"日志一律写 .txt"的纪律
两个真机实锤的根因:其一,hilog 有流控,大量 INFO 级日志会触发停读,OH_LOG 在 datagram socket 上的发送线程被永久阻塞——表象是应用黑屏假活、零 CPU 占用、零日志;其二,设备安全服务实时扫描 *.log 文件,扫描期间写盘挂起,曾与 GLib 日志锁跨线程死锁叠加,造成导出后 ANR 循环。纪律由此固定:应用自产日志一律 .txt 后缀(gimp_trace.txt / airgimp_stderr.txt),只有 ERROR 级才上 hilog。
难点四:musl dlopen 的严格性,逼出一条 ELF 纪律
musl 的 dlopen 对 ELF 布局的要求比 glibc 严格得多。二进制 patchelf 会把 .dynsym/.dynamic 搬进尾部对齐段,污染过的库(本项目实测过 116 个)在设备上 dlopen 即崩且无日志;hvigor 的 llvm-strip 会把预编译库 64K 对齐的 PT_LOAD 重排成非法 ELF;meson 安装的库 NEEDED 带版本号后缀,而打包通道里的文件名无版本。对策是一套自研原位编辑工具链(elf_edit.py:改 NEEDED/RPATH 不动段布局)+ 关闭 native strip + NEEDED 统一去版本号。还有一个隐蔽的假绿:构建树把静态库列为 order-only 依赖,改了 .a 不会触发核心 .so 重链,hvigor 全绿但设备跑的是旧代码——改静态库后必须删产物强制重链并做字节级核验。
难点五:触摸输入的数值"复利损坏"
对话框数值框的提交时机踩过一个大坑:如果逐字符把文本提交给 GtkSpinButton,spin 控件会重写文本,与差分镜像失配,数值被反复翻倍——实测一个 1280 能一路变成 524288。纪律是值提交只发生在输入会话结束时(点到文本框之外或点击确定按钮),一次 gtk_spin_button_update 定案。同族的坑还有:同一次触摸浮层和主窗各收一份事件(需 30ms 去重);GTK3 combo 默认的菜单模式在无 WM 环境不可承载(CSS 强制列表模式);双击全选在纯触摸下没有 GTK 语义(core 侧自行记账合成)。
难点六:外部接收链路各有各的暗坑
碰一碰/隔空投送的 receive() 要求传 file:// URI 而不是裸沙箱路径(否则原生层直接返回 EINVAL 且无任何回调),且接收目录必须"已存在且为空",每次传输开独立子目录;Promise 不接 catch 则失败无声。拖放接收要注意 kit 导入在不同 SDK 版本的可用性差异;剪贴板粘贴要同时处理 pixelMap 与 uri 型两种记录,只按主 MIME 分流会静默跳过图库复制来的图片。
七、编译、安装与启动
环境前置:DevEco Studio(提供 hvigorw 与 OHOS SDK)、HarmonyOS SDK 6.1.0(23)、meson + ninja、python3、交叉工具链(clang/llvm 系列,路径见 native/scripts/env.sh)。
预编译依赖已随仓库分发,日常打包只需要:
cd AirPS
./hvigorw assembleApp --no-daemon
# 产物:entry/build/default/outputs/default/entry-default-signed.hap
当前仓库构建出的签名 HAP 约 92 MiB(含 39 MB 资源数据、60 余个运行时模块与全部依赖库)。只有在改动 babl / GEGL / GTK 后端 / GIMP 本体时才需要重新交叉编译:
source native/scripts/env.sh
native/scripts/build_babl.sh # 改 babl
native/scripts/build_gegl.sh # 改 GEGL
native/scripts/build_gtk3.sh # 改 OHOS GDK 后端(patches/gtk3-ohos)
native/scripts/build_libintl.sh # 中文化真 gettext
# GIMP 本体:native/build/gimp 构建树内 ninja 编译 libapp
native/scripts/deploy_modules.sh # 刷新 entry/libs 运行时模块
./hvigorw assembleApp --no-daemon
完整构建顺序以 native/BUILD.md 为权威入口(文档中个别历史路径沿袭开发仓库 AirGimp 的表述,以 env.sh 当前值为准)。安装与启动:
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.airps.app
排障以文件日志为准(原因见难点三):
hdc shell "tail -30 /data/app/el2/100/base/com.airps.app/haps/entry/files/gimp_trace.txt"
hdc shell "tail -30 /data/app/el2/100/base/com.airps.app/haps/entry/files/airgimp_stderr.txt"
八、当前功能边界与合规
当前 HarmonyOS 版已在真机验证的能力:
- 可安装的 HAP、Stage 模型 Ability、TEXTURE 型 XComponent 全窗口渲染,覆盖 phone / tablet / 2in1 三形态;
- GIMP 3.3.1 引擎完整引导,直达主界面,启动无崩溃;
- ArkUI 原生菜单栏(中文化、PS 化顺序)⇄ 真实 GimpAction 执行;
- 触摸与硬件键盘事件链;GtkEntry 系触摸输入(点选光标、双击全选、软键盘直挂、数值提交);
- 多实例对话框:2in1 真子窗口与手机/平板浮层双模式,含短屏滚动与底部手势区避让(P29–P32 手机形态专项);
- 文件加载与导出闭环;
- 外部图片四入口(分享 / 碰一碰 / 拖放 / 剪贴板),空文档新建、有文档加层的打开语义;
- PhotoGIMP 工作区:PS 快捷键、工具预置、导出默认值、启动画面。
边界之外的能力要如实说明:GIMP 上游的插件进程体系被整体禁用,依赖独立插件进程的第三方扩展与脚本插件暂不在当前形态内,文件装载导出由进程内 GEGL 链兜底;手写笔压感、捏合缩放、暗黑模式等社区移植验收标准项,落地状态以仓库 ai_log/ 与 README 的阶段记录为准,不提前宣称完成。
GPL 合规是这类项目绕不开的一环:GIMP 与 GEGL 均为 GPL-3.0-or-later,本项目对源码的全部修改在同协议下公开,预编译产物与源码同仓分发以满足对应源义务;再分发 HAP 或预编译库时必须一并提供完整源码。GIMP 是 GNOME 项目商标,AirPS 是独立衍生作品,不代表 GNOME 官方背书;PhotoGIMP 布局配置(GPL-3.0)来自 Diolinux 开源项目。签名私钥与含密钥配置严禁入库,仓库提供发布前的校验命令逐项排查。
九、总结
AirPS 的适配价值,不在于"能跑一个 GTK 演示程序",而在于证明了一条把 GTK 大型桌面应用整体搬到鸿蒙的路径:用自研 GDK OHOS 后端接替 X11/Wayland,用 ArkTS 原生菜单与自绘标题条补上无 WM 的窗口治理,用真 gettext 与配置种子保住本地化与工作区手感,再用一套 ELF 原位编辑纪律守住 musl dlopen 的严格性。最终产物不是静态界面,而是能在 HarmonyOS PC、平板乃至手机上安装、启动并完成真实图像编辑工作的 GIMP 3.3.1。
这套沉淀已经在系列工程中复用:OHOS GDK 后端、对话框双模式路由、触摸文本输入终态,已成为其它 GTK/Qt 移植工程直接照抄的基准实现。后续工作的重心同样清晰:插件能力的进程内替代、验收标准项(手写笔、捏合缩放、暗黑模式)的逐项落地,以及对 GIMP 上游 3.x 后续版本的持续跟进——每一步都以可构建的 HAP 和真机上的实际编辑行为作为验收依据。
更多推荐



所有评论(0)