0%

在OpenResty下调用ImageMagick Binding of Lua

作为国内最早的一批PHP程序员,在图形库选用上从最早的GD切换到ImageMagick后再未更换过,其以强大的API,远胜GD的图文能力以及强劲的性能给我留下了深刻的映像。近来因疫情闲来无事,也算是与时俱进,玩起了OpenResty并打算造几个轮子,其中一个轮子需要进行图片处理,作为ImageMagick的忠实粉丝自然要探究一下Lua下的ImageMagick Binding。

我们先从包管理器开始,Lua的主流包管理名为Luarocks,在OpenResty下使用需要先行安装:

1
2
3
4
5
6
7
8
9
10
11
12
#下载当前最新版Luarocks
wget https://github.com/luarocks/luarocks/archive/v3.3.1.tar.gz
tar zxvf v3.3.1.tar.gz
cd luarocks-3.3.1
#配置Luarocks work with Luajit of OpenResty
./configure --prefix=/usr/local/openresty/luajit \
--with-lua=/usr/local/openresty/luajit/ \
--with-lua-interpreter=luajit \
--with-lua-include=/usr/local/openresty/luajit/include/luajit-2.1 \
--with-lua-lib=/usr/local/openresty/luajit/lib
make
make install

因为OpenResty作为一个集成了Nginx+lua-nginx-module+LuaJIT的独立发行版,为了使Luarocks安装的包可以匹配OpenResty并能被直接require,需要将其安装到OpenResty目录内并使用其内置的LuaJIT作为解释器。

接下来就可以看一下Luarocks提供了哪些与ImageMagick相关的包可供使用:

1
2
3
4
5
6
7
8
9
luarocks search imagemagick

imagemagick - Search results for Lua 5.1:
=========================================

luarocks search imagick

imagick - Search results for Lua 5.1:
=====================================

一无所获,出乎意料,但Openresty还提供了一个包管理名为opm,试试看:

1
2
root@test:/usr/local/openresty/bin# ./opm search imagemagick
kwanhur/lua-resty-magick Lua wrapper with magick to process image.

opm返回了一个结果,可以拿来试一下,但在这里需要注意的是,Lua不同于PHP/Python等语言,内置了丰富的三方模块并由成熟的社区/商业机构维护,普通用户开封即用。Lua的三方模块多是由开源爱好者基于自己的需求开发并贡献给他人使用,普遍缺乏全面的测试和长期维护,在选用时务必进行甄别。

opm索引的lua-resty-magick托管于Github,2018年1月22日创建了仓库,当天内提交了五次后便沉寂至今,没有任何Issue,版本停留在0.0.1,也仅仅只实现了五个方法的封装,可以视为一个荒废的项目。虽然基于LuaJIT的ffi接口可以快速地封装ImageMagick的C库函数,但作为非业务模块如果有现成的轮子又何必再去造呢,于是我又在Github上进行了一轮搜索(基于Recently updated排序),找到了下面两个有一定星数,最后一次更新不晚于2年的项目:

第一个项目是由搞出了MoonScript的大神leafo创建并维护,今年4月还修了一次内存泄露的bug。第二个项目由一位俄罗斯开发者维护,今年没有任何更新,但对Issue的处理很勤勉。两个项目都实现了对ImageMagick基本功能的OOP封装,但在实现方式上迥异,支持的功能也略有差别,如下表所示:

leafo/magick isage/lua-imagick
封装方式 FFI C Module
封装库 ImageMagick&GraphicsMagick ImageMagick
LuaJIT
Luarocks ×
装载 文件&BLOB 文件&BLOB
裁剪
旋转
模糊
锐化
格式转换
比例缩放
缩略图
画板 ×
调色 ×
文字嵌入 ×

总体综合来看,isage/lua-imagick对ImageMagick的封装较leafo/magick更为完整,实现方式上来讲也比FFI性能更高,leafo/magick胜在可以用Luarocks安装,我们此前未能找到该模块是因为Luarocks命令行不支持关键词查找,但通过网站搜素可以匹配到。

如果对文字嵌入没有需求,leafo/magick足以满足一般的需求,我们先安装它:

1
2
3
4
5
#安装MagicWand
apt-get install libmagickwand-dev
#安装GraphicsMagick's Wand
apt-get install libgraphicsmagick1-dev
luarocks install magick

接着安装isage/lua-imagick:

1
2
3
4
5
6
7
#如果没有camke需要先安装
apt-get install cmake
wget https://github.com/isage/lua-imagick/archive/master.zip
unzip master.zip
cd lua-imagick-master/
cmake ..
make && make install

如果cmake不能找到你的OpenResty安装目录,可以修改cmake/FindLuaJIT.cmake文件或:

1
2
cmake -DLUA_LIBRARY=/path of your openresty/luajit/lib/ \
-DLUA_INCLUDE_DIR=/path of your openresty/luajit/include/luajit-2.1 ..

接下来来用load from png->resize->save to jpg来做一个基准测试,代码分别如下:

  1. magick1.lua
1
2
3
4
5
6
7
8
9
10
11
local magick = require "magick.wand" --eafo/magick

for i = 1, 100 do
local img = assert(magick.load_image("csgotest.png"))
img:resize(1280, 720)
img:set_format("jpg")
img:set_quality(90)
img:write("/dev/null")
img:destroy()
end
print(gcinfo())
  1. magick2.lua
1
2
3
4
5
6
7
8
9
10
11
local magick = require "imagick" --isage/lua-imagick

for i = 1, 100 do
local img = assert(magick.open("csgotest.png"))
img:resize(1280, 720)
img:set_format("jpg")
img:set_quality(90)
img:write("/dev/null")
img:destroy()
end
print(gcinfo())

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
time /usr/local/openresty/bin/resty magick1.lua
358

real 0m14.479s
user 0m26.219s
sys 0m0.922s

time /usr/local/openresty/bin/resty magick2.lua
224

real 0m15.325s
user 0m32.203s
sys 0m1.438s

结果让人大跌眼镜,让我对LuaJIT的FFI刮目相看,isage/lua-imagick采用的C Module封装除了在内存占用方面少消耗130kb,速度方面比采用FFI封装的eafo/magick慢了整整25%,也许是基准测试中基本都是call函数,所有的计算和文件操作都是在C中完成,没有涉及到太多FFI占劣势的部分,比如Lua和C的数据交换。

总之, 通过测试更加印证了上面说过的话,如果对文字嵌入没有需求,leafo/magick足以满足一般的需求,而且可以跑得更快!