如何在iOS上运行Python+Pillow(PIL)(一)

如何在iOS上运行Python+Pillow(PIL)

  • 先看下最终App运行结果
  • 使用到的架构和工具
    • 第一步:使用[Beeware](http://beeware.org)生成一个App基础架构
    • 第二步:编译pillow静态库
    • 第三步:深入修改集成objective-c
    • 第四步,开始真正的python编程

Pillow是强大的图像处理器,却很难在iOS上运行,能在iOS上运行pillow,几乎是很多人甚至技术公司的需要。这使得手机作为当前常用的拍照工具无法能借助Pillow做图像处理。同时,使用python开发iOS App也是当前的一个重要需求。开源kivy和beeWare在这方面都有发展。

然而,通过google和百度出来的python+pillow+iOS的结果,都几乎没有可用的,或者说基本没有真正能跑的通的一个真实项目。

本文就是阐述,如何一步步,通过使用beeWare的基础架构工具(Rubicon和Toga),让Pillow(PIL)成功运行于iOS系统上。

App代码完全使用Python开发。最后的App功能就是允许从手机相册中选取一张图片,然后使用各种PIL图像功能对图像进行处理,并展示最终图像处理结果。

先看下最终App运行结果

如何在iOS上运行Python+Pillow(PIL)(一)_第1张图片
如何在iOS上运行Python+Pillow(PIL)(一)_第2张图片如何在iOS上运行Python+Pillow(PIL)(一)_第3张图片
从结果可见,python可访问手机相册,获得图片后,使用PIL功能进行各种处理,处理结果可以展现。只要稍稍改进,便可成为iOS的图片处理的App。当然,本文并不是针对如何开发App,因此,这部分留给读者自己扩展。

使用到的架构和工具

  1. BeeWare的Rubicon作为Python和Object-C的开发桥梁
  2. Beeware的toga作为跨平台的GUI工具
  3. Python3.7编译好的静态库.a (来自beeware suit)
  4. Pillow的C源程序和py源程序和图像解码插件libimg的c源程序

第一步:使用Beeware生成一个App基础架构

如果仅仅让Python+Pillow运行在iOS上,把python解释器编译成静态库,通过接口,也勉强可以运行。但实际问题是,如何读取相册,如何展示图片,这些必须用到objective- c的基础库。因此,和objective c的集成是必不可少的。这点上,beeWare的框架做的还不错,随着他提供的教程步骤,6步就能生成框架。熟悉的人几分钟就够了,当然对了解beeware的rubicon和toga的人来说,自己手动安装使用也可以。

beeware的App框架6步教程在这里:
https://docs.beeware.org/en/latest/

第二步:编译pillow静态库

去github下载pillow-master源程序,pillow的底层是使用c开发的,因此,需要把底层库编译成静态库才能在iOS上运行。特别注意的是,pillow的图像解码器部分是插件形式的,因此,解码器是单独的源程序,插件部分在github上fork比较多,但差别都不太大,一般搜索libimg下面的某个版本就可以了。

接下来是最痛苦的部分,如果你直接用make编译,99%的情况会直接fail掉,而且就算成功,也不是静态库。用xcode的libtool转过来也不能用。因此,自己在xcode下创建静态库项目,然后再进行编译是最佳方案,当然,想编译成功需要非常多的细节工作,包括修改.h 和.c的源文件。

这里要说明的是,为了避免单独插件的编译,我把插件的源代码和pillow的源代码合并编译了,结果这个坑很深,调试了好久,修改了不知道多少代码,最终,成功编译。

我已经把编译好的静态库上传到github,一般需要可以直接下载使用,一个是libpillow_simu.a 使用模拟器的,一个是libpillow_iphone.a是给真机用的,

第三步:深入修改集成objective-c

此时如果正常,通过添加以下code,应该可以正常使用pillow了。

在第一步的App项目框架中,找到main.m, 打开后,在其头部(main(。。。)的上面),添加如下代码:

void imaging_importer() {
    PyRun_SimpleString(
        "import sys, importlib\n" \
        "class ImagingImporter(object):\n" \
        "    def find_module(self, fullname, mpath=None):\n" \
        "        if fullname in (" \
        "                    '_imaging', " \
      "                ):\n" \
        "            return self\n" \
        "        return\n" \
        "    def load_module(self, fullname):\n" \
        "        f = '__' + fullname.replace('.', '_')\n" \
        "        mod = sys.modules.get(f)\n" \
        "        if mod is None:\n" \
        "            mod = importlib.__import__(f)\n" \
        "            sys.modules[fullname] = mod\n" \
        "            return mod\n" \
        "        return mod\n" \
        "sys.meta_path.append(ImagingImporter())"
    );
}

extern PyMODINIT_FUNC
PyInit__imaging(void);

这部分就是定义把静态库load进内存以便于以后使用。

ok,现在代码往下翻以下,找到这句:
PyEval_InitThreads();
在这句后面添加如下代码:

       imaging_importer();

至此,完成了代码,当然如果编译成功,就可以正常调用PIL使用了。但是,要编译成功,需要进行适当的配置。

  1. 首先就是arch,这个是xcode的基础配置,我们一般开始都在simulator上调试,因此,设置成x86_64,

  2. 现在,需要把pillow静态库加进来了,点击target,选择building phases,在link binary with libraries中,添加libpillow-simu.a. 如下图:
    如何在iOS上运行Python+Pillow(PIL)(一)_第4张图片
    3,把PIL的.py源程序,添加进project的support package里面。当然单独建一个目录。下面是正常的下面的重要目录结构:
    如何在iOS上运行Python+Pillow(PIL)(一)_第5张图片

  3. 试试你的运气把,点击build,看看是否编译链接成功。如果成功,恭喜你,最关键的一部,完成了。如果发现错误,一般是链接错误,那么或者配置arch问题,或者静态库没链接对。

第四步,开始真正的python编程

既然我们用python编iOS的app,不写python怎么能行。现在进入真正的功能实现方面。
我们的功能是允许打开相册,选取一张照片,使用PIL进行图片处理,然后再把处理结果展示处理。很简单有没有?嗯嗯,欢迎入坑。
真正开始编程时发现,beeware确实就是个框架,需要大量的自己集成的部分。首先说以下beeware的rubicon和toga都是怎么回事。

  1. ** rubion:** 是个提供对object c的基础库进行封装的python使用框架,说的直白一点就是把过去要用ctypes的人工要做的对动态库函数的访问,现在通过rubicon,变得省事了,譬如object c的UITableView,如果我们想使用它,那么现在rubicon已经为我们封装好了一个python的class,使用如下方法。你可以在uikit.py文件中找到它。
    UITableView = ObjCClass(‘UITableView’)

通过这种封装,我们就可以直接使用使用UITableView的方法和属性了。这就是rubicon的作用。但是,这里有非常多的坑,很多的function call是并没有集成的,譬如,我们这次图像处理要用到的把图像先转成jpeg格式的object function:
UIImageJPEGRepresentation(UIImage,float)
要想在python里面使用这个功能,必须要自己去按ctypes的规则去声明它的restype和argtypes,以这个功能为例,说明方法如下:

uikit.UIImageJPEGRepresentation.restype = objc_id
uikit.UIImageJPEGRepresentation.argtypes=[objc_id,CGFloat]

这里一定要注意,因为在rubicon中,扩展了ctypes的类型,因此,譬如objc_id,是标准ctypes里面没有的。

在上面正常说明后(在uikit.py)中,此后程序就可以使用上述功能了。

是不是累了,那么先休息一下,我将在下一篇中,讲述如何对rubion和toga扩展,成功访问到相册,如何在python中设置delegate,对相册图片返回进行处理,及如何修改toga的class,使得其可以支持多种图片格式以便于可以和PIL进行图片的数据传输。

当然,我也会把资源开放出来。供大家下载测试。

你可能感兴趣的:(Python,Pillow,python,ios,objective-c,xcode,编程语言)