欢迎光临
我们一直在努力

鸿蒙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)
    未经允许不得转载:171主机测评 » 鸿蒙PC迁移:ImageIO Python 三方库鸿蒙PC适配全记录
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址