鸿蒙PC迁移:ImageIO Python 三方库鸿蒙PC适配全记录
欢迎加入鸿蒙PC开发者社区,共同打造开发者工具生态:鸿蒙PC开发者社区:https://harmonypc.csdn.net/
项目开源地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_imageio
欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
这篇文章记录的是一次把 Python 图片处理三方库 ImageIO 接入 HarmonyOS PC / 鸿蒙 PC 应用的完整过程。
和很多 Electron、Qt、Java Swing 项目不同,ImageIO 本身并不是一个桌面 GUI 应用。它是一个 Python 图片读写库,主要能力是读取图片、获取图片属性、把图片重新编码为其他格式。也就是说,这次适配的关键不是“把一个现成窗口搬到鸿蒙 PC 上”,而是要解决一个更通用的问题:
鸿蒙 PC 应用如何调用已经存在于 HNP Python 环境里的 Python 三方库,并把它做成用户真正能使用的小工具?
最终我们在项目里新增了一个 examples/harmony_pc/ 示例工程。这个工程不是一个命令行验证 demo,而是一个真实可用的鸿蒙 PC 图片格式转换工具:用户可以选择本机图片,选择目标格式,然后导出到指定位置。

一、项目背景:ImageIO 是什么,为什么不能只改后缀
ImageIO 是 Python 生态里常用的图片读写库,底层可以配合 Pillow、NumPy 以及其他插件完成不同格式的图片读取和写入。
这次示例里用到的能力主要是:
- 读取图片内容;
- 获取图片属性;
- 把图片重新编码为
JPG、PNG、WEBP、BMP、TIFF等格式; - 处理部分常见格式兼容问题,例如带透明通道的 PNG 转 JPG。
这里要特别说明一点:图片格式转换不是“把文件后缀改掉”。如果只是把 a.png 改名成 a.jpg,文件内部仍然是 PNG 编码,很多图片查看器或上传系统会识别异常。真正的格式转换需要读取原始图片像素数据,再按照目标格式重新编码写出。
本次鸿蒙 PC demo 做的就是后者。比如 PNG 转 JPG 时,Python worker 会执行类似下面的逻辑:
image = iio.imread(input_path)
iio.imwrite(output_path, image, extension=".jpg", quality=90)
这样输出文件内部就是 JPEG 编码,而不是一个伪装成 .jpg 的 PNG 文件。
二、路线选择:不重写 ImageIO,而是通过 HNP Python 调用
适配前先分析了几条路线。
第一条路线是把 ImageIO 或 Pillow 的能力改写成鸿蒙 ArkTS / Native 实现。这条路理论上最“原生”,但工作量很大,而且 ImageIO 本身还涉及 NumPy、Pillow、插件后端、图片格式兼容等问题。为了一个 demo 直接重写整套图片处理能力,性价比不高。
第二条路线是保留 Python 三方库,让鸿蒙应用通过 HNP Python 调用 ImageIO。这条路线更适合当前场景:鸿蒙 PC 上已经能运行 Python,也能在 HNP Python 里安装 Python 三方库,那么应用侧只要把参数传进去,让 Python worker 做真正的图片处理即可。
最终采用的是第二条路线:
鸿蒙 ArkTS 页面
-> ArkTS Client 封装请求
-> Native/N-API 启动 HNP Python
-> Python worker import imageio
-> ImageIO 完成图片转码
-> 结果返回给 ArkTS 页面
这条路线的好处是:
- 不需要重写 ImageIO;
- 可以复用 Python 生态里已有的能力;
- 适合工具类、批处理类、图片处理类应用;
- 后续换成其他 Python 三方库时,也能复用同一套桥接思路。
限制也很明确:
- 设备上必须存在可用的 HNP Python;
- Python 环境里必须已经安装目标三方库;
- 部分带 native 扩展的 Python 包需要额外验证 ABI、动态库和沙箱限制;
- 当前 demo 的 Native 调用是同步的,生产项目里建议放到后台线程。
三、先确认 HNP Python 里三方库能跑通
在写鸿蒙 UI 之前,第一步不是打开 DevEco Studio,而是先确认真机上的 HNP Python 是否能正常运行。
这里的“Python 环境”不能只理解成设备上能执行 python3.12。这个示例最终要在鸿蒙应用里启动 HNP Python,再让 Python worker 去 import imageio、import PIL、import numpy。所以用户侧需要先把 Python 运行环境和三方库都准备好。
普通用户可以按下面顺序操作:
- 在鸿蒙 PC 上打开应用市场,搜索并安装
Python 安装器。鸿蒙 PC 默认不一定自带可供 HNP 调用的 Python 环境,安装器完成安装后,设备上才会出现 HNP Python。 - 打开
Python 安装器,按页面提示完成 Python 运行环境安装。 - 打开 HiShell 或鸿蒙 PC 终端,确认
python3.12能执行。 - 使用同一个 HNP Python 安装 ImageIO 示例需要的 Python 包:
imageio、pillow、numpy。 - 最后执行 import 验证。只有
imageio、PIL、numpy都能成功导入,鸿蒙 App 里调用 Python worker 才有基础保障。
本次真机上使用的 Python 路径是:
/data/service/hnp/python.org/python_3.12/bin/python3.12
可以在鸿蒙 PC 的 HiShell 里执行:
/data/service/hnp/python.org/python_3.12/bin/python3.12 --version
如果版本命令能输出结果,再用同一个 Python 安装本示例需要的三方库:
/data/service/hnp/python.org/python_3.12/bin/python3.12 -m pip install -U imageio pillow numpy
如果真机不能直接联网,也可以把适配鸿蒙 PC / HNP Python 的 wheel 包提前放到设备本地,再离线安装:
/data/service/hnp/python.org/python_3.12/bin/python3.12 -m pip install /path/to/imageio*.whl /path/to/pillow*.whl /path/to/numpy*.whl
安装完成后,再验证 Python 三方库能否真正导入:
/data/service/hnp/python.org/python_3.12/bin/python3.12 -c "import imageio, PIL, numpy; print('imageio', imageio.__version__); print('pillow', PIL.__version__); print('numpy', numpy.__version__)"
这里有个容易忽略的小点:安装包名叫 pillow,但 Python 代码里导入的是 PIL。所以验证命令里要写 import PIL,不能写成 import pillow。
如果这里失败,说明问题还在 Python 环境阶段,鸿蒙应用里也无法直接调用成功。需要先处理 Python 包安装、依赖动态库、权限或 ABI 问题。

