IM 用C写了一系列的command,然后由这些command 配以各种参数,搭配出种类繁多的图像处理特效。
这样做的好处是:1)层次分明;2)上层逻辑实现简单快速。
坏处是:1)用shell脚本写的特效,不方便由C代码调用;2)Shell 脚本执行效率没有C原生的快
如果对某种特效,需要频繁调用,这时就要考虑将shell 脚本由C/C++ 重写了。以下将记录这方面的学习笔记。
曾经听说过Lua 脚本和C 代码关系比较密切,考虑将shell 脚本转成Lua脚本,然后将Lua脚本“自动转成C 代码”,后来发现,这是不可能的,太天真了!并且,如果由C 调用Lua 脚本,并且频繁跨越C/Lua 边界的话,效率也很低下。于是放弃Lua 思路,只有一条路:手工将Shell 脚本转成C 代码。于是要搞清楚以下问题:
观察IM 的代码和安装后的文件,有以下发现:
1)貌似convert 命令是由wand/convert.h, wand/convert.c 生成的
2)安装了IM 之后,在 /usr/local/lib 中会有以下和IM 相关的库文件:
-rw-r--r--. 1 root root 4561412 Apr 21 20:00 libMagick++-6.Q16.a
-rwxr-xr-x. 1 root root 1123 Apr 21 20:00 libMagick++-6.Q16.la
lrwxrwxrwx. 1 root root 26 Apr 21 20:00 libMagick++-6.Q16.so -> libMagick++-6.Q16.so.1.0.0
lrwxrwxrwx. 1 root root 26 Apr 21 20:00 libMagick++-6.Q16.so.1 -> libMagick++-6.Q16.so.1.0.0
-rwxr-xr-x. 1 root root 2134069 Apr 21 20:00 libMagick++-6.Q16.so.1.0.0
-rw-r--r--. 1 root root 11540026 Apr 21 20:00 libMagickCore-6.Q16.a
-rwxr-xr-x. 1 root root 1075 Apr 21 20:00 libMagickCore-6.Q16.la
lrwxrwxrwx. 1 root root 28 Apr 21 20:00 libMagickCore-6.Q16.so -> libMagickCore-6.Q16.so.1.0.0
lrwxrwxrwx. 1 root root 28 Apr 21 20:00 libMagickCore-6.Q16.so.1 -> libMagickCore-6.Q16.so.1.0.0
-rwxr-xr-x. 1 root root 6309290 Apr 21 20:00 libMagickCore-6.Q16.so.1.0.0
-rw-r--r--. 1 root root 4043444 Apr 21 20:00 libMagickWand-6.Q16.a
-rwxr-xr-x. 1 root root 1113 Apr 21 20:00 libMagickWand-6.Q16.la
lrwxrwxrwx. 1 root root 28 Apr 21 20:00 libMagickWand-6.Q16.so -> libMagickWand-6.Q16.so.1.0.0
lrwxrwxrwx. 1 root root 28 Apr 21 20:00 libMagickWand-6.Q16.so.1 -> libMagickWand-6.Q16.so.1.0.0
-rwxr-xr-x. 1 root root 2254380 Apr 21 20:00 libMagickWand-6.Q16.so.1.0.0
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
extern WandExport MagickBooleanType
ConvertImageCommand(ImageInfo *,int,char **,char **,ExceptionInfo *);
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
而这个ConvertImageCommand() 函数正是在 IM 解压根目录/utilities/convert.c 中真正干活的函数,也就是说,通过#include "/usr/local/include/ImageMagick-6/wand/convert.h" 和其他相关的头文件,然后link /usr/local/lib 下的某些库文件,我们就能够方便地模拟调用IM 生成的命令了。在此基础上,加上合适的上层逻辑,传入合适的参数,就能模拟IM 提供的Shell 脚本的功能了。
参照这里给出的简单例子和 /home/yasi/ImageMagick-6.8.4-10/utilities/convert.c,下面是一个调用ConvertImageCommand()的简单实现:test.cpp。
#include <wand/MagickWand.h>
int main(int argc, char* argv[])
{
ExceptionInfo *exception;
ImageInfo *image_info;
MagickBooleanType status;
MagickCoreGenesis(*argv,MagickTrue);
exception=AcquireExceptionInfo();
image_info=AcquireImageInfo();
status=MagickCommandGenesis(image_info,ConvertImageCommand,argc,argv,(char **) NULL,exception);
image_info=DestroyImageInfo(image_info);
exception=DestroyExceptionInfo(exception);
MagickCoreTerminus();
return(status);
}
编译方式:
g++ -o test `pkg-config --cflags --libs MagickWand` test.cpp
其实,pkg-config --cflags --libs MagickWand 的执行结果是这样的:
-fopenmp -DMAGICKCORE_HDRI_ENABLE=0 -DMAGICKCORE_QUANTUM_DEPTH=16 -I/usr/local/include/ImageMagick-6 -L/usr/local/lib -lMagickWand-6.Q16 -lMagickCore-6.Q16
./test -negate ./lena_noise.jpg ./lena_converted.jpg
但是,这样是不够的,IM 的脚本中常常会多次用到某个函数,如ConvertImageCommand,或者先后用到多个函数;对于这种情况,就要搞清楚下面一些问题:
这里有IM 的主要函数的详细说明,理论上可以通过这些函数实现convert 等工具的所有功能,但工作量巨大。能否找到这样一种简单的做法:
尽量多地利用IM 提供的封装好的代码,fully handle 所有命令参数,同时又不失灵活性,可以通过内存对象传递临时图像对象,从shell 脚本转译成C++代码工作量降至最低。
之所以希望这样,是因为Fred's ImageMagick Scripts 已经对IM 的命令做了大量的调优,做成了大量的效果很不错的图像处理脚本,我们不想对这些工作再从头做起,而是简单地临摹这些脚本,翻译成C++代码。
如果不考虑临摹所有的脚本,只针对个别的特性,如文字图像的二值化,可以从TEXTCLEANER 脚本和API 说明中抠出最核心的代码,从底层做起。这样的代码更紧凑,效率更高。
其实,TEXTCLEANER 脚本 效果去除背景图像、将图像调整角度(水平放置)效果挺好的,并且在其页面最后给出了脚本对应的 convert 命令格式:
convert \( $infile -colorspace gray -type grayscale -contrast-stretch 0 \) \
\( -clone 0 -colorspace gray -negate -lat ${filtersize}x${filtersize}+${offset}% -contrast-stretch 0 \) \
-compose copy_opacity -composite -fill "$bgcolor" -opaque none +matte \
-deskew 40% -sharpen 0x1 \ $outfile
对于这个TEXTCLEANER 脚本,需要增强的效果是:既然已经去除背景了,只关注文字线条轮廓本身,那么可以进一步二值化,缩小图像文件尺寸。
============== 杂项,待整理 =================
ConvertImageCommand() 是在 wand/convert.c 中实现的
MagicCommandGenesis() 是在 wand/mogrify.c 中实现的
struct _ImageInfo 是在 magick/image.h 中定义的
AcquireImageInfo() 是在 magick/image.c 中实现的
magick/magick-type.h:typedef struct _Image Image; 而_Image 是magick/image.h 中定义的
FireImageStack、PushImageStack 和 PopImageStack是在wand/mogrify-private.h 中定义的宏
这里有个问题,MagickCommandGenesis(image_info,ConvertImageCommand,argc,argv,(char **) NULL,exception) 中传入的argc, argv是 int, char**类型的,所以对于字符串类型的参数,如 "./test -negate ./lena_noise.jpg ./lena_converted.jpg",要先转成 int, char** 类型的。方法如下:
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
void split_begin(const string &sCmd, vector<char*> &args) {
istringstream iss(sCmd);
string token;
args.clear();
while(iss >> token) {
char *arg = new char[token.size() + 1];
copy(token.begin(), token.end(), arg);
arg[token.size()] = '\0';
args.push_back(arg);
}
}
void split_end(vector<char*> &args) {
for(int i = 0; i < args.size(); ++i) {
delete[] args[i];
}
args.clear();
}
int main(int argc, char* argv[]) {
string sCmd = "/opt/usr/bin/app -p 12300 -d 200";
vector<char*> args;
split_begin(sCmd, args);
for(int i = 0; i < args.size(); ++i) {
cout << args[i] << "~" << endl;
}
split_end(args);
return 0;
}
IM 的各种类型定义是在 /usr/local/include/ImageMagick-6/magick/magick-type.h 中,如MagickRealType、Quantum、SignedQuantum、MagickSizeType、MagickBooleanType 等。
对于编译出现的下面的提示
In file included from /usr/local/include/ImageMagick-6/magick/magick-type.h:25, from main.cpp:2:
/usr/local/include/ImageMagick-6/magick/magick-config.h:29:3: warning: #warning "you should set MAGICKCORE_QUANTUM_DEPTH to sensible default set it to configure time default"
/usr/local/include/ImageMagick-6/magick/magick-config.h:30:3: warning: #warning "this is an obsolete behavior please fix your makefile"
/usr/local/include/ImageMagick-6/magick/magick-config.h:52:3: warning: #warning "you should set MAGICKCORE_HDRI_ENABLE to sensible default set it to configure time default"
/usr/local/include/ImageMagick-6/magick/magick-config.h:53:3: warning: #warning "this is an obsolete behavior please fix yours makefile"
在编译选项中加入“
-DMAGICKCORE_QUANTUM_DEPTH=16 -DMAGICKCORE_HDRI_ENABLE=1”(当然也可以设置除了16和1以外的其他值),如:
$(OBJ_DIR)/%.o : $(SRC_DIR)/%.cpp
$(CXX) $(INC_OPT) -c -DMAGICKCORE_QUANTUM_DEPTH=16 -DMAGICKCORE_HDRI_ENABLE=1 -o $@ $<
这里有简单的使用IM 代码的例子