欢迎加入鸿蒙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 以及其他插件完成不同格式的图片读取和写入。

这次示例里用到的能力主要是:

  • 读取图片内容;
  • 获取图片属性;
  • 把图片重新编码为 JPGPNGWEBPBMPTIFF 等格式;
  • 处理部分常见格式兼容问题,例如带透明通道的 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 imageioimport PILimport numpy。所以用户侧需要先把 Python 运行环境和三方库都准备好。

普通用户可以按下面顺序操作:

  1. 在鸿蒙 PC 上打开应用市场,搜索并安装 Python 安装器。鸿蒙 PC 默认不一定自带可供 HNP 调用的 Python 环境,安装器完成安装后,设备上才会出现 HNP Python。
  2. 打开 Python 安装器,按页面提示完成 Python 运行环境安装。
  3. 打开 HiShell 或鸿蒙 PC 终端,确认 python3.12 能执行。
  4. 使用同一个 HNP Python 安装 ImageIO 示例需要的 Python 包:imageiopillownumpy
  5. 最后执行 import 验证。只有 imageioPILnumpy 都能成功导入,鸿蒙 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。

后来改成了真正的工具流:

  1. 用户点击 选择图片
  2. 通过 DocumentViewPicker.select() 选择鸿蒙 PC 上的图片文件;
  3. 应用把选择器返回的 URI 复制到应用沙箱;
  4. 用户选择目标格式;
  5. 用户点击 转换并导出
  6. 通过 DocumentViewPicker.save() 选择保存位置;
  7. Python worker 转码;
  8. 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

在这里插入图片描述

十、真机使用:选图、选格式、导出

应用运行起来后,用户看到的是一个图片转换器,而不是技术验证页面。

操作流程如下:

  1. 点击 选择图片
  2. 从鸿蒙 PC 文件选择器里选择一张图片;
  3. 目标格式 里选择 JPGPNGWEBPBMPTIFF
  4. 如果是 JPGWEBP,可以调整 图片质量
  5. 点击 转换并导出
  6. 在保存选择器里选择目标位置;
  7. 应用完成转码并写入用户指定位置。

在这里插入图片描述

十一、验证导出结果

导出完成后,建议不要只看应用状态,还要打开用户选择的保存目录,确认文件真的写到了目标位置。

可以这样验证:

  1. 在应用中选择一张 PNG;
  2. 目标格式选择 JPG
  3. 点击 转换并导出
  4. 保存到桌面或一个容易找到的目录;
  5. 打开文件管理器,进入保存目录;
  6. 用图片查看器打开导出的 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 工具库,都可以沿着同样的方式继续扩展。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