四、创建鸿蒙 PC 示例工程
适配完成后,项目新增了一个鸿蒙 PC 示例目录:
examples/harmony_pc/
AppScope/
entry/
hvigor/
build-profile.json5
oh-package.json5
imageio_hnp_bridge.py
imageio_hnp_demo.py
sample_request.json
run_hishell.sh
其中真正的鸿蒙应用工程就在 examples/harmony_pc,不是再嵌套一层 harmony_app。这样 DevEco Studio 打开目录时更直接,也避免目录层级过深。
几个关键文件如下:
entry/src/main/ets/pages/Index.ets
entry/src/main/ets/imageio/ImageioHnpClient.ets
entry/src/main/ets/native/ImageioHnpNative.ets
entry/src/main/ets/util/RawFileSync.ets
entry/src/main/cpp/napi_init.cpp
entry/src/main/resources/rawfile/python/imageio_hnp_bridge.py
每个文件的职责比较清楚:
Index.ets:用户界面,处理选择图片、选择格式、保存位置和结果展示;ImageioHnpClient.ets:ArkTS 侧的业务封装,把转换操作变成 JSON 请求;ImageioHnpNative.ets:Native 模块的 ArkTS 类型声明;RawFileSync.ets:负责 rawfile 同步和 URI / 沙箱文件复制;napi_init.cpp:通过 N-API 暴露 Native 方法,并启动 HNP Python;imageio_hnp_bridge.py:真正 import ImageIO 并执行图片转码的 Python worker。

五、ArkTS 页面:从技术 demo 改成用户能用的小工具
最开始的页面只是技术验证:准备后端、生成示例图片、读取属性、转 JPG。这种页面可以证明链路能跑,但不算真正的用户 demo。
后来改成了真正的工具流:
- 用户点击
选择图片; - 通过
DocumentViewPicker.select()选择鸿蒙 PC 上的图片文件; - 应用把选择器返回的 URI 复制到应用沙箱;
- 用户选择目标格式;
- 用户点击
转换并导出; - 通过
DocumentViewPicker.save()选择保存位置; - Python worker 转码;
- ArkTS 把沙箱输出复制到用户选择的保存 URI。
之所以要多一步“复制到应用沙箱”,是因为鸿蒙文件选择器返回的是 URI,而 Python 三方库通常更习惯处理真实文件路径。直接把 URI 传给 Python,不一定能稳定工作。这里采用的方式是:
用户文件 URI
-> RawFileSync.copyFile()
-> 应用沙箱 input 路径
-> Python 读取 input
-> Python 写入 output
-> RawFileSync.copyFile()
-> 用户保存 URI
页面中的核心调用可以理解成:
const result = this.client.convert(
this.selectedInputPath,
outputPath,
this.currentFormatExtension(),
quality
);
这一步不会直接操作 ImageIO,而是把业务参数交给 ImageioHnpClient。
六、ArkTS Client:把业务操作变成 JSON 请求
ImageioHnpClient.ets 是应用侧调用 Python 的封装层。
它做了几件事:
- 确定应用沙箱工作目录;
- 把 rawfile 里的 Python worker 复制到沙箱;
- 把 ArkTS 业务参数写成 JSON 请求文件;
- 调用 Native 方法启动 HNP Python;
- 把 Python stdout 解析成
ImageioHnpCallResult。
图片转换时,ArkTS 会生成类似这样的请求:
{
"op": "convert",
"input": "/data/storage/el2/base/files/imageio-hnp/inputs/input.png",
"output": "/data/storage/el2/base/files/imageio-hnp/outputs/output.jpg",
"format": "jpg",
"quality": 90
}
然后调用:
imageioHnpNative.runRequestJson(
this.pythonPath,
this.paths.bridgeScriptPath,
requestPath,
this.paths.workDir
);
这里的 requestPath 是 JSON 请求文件路径,bridgeScriptPath 是 Python worker 在应用沙箱中的路径。
七、Native/N-API:启动 HNP Python 进程
ArkTS 本身不能直接像 Python 一样 import imageio,所以中间需要 Native/N-API 做桥接。
napi_init.cpp 暴露了 runRequestJson() 方法。它接收 ArkTS 传过来的 Python 路径、worker 路径、请求 JSON 路径和工作目录,然后拼出一条命令:
cd <workDir> &&
PYTHONDONTWRITEBYTECODE=1
OPENBLAS_NUM_THREADS=1
OMP_NUM_THREADS=1
GOTO_NUM_THREADS=1
NUMEXPR_NUM_THREADS=1
VECLIB_MAXIMUM_THREADS=1
OPENBLAS_MAIN_FREE=1
<pythonPath> <bridgeScriptPath> --request <requestJsonPath> 2>&1
这里有两个细节:
第一,命令会先 cd 到工作目录。这样 Python worker 运行时的相对路径、临时文件和请求文件都更可控。
第二,设置了一组 OpenBLAS / NumPy 相关环境变量。早期真机测试时,NumPy 依赖的 OpenBLAS 在鸿蒙应用沙箱里可能触发 SIGSYS(SYS_SECCOMP),所以这里把线程数和相关探测行为压到最低,避免触发被限制的系统调用。
八、Python worker:真正调用 ImageIO 的地方
imageio_hnp_bridge.py 是整个示例里真正调用 Python 三方库的地方。
它启动后会读取 JSON 请求,根据 op 分发到不同函数:
OPS = {
"demo": op_demo,
"props": op_props,
"convert": op_convert,
}
转换图片时执行:
image = iio.imread(input_path)
image = _prepare_image_for_output(image, output_format)
iio.imwrite(output_path, image, **kwargs)
其中 _prepare_image_for_output() 用来处理一些真实用户会遇到的问题。例如 PNG 带透明通道时,直接写 JPG 容易报错,因为 JPG 不支持 alpha 通道。这里会把透明图合成到白底,再写出 JPG。
Python worker 最后会输出 JSON:
{
"ok": true,
"op": "convert",
"input": "...",
"output": "...",
"format": "jpg",
"shape": [1080, 1920, 3],
"dtype": "uint8"
}
Native 层拿到 stdout 后返回给 ArkTS 页面,页面再显示转换结果。
九、构建、签名和安装
这个示例使用 DevEco / Hvigor 构建。当前本机验证使用的 SDK 是 HarmonyOS 6.0.2 / API 22。
在 examples/harmony_pc 目录执行:
DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk \
/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw assembleApp --no-daemon
构建成功后,可以找到签名后的 HAP:
entry/build/default/outputs/default/entry-default-signed.hap
安装到鸿蒙 PC 真机:
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
启动应用:
hdc shell aa start -a EntryAbility -b org.imageio.hnp.example -m entry

十、真机使用:选图、选格式、导出
应用运行起来后,用户看到的是一个图片转换器,而不是技术验证页面。
操作流程如下:
- 点击
选择图片; - 从鸿蒙 PC 文件选择器里选择一张图片;
- 在
目标格式里选择JPG、PNG、WEBP、BMP或TIFF; - 如果是
JPG或WEBP,可以调整图片质量; - 点击
转换并导出; - 在保存选择器里选择目标位置;
- 应用完成转码并写入用户指定位置。

十一、验证导出结果
导出完成后,建议不要只看应用状态,还要打开用户选择的保存目录,确认文件真的写到了目标位置。
可以这样验证:
- 在应用中选择一张 PNG;
- 目标格式选择
JPG; - 点击
转换并导出; - 保存到桌面或一个容易找到的目录;
- 打开文件管理器,进入保存目录;
- 用图片查看器打开导出的 JPG。
如果导出的图片可以正常打开,并且文件后缀、文件大小、查看器识别格式都正常,就说明这不是简单改后缀,而是真正完成了转码。

十二、适配过程中踩到的关键问题
1. 不能让 Python 直接处理文件选择器 URI
鸿蒙文件选择器给到 ArkTS 的是 URI,而 ImageIO、Pillow 这类 Python 库通常更习惯普通文件路径。为保证稳定,最终采用了“先复制到应用沙箱,再交给 Python”的方式。
对应工具方法是:
RawFileSync.copyFile(inputUri, localInputPath);
导出时再反过来:
RawFileSync.copyFile(localOutputPath, saveUri, false);
这样 Python 只处理沙箱里的普通路径,系统 URI 的读写由 ArkTS 侧负责。
2. 转换完成后必须写入用户指定位置
早期版本里,转换完成后只是生成到了应用沙箱目录,用户还需要再点一次导出。这个流程不符合真实工具习惯。
最终改成一个按钮完成:
转换并导出
-> 选择保存位置
-> Python 转码
-> 写入保存 URI
这样用户点击一次后,结果就会落到用户指定的位置。
3. PNG 透明通道转 JPG
JPG 不支持 alpha 通道。如果用户选择一张透明 PNG 转 JPG,Pillow 可能报类似 “cannot write mode RGBA as JPEG” 的错误。
为减少用户踩坑,Python worker 里增加了处理逻辑:当目标格式是 JPG/JPEG 且输入图片有 alpha 通道时,先合成到白底,再写出 JPG。
4. NumPy / OpenBLAS 的 seccomp 问题
真机测试中曾经遇到过 SIGSYS(SYS_SECCOMP),堆栈和 NumPy 依赖的 OpenBLAS 有关。这个问题不是 ArkTS 编译问题,而是 native 扩展库在应用沙箱里触发了受限系统调用。
示例里在 Native 和 Python 两层都设置了:
OPENBLAS_NUM_THREADS=1
OMP_NUM_THREADS=1
GOTO_NUM_THREADS=1
NUMEXPR_NUM_THREADS=1
VECLIB_MAXIMUM_THREADS=1
OPENBLAS_MAIN_FREE=1
PYTHONDONTWRITEBYTECODE=1
这能规避一部分 OpenBLAS 初始化和多线程探测问题。
十三、这种方式怎么迁移到其他 Python 三方库
这次 ImageIO 适配最有价值的地方,不只是做了一个图片转换器,而是整理出了一种通用模式。
如果要接入其他 Python 三方库,可以按下面步骤做:
第一步,先在 HNP Python 里验证库能 import:
/data/service/hnp/python.org/python_3.12/bin/python3.12 -c "import your_lib; print('ok')"
第二步,写一个 JSON bridge:
def op_task(request):
input_path = request["input"]
output_path = request["output"]
result = your_lib.do_something(input_path, output_path)
return {"ok": True, "result": result}
第三步,ArkTS Client 只负责生成请求:
return this.runRequest({
op: 'task',
input: inputPath,
output: outputPath
}, 'task');
第四步,Native 层继续复用 runRequestJson():
HNP Python + bridge.py + request.json
第五步,文件类任务统一走沙箱中转:
URI -> 沙箱 input -> Python -> 沙箱 output -> 保存 URI
这样接入其他库时,不需要每次重新设计一套鸿蒙与 Python 的通信方式。
十四、总结
这次 ImageIO 鸿蒙 PC 适配的重点,不是单纯证明“Python 命令能跑”,而是把 Python 三方库能力接入到一个真实鸿蒙 PC 应用里,并形成用户可操作的完整闭环。
最终完成的能力包括:
- 新增
examples/harmony_pc鸿蒙 PC 工程; - ArkTS 页面实现选择图片、选择格式、质量调节、转换并导出;
- ArkTS Client 把业务操作封装成 JSON 请求;
- Native/N-API 启动 HNP Python;
- Python worker 调用 ImageIO 完成真实图片转码;
- 处理文件选择器 URI 和应用沙箱路径之间的转换;
- 修复透明 PNG 转 JPG、OpenBLAS seccomp 等真实环境问题;
- 真机上完成构建、安装和运行验证。
从迁移经验看,鸿蒙 PC 调用 Python 三方库时,最重要的是把层次拆清楚:
页面负责用户交互
ArkTS Client 负责请求封装
Native 负责启动 Python
Python worker 负责三方库能力
ArkTS 文件工具负责 URI 和沙箱路径转换
只要这条链路稳定,后续不管是 ImageIO、OpenCV、Pandas,还是其他 Python 工具库,都可以沿着同样的方式继续扩展。
更多推荐



所有评论(0)