ohos_Pinta:基于 OpenHarmony PC 生态的图像编辑软件项目

基于 .NET/C#+GTK4 的绘图软件,因鸿蒙禁 JIT 致 .NET 运行时无法落地,改用「ArkWeb 宿主+Web/Canvas2D 前端+C# 核心编 WASM」路线并确立图层缓冲常驻预乘 BGRA 的性能铁律,最终在鸿蒙 PC 跑通画笔、撤销重做、选区与滤镜的图像编辑 MVP。

分支1Tags0
文件最后提交记录最后更新时间
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
12 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前

Pinta 的 OpenHarmony 适配记录

这份文档记录我们把 Pinta 搬到 OpenHarmony / HarmonyOS PC 上的过程:原项目是什么、为什么不能照搬原来的代码、最后选了哪条路、鸿蒙工程怎么搭、HAP 怎么编出来跑到真机上,以及现在哪些功能在鸿蒙端能用、哪些是后续要补的。写得比较实,因为这个项目和 Qt / Electron 那类"有现成运行时直接套壳"的移植很不一样——Pinta 是 .NET / C# + GTK4 的桌面绘图软件,鸿蒙上既没有 GTK 也没有能直接跑的 .NET 运行时,所以我们没法"套个壳加载原代码"了事,整条路都得重新趟。

Pinta 原项目(GTK# 版的 Paint.NET 克隆):http://pinta-project.com/ ,源码 https://github.com/PintaProject/Pinta

先把背景和路线讲清楚

原版 Pinta 的技术栈是 .NET 10 / C#,UI 用 GTK4 + libadwaita,画布和成像走 Cairo。这套东西在 Linux / Windows / macOS 上跑得很好,但到了鸿蒙就遇到两堵墙:

  • UI 层(GTK4 + libadwaita,约 90 个文件)在鸿蒙上复用率基本为零——鸿蒙没有 GTK 这一套,菜单、工具栏、图层面板、对话框只能重写。
  • 运行时也是个硬约束:HarmonyOS 5.0+ 禁止 JIT / 匿名可执行内存,CoreCLR / Mono 的 JIT 都用不了,.NET 在鸿蒙上理论上只剩 NativeAOT 一条路,而且 GC / seccomp 还有坑;我们也调研过 OpenHarmony.NET / Avalonia 这类"让 .NET 直接上鸿蒙"的方案,但它在 2026-05 已经归档、停在 .NET 9、核心仓 license 都是 null,没法当基座。

所以我们在 harmony-port/README.md 里做了一轮比较完整的方案对比,最后定下来的路线(项目里叫"路线 Y")是:

用 ArkWeb 当宿主,UI 和画布用 Web 技术(HTML/CSS/JS + Canvas2D)重写,C# 核心算法当作"规格"逐步移植 / 将来编成 WASM 复用,系统能力经 ArkWeb ↔ ArkTS 桥接到鸿蒙 API。

选 ArkWeb 的理由很直接:UI 反正怎么都得重写,那就用生态最熟、和 Cairo 直绘对得上的 Web 路线(Cairo 的绘图 API 和 Canvas2D 几乎 1:1);而 ArkWeb 是华为定制的 Chromium(Blink + V8),WASM / Canvas2D / WebGL / JS↔ArkTS 桥都支持,PC 形态还有独立渲染进程,是现成可用的宿主。C# 那几万行算法(Pinta.Core / Pinta.Effects / Pinta.Tools)是 Pinta 的核心资产,留着将来编 WASM 复用,但因为它强依赖 Cairo(每个图层的像素缓冲就是 Cairo.ImageSurface,光直接用到 Cairo 的就有 135 个文件),得先做一层 Cairo 抽象才能编,这块还没动。

需要老实说明的是:当前鸿蒙端的画图能力,是我们用 JS 在 Web 前端里按 Pinta 的算法重新实现的,还不是把原 C# 代码编进去跑。原 C# 工程在这次适配里没有被改动,它在桌面端照旧能 dotnet run。换句话说,这次是"把功能在鸿蒙能用的栈上重做",不是"把原二进制搬过来"。

我们在动手重写之前先验了最致命的那个风险——大图逐像素滤镜在 Web Canvas 上够不够快。结论是够(详见后文"适配里踩过的关键点"),这才放心走 Web 路线。


工程结构 / 鸿蒙工程在哪

鸿蒙相关的全部内容都在仓库根目录下的 harmony-port/(注意是连字符)。这个目录是独立于原 Pinta 源码树的,原项目的 Pinta/ Pinta.Core/ Pinta.Effects/ 等还在根目录原封不动放着。harmony-port/ 内部又分成"Web 前端开发源"和"鸿蒙宿主工程"两块:

ohos_Pinta/
├── Pinta/  Pinta.Core/  Pinta.Effects/  Pinta.Tools/ ...   # 原版 .NET/C#+GTK 源码(桌面端,未改动)
├── Pinta.sln                                                # 原桌面解决方案
├── README.md                                                # 原项目说明
└── harmony-port/                          # ★ 鸿蒙适配工作目录(全部新增内容在这)
    ├── README.md                          # 路线决策 + 进度 + 真机验证记录(第一手资料)
    ├── deploy.sh                          # 一键:打包前端 → 构建 HAP → 装机 → 启动
    ├── sync-webapp.sh                     # 打包 Web 前端并同步进鸿蒙工程的 rawfile
    ├── spike-canvas-perf/                 # 风险①验证:纯 JS 跑 Pinta 真实算子的性能 spike
    ├── app/                               # ★ Web 前端开发源(纯前端、无构建框架,可 node 测)
    │   ├── index.html  styles.css
    │   ├── package.json                   # scripts: test / serve
    │   ├── build-webapp.mjs               # 把 src/ 打成 dist/app.bundle.js(带"漏文件防呆")
    │   ├── test/smoke.node.js             # node 冒烟测试(像素/合成/滤镜/撤销重做/选区…)
    │   └── src/
    │       ├── pixels.js                  # RGBA↔预乘BGRA 转换、预乘 alpha-over
    │       ├── document.js                # Document / Layer 模型(图层=预乘BGRA缓冲)
    │       ├── compositor.js              # 多图层预乘空间合成
    │       ├── filters.js / adjustments.js / imageops.js   # 滤镜 / 调整 / 图像操作
    │       ├── tools.js  viewport.js  canvasview.js  history.js
    │       ├── platform.js                # ★ 平台抽象:有桥走原生、无桥退化浏览器
    │       ├── selftest.js                # 浏览器内自检
    │       └── ui.js / app.js             # 工具栏/面板/菜单装配 + 入口
    └── ohos-app/                          # ★ 鸿蒙宿主工程(DevEco Studio 打开这个目录)
        ├── build-profile.json5            # 工程级:SDK 版本 / 签名 / 产物
        ├── AppScope/app.json5             # 应用级:bundleName / 版本 / 图标 / 显示名
        ├── oh-package.json5  hvigorfile.ts
        └── entry/                         # 主模块(HAP)
            └── src/main/
                ├── module.json5           # EntryAbility + deviceTypes(含 2in1)
                ├── ets/
                │   ├── entryability/EntryAbility.ets   # 加载 pages/Index
                │   └── pages/Index.ets    # ★ ArkWeb 宿主 + NativeBridge(原生桥)
                └── resources/
                    └── rawfile/webapp/    # ★ 打包后的 Web 前端(部署产物,由 sync 脚本生成)
                        ├── index.html  app.bundle.js  styles.css

要点说三句:

  • 鸿蒙宿主工程是 harmony-port/ohos-app/,用 DevEco Studio 要打开的就是这个目录(不是仓库根,也不是 harmony-port/)。
  • harmony-port/app/ 是 Web 前端的开发源,可以脱离鸿蒙、直接在浏览器和 node 里开发测试;ohos-app/.../rawfile/webapp/ 是它打包后的部署产物,由 sync-webapp.sh 拷进去。改了 app/ 里的前端代码,一定要重新同步一次,rawfile 里才是新的。
  • 这套设计让同一份前端在浏览器和鸿蒙里都能跑(靠 app/src/platform.js 做"有桥走原生、无桥退化浏览器"的抽象),开发时绝大多数事情在浏览器里就能调完,省得每次都去打 HAP。

环境要求

项目 要求 备注
DevEco Studio 装好 HarmonyOS SDK 命令行部署用到它自带的 hvigorw / hdc / node,路径见 deploy.sh
compatibleSdkVersion 5.0.0(12) harmony-port/ohos-app/build-profile.json5;DevEco 打开时可能会按你本地 SDK 校正
runtimeOS HarmonyOS 同上
目标设备 2in1(鸿蒙 PC)为主,兼容 tablet / phone module.json5deviceTypes 三种都声明了,真机验证是在 PC(2in1)上做的
Node.js 需要 用来跑前端打包脚本 build-webapp.mjs 和 node 冒烟测试;deploy.sh 用的是 DevEco 自带的 node
Python 3 可选 只在本地用 python3 -m http.server 起前端调试服务器时用
.NET 10 SDK 暂不需要 只有将来要把 C# 核心编成 WASM 时才需要(含 wasm-tools);当前鸿蒙端不依赖 .NET
签名 内置一份本机调试签名 build-profile.json5 里的 signingConfigs 指向 ~/.ohos/config/ 下的证书,换机器要重新生成

应用信息(取自 AppScope/app.json5 与资源文件):

  • bundleNamecom.pinta.ohos
  • 显示名(app_name):Pinta
  • versionName0.1.0versionCode1000000
  • vendorPintaProject

编译与运行

整个流程是「打包前端 → 同步进鸿蒙工程 → 构建 HAP → 装到 2in1 设备」。仓库里 deploy.sh 已经把这几步串成一条命令了,下面先讲手动分步,再讲一键。

1. 拉代码

git clone <仓库地址>
cd ohos_Pinta

2.(建议)先在浏览器里把前端跑一遍

前端是纯 ES 模块、没有构建框架,但因为用了 ES module,得经 http 服务打开,不能 file:// 直接开:

cd harmony-port/app
python3 -m http.server 8000     # 或 npm run serve
# 浏览器打开 http://localhost:8000/

改业务逻辑、调样式建议都先在浏览器里确认,比每次打 HAP 快太多。还可以跑一下 node 冒烟测试:

cd harmony-port/app
npm test        # 实际是 node test/smoke.node.js

3. 打包前端并同步进鸿蒙工程

bash harmony-port/sync-webapp.sh

这一步做两件事:先用 node app/build-webapp.mjsapp/src/ 那十几个模块按 ORDER 拼成单文件 app.bundle.js、连同 index.html / styles.css 一起生成到 app/dist/,再把 app/dist/ 里的 index.html / app.bundle.js / styles.css 拷到:

harmony-port/ohos-app/entry/src/main/resources/rawfile/webapp/

打包脚本带"漏文件防呆":如果你在 app/src/ 新增了模块却忘了加进 build-webapp.mjsORDER,构建会直接失败——这是早期踩过坑加的,漏了的话单文件 bundle 里符号未定义,真机上就是白屏。

4-A. 命令行一键部署(推荐)

装了 DevEco Studio 的机器上,直接:

bash harmony-port/deploy.sh

脚本里依次是:同步前端(调上面的 sync-webapp.sh)→ 用 hvigorw ... assembleHap 构建 HAP → hdc install -r 装到设备 → hdc shell aa start -b com.pinta.ohos -a EntryAbility 启动。它默认从 /Applications/DevEco-Studio.app/Contents 下找 SDK / hvigor / hdc,路径不对的话改脚本顶部那几个变量即可。产物在:

harmony-port/ohos-app/entry/build/default/outputs/default/entry-default-signed.hap

4-B. 用 DevEco Studio 跑

第一次上手或者要调鸿蒙原生侧(Index.ets 的桥)走这条更省心:

  1. 用 DevEco Studio 打开 harmony-port/ohos-app/(注意是这个子目录);
  2. 第一次先 File → Sync and Refresh Project,再到 Project Structure → Signing Configs 配好自动签名(真机/模拟器都要);
  3. 2in1(PC)设备或模拟器,点 Run;
  4. 改了 app/ 里的前端后,回仓库根跑一次 bash harmony-port/sync-webapp.sh 把 rawfile 更新掉,再 Run。

这个项目是如何适配的

和套壳类项目不一样,这里没有"加载原应用"这种事——鸿蒙端的界面和画图是我们按 Pinta 重写 / 重做出来的。逐条说清楚改了什么、为什么这么改。

先验最致命的风险,再决定路线harmony-port/spike-canvas-perf/)。走 Web 路线最怕的是"逐像素滤镜在 Web Canvas 上太慢、根本没法用",这个要是不成立,整条路就废了。所以我们先写了个纯 JS 的 spike,忠实移植了 Pinta 真实的 InvertDesaturate 两个算子(连"预乘 BGRA ↔ 非预乘 RGBA"的转换都如实处理),再用可分离 box blur 代表邻域卷积类的重滤镜,实测下来:12MP(4000×3000)大图上 Invert 约 14.5ms、Desaturate 约 15.5ms,纯 JS 基线就够用,WASM 后还更宽裕;box blur 这类重滤镜(99ms)才是瓶颈,留给 WASM/WebGL 解决。正确性自检 4/4 通过。这个风险排除后,Web 路线才算成立。

一条架构铁律:图层常驻预乘 BGRA,转换只做一次app/src/pixels.js)。spike 里有个意外发现——"RGBA↔预乘BGRA 往返"本身就要 57ms(12MP),比滤镜还贵。所以前端设计上定死了:工作缓冲一直存预乘 BGRA,绝不每帧来回转,转换只在加载和上屏各做一次。document.js 里每个图层的缓冲就是预乘 BGRA,compositor.js 也在预乘空间里合成。

界面用 Web 重做成原版 Pinta 的桌面布局。菜单栏(文件 / 编辑 / 视图 / 图像 / 图层 / 调整 / 效果 / 帮助)+ 工具栏 + 左侧工具箱 + 右侧停靠面板(图层 / 历史 / 调色板)+ 状态栏,尽量贴着原版 Pinta 的样子来,这样老用户上手不别扭。这部分在 ui.js / app.js 里装配。

画布渲染和工具用 JS + Canvas2D 重写canvasview.js 负责渲染循环和指针输入派发(只有内容变了才重新合成,省开销);tools.js 实现画笔(轨迹插值落墨)、抓手等;viewport.js 管平移缩放的坐标变换。Cairo 那套直绘换成 Canvas2D,因为两边 API 几乎对得上。

滤镜 / 调整按 Pinta 的算法逐个移植filters.js / adjustments.js / imageops.js)。InvertDesaturate 是从 Pinta 忠实移植的,box blur 先放了个重算子占位。调整、图像翻转旋转这些也在这几个文件里。

撤销 / 重做用脏矩形快照history.js)。历史面板 + Ctrl+Z / Ctrl+Y,配合脏矩形快照做局部恢复。

选区(Selection)。矩形选区带蚂蚁线虚线框,画笔 / 橡皮 / 填充 / 滤镜都只作用于选区内;Esc 或"取消选择"清除。

鸿蒙宿主:ArkWeb 加载 rawfile 里的单文件前端ohos-app/entry/src/main/ets/pages/Index.ets)。用 Web 组件、src 指向 $rawfile('webapp/index.html')。这里有个坑:ES module 在 resource://rawfile 方案下可能被拦,所以我们才在 build-webapp.mjs 里把前端打成单文件经典脚本绕开它。Web 组件上开了 javaScriptAccess / domStorageAccess / fileAccess / imageAccess

原生桥:registerJavaScriptProxy 把系统能力注入前端Index.ets 里的 NativeBridge)。在 onControllerAttached 里把 NativeBridge 注册成 window.harmony,暴露两个方法:platform(同步,返回 'harmonyos',给前端探测平台用)和 savePng(异步,注册在异步方法列表里,JS 端拿到的是 Promise)。savePng 的实现是:把前端传来的 data:image/png;base64,... 解码,用 picker.DocumentViewPicker 弹系统文件管理器让用户自选保存位置和文件名,再用 fileIo 写盘;取消返回 'cancelled',出错返回 'error: ...'

前端侧的平台抽象:同一份前端两处跑app/src/platform.js)。Platform.isHarmonywindow.harmony 是否存在判断;保存 PNG 时,鸿蒙里走 window.harmony.savePng(弹系统文件管理器落盘),浏览器里退化成 <a download> 触发下载。所以前端代码不用写两份,环境差异这一层就吸收掉了。

应用配置module.json5deviceTypes 声明了 2in1 / tablet / phone,主攻 PC(2in1);入口 EntryAbilityonWindowStageCreateloadContent('pages/Index')。目前没申请网络权限——rawfile 是本地加载,用不上;将来如果改成加载远程前端,才需要在 module.json5ohos.permission.INTERNET。图标是 1024×1024 的设计版 logo,换图标后得卸载重装才能刷新系统图标缓存。


功能支持情况

状态只有两个,含义如下:

  • 能用:在鸿蒙 PC 真机上实际用过、正常。
  • 未适配:桌面端特有、或平台没有对应概念的能力,按平台特点主动换了做法或没做,是取舍不是没做完。

补充一句口径上的不同:这里和套壳项目最大的不同是,鸿蒙端能力是我们用 Web 栈重做的,不是把原 C# 跑起来,所以"原版有、这里还没有"的功能会比套壳类多——这是路线决定的,原 C# 几十个滤镜要一个个移植 / 将来上 WASM。

真机验证依据:harmony-port/README.mdharmony-port/ohos-app/README.md 里的真机记录。这两份 README 按截图文件名(pinta-on-device.jpeg / pinta-v2-*.jpeg / pinta-v3-*.jpeg 等)引用了验证截图,但截图本身没有随仓库提交,当前树里找不到这些图片文件。下面凡写"真机验过"的,都对应 README 里的文字记录。

T0 基础能力(装得上、起得来、画得出)

这一档是底线:装不上、起不来、一打开白屏,后面都白搭。这一层在真机上全部跑通。

能力 状态 说明
编译打包成 HAP 能用 deploy.sh(hvigorw assembleHap)或 DevEco 都能构建,产物 entry-default-signed.hap
安装 / 启动 / 不白屏 能用 真机装好能起、进主界面;前端打成单文件经典脚本规避了 rawfile 下 ES module 被拦的白屏问题
ArkWeb 加载前端、界面渲染 能用 Pinta 风格的桌面布局(菜单 + 工具栏 + 工具箱 + 停靠面板 + 状态栏)真机完整渲染
原生桥生效(window.harmony 能用 真机状态栏显示"平台 harmonyos (ArkWeb)",registerJavaScriptProxy 注入成功
画笔交互可绘 能用 真机上画得出来;指针输入派发正常

小结:T0 这层在 2026-06-11 真机上过了——能构建签名、能装能起、不白屏、桥生效、能画。Web 路线在鸿蒙 PC 上能跑这件事,这次验证到了。

T1 主要能力(一个画图软件日常要用的)

平时真正会用的就是这一档:工具、图层、调整、效果、撤销重做、选区、存盘。这些都在真机上实测可用。

能力 状态 说明
画笔 / 铅笔 / 橡皮擦 / 填充 / 吸管 能用 真机实测可用,指针输入派发正常
矩形选区(蚂蚁线) 能用 真机拖拽成框验过;画笔/橡皮/填充/滤镜均只作用选区内
选区约束填充 / 选区约束滤镜 能用 真机验过(选区内 invert 仅处理选区,2.9–4.4ms)
缩放 / 抓手(平移缩放) 能用 真机实测可用,viewport.js 坐标变换
图层:增 / 复制 / 删 / 上下移 / 显隐 / 选择 能用 真机实测可用,node 冒烟测试同样通过
图层:不透明度 / 混合模式(正常·正片叠底·滤色) 能用 真机实测可用(预乘空间合成)
调整:亮度·对比度(对话框实时预览) 能用 真机实测可用
调整:自动色阶 / 黑白 / 反色 / 色调分离 / 棕褐 能用 真机实测可用(反色滤镜 8.6ms)
效果:模糊(Box Blur) 能用 真机可用,纯 JS 下偏慢(重算子),后续靠 WASM/WebGL 提速
图像:水平/垂直翻转、旋转 90°/180°、拼合、裁剪到选区 能用 真机实测可用,node 测同样通过
编辑:全选 / 取消选择 / 填充选区 / 擦除选区 能用 真机实测可用
撤销 / 重做(历史面板,Ctrl+Z/Y) 能用 真机验过逐笔撤销(只撤最后一笔);脏矩形快照
调色板 能用 真机实测可用
打开图片 / 导出 PNG 能用 导出 PNG 经原生桥 savePng 弹系统文件管理器自选位置落盘,真机验过

小结:T1 这层"能画、能分层、能调、能撤、能存"的核心闭环已经在鸿蒙 PC 真机上跑通,逐项实测可用。node test/smoke.node.js 23/23 通过。Box Blur 功能本身可用,只是纯 JS 实现下偏慢,后续上 WASM/WebGL 提速。

T2 增强 / 平台受限能力(原版的加分项 / 还在路上的部分)

这一档是原版 Pinta 有、但这次路线下还没来得及做、或者得靠后续技术手段才划算做的部分。

能力 状态 说明
原生"保存为可再编辑工程格式" 未适配 导出 PNG 的 picker 落盘已通、真机验过(见 T1);工程格式保存是 README 里列的下一步,当前实现只到 PNG
其余 30+ 滤镜 / 效果 未适配 原 C# Pinta.Effects 里还有几十个效果没移植;按算法逐个重写 / 将来上 WASM 复用
形状 / 文字 / 渐变 / 魔棒等工具 未适配 这阶段没实现,属于后续工具扩展,不放假按钮
更多可配置的调整 未适配 同上,先把核心几个做扎实
C# 核心编成 WASM 复用 未适配 被 Cairo 耦合阻挡,需先做 Cairo 抽象层 + 装 .NET 10 SDK / wasm-tools,属于后续进阶工作
OffscreenCanvas → Worker → WebGL、8K 大画布内存 未适配 重滤镜上 WASM/WebGL 时才需要的进阶能力,本阶段未实现
网络权限相关能力 未适配 当前 rawfile 本地加载不需要联网,故未申请 ohos.permission.INTERNET

小结:T2 这层大部分标"后续",原因是路线决定的——原版几十个滤镜和高级工具是 C# 写的,要么按算法重写、要么等 C# 核心编成 WASM 才能整批复用,而 WASM 这条还卡在"得先抽掉 Cairo 耦合"上,属于后续进阶工作。这些是"还在路上",不是"按平台取舍掉了"——和套壳项目里"托盘/菜单栏按平台习惯不做"性质不同,得区分开。


这么算下来,整体到什么程度

把三档合起来看:

  • T0 基础能力:构建签名、装机启动、不白屏、ArkWeb 加载、原生桥、画笔可绘,真机全部验过、可以正常使用。这是底线,已经到位。
  • T1 主要能力:核心画图闭环(工具 / 图层 / 调整 / 效果 / 撤销重做 / 选区 / 导出 PNG)已在鸿蒙 PC 真机上逐项实测可用,node 冒烟测试 23/23 过。Box Blur 功能本身可用,只是纯 JS 实现下偏慢。
  • T2 增强能力:导出 PNG 的原生 picker 已通、真机验过;其余几十个滤镜、形状/文字/渐变等工具、以及 C# 核心编 WASM 都还在后续清单里,这是路线和阶段决定的,属于"还在路上"。

一句话总结:这次不是把 Pinta 原样搬上鸿蒙——GTK 和 .NET 运行时这两堵墙决定了那条路走不通。我们选的是 ArkWeb + Web 前端重写、C# 核心当规格逐步移植 / 留给 WASM 的路线,先把最致命的 Web 渲染性能风险验掉,再把一个有图层、有选区、有撤销重做、能导出 PNG 的画图 MVP 在鸿蒙 PC 真机上跑通、实测可用。剩下的是把原版那几十个滤镜和高级工具一项项补齐,并把 C# 核心编成 WASM 复用。

适配的第一手细节、方案对比、真机验证记录都在 harmony-port/README.mdharmony-port/ohos-app/README.md 里(这两份 README 按文件名引用了验证截图,但截图本身未随仓库提交)。

项目介绍

基于 .NET/C#+GTK4 的绘图软件,因鸿蒙禁 JIT 致 .NET 运行时无法落地,改用「ArkWeb 宿主+Web/Canvas2D 前端+C# 核心编 WASM」路线并确立图层缓冲常驻预乘 BGRA 的性能铁律,最终在鸿蒙 PC 跑通画笔、撤销重做、选区与滤镜的图像编辑 MVP。

定制我的领域