PythonMagick库使用心得
PythonMagick是imagemagick的python版本
实际上他是magick++的封装。函数,参数和magick++是一样的
参考文档:https://imagemagick.org/Magick++/tutorial/Magick++_tutorial.pdf
下面是一些摸索下来的心得,提供给需要的人,少走弯路
1 颜色空间转换
接到一个任务,批量生成荣誉证书,要求根据设计稿,生成图片:格式:JPG,PPI:300,颜色模式:CMYK,提供的素材也是这样的格式
首先想到的是用PIL库来操作,毕竟在python上面基本是标准库了
因为证书上还需要添加证件照,证件照只有RGB模式的,所以需要将图片从RGB转到CMYK
PIL下面就是一个convert就搞定了
lena =Image.open("lena.jpg") lena_CMYK =lena.convert("CMYK")
但是就是颜色不对,转过来的图片变色了(如下图,右边是美工提供的设计稿,左边是通过PIL转换的cmyk),这样的色差是难以接受的
查了下原因,PIL的转换,操作比较简单,k值直接是0,这样颜色必然是不准的
模式“RGB”转换为模式“CMYK”以后,图像从三通道变成了四通道,其C、M、Y三个通道的数值是通过之前的公式计算得到,K通道被直接赋值为0。 C = 255 - R M = 255 - G Y = 255 - B K = 0
通过查询,得知需要导入icc文件才能得到ps的转换效果,查询了PIL,没有找到导入的选项(这里可能是本人水平有限,没有找到,如果有的话,希望能回复下,研究下看看)
查询下来,找到了一个工具imagemagick,是一个图片处理的工具包,这个工具包可以将图片转换颜色,按照提供的icc进行转换
命令行操作很简单
convert -profile "sRGB.icm" RGB.jpg -profile "CMYK.icc" CMYK.jpg
这样就完成了颜色的转换,和PS下转换的效果是一样的
然后发现这个工具包有python下面的库,http://www.lfd.uci.edu/~gohlke/pythonlibs/#pythonmagick
2.7版本的不知道为啥,总是装不好,换了3.7的就ok了
安装好以后查看支持的函数,dir(PythonMagick),可以看到如下函数,具体的函数帮助可以输入help()来查看
['Blob', 'Color', 'ColorspaceType', 'CompositeOperator', 'CompressionType', 'Coordinate', 'DecorationType', 'DrawableAffine', 'DrawableAlpha', 'DrawableArc', 'DrawableBezier', 'DrawableCircle', 'DrawableClipPath', 'DrawableColor', 'DrawableCompositeImage', 'DrawableEllipse', 'DrawableFillColor', 'DrawableFillOpacity', 'DrawableFillRule', 'DrawableFont', 'DrawableGravity', 'DrawableLine', 'DrawableMiterLimit', 'DrawablePath', 'DrawablePoint', 'DrawablePointSize', 'DrawablePolygon', 'DrawablePolyline', 'DrawablePopClipPath', 'DrawablePopGraphicContext', 'DrawablePopPattern', 'DrawablePushClipPath', 'DrawablePushGraphicContext', 'DrawablePushPattern', 'DrawableRectangle', 'DrawableRotation', 'DrawableRoundRectangle', 'DrawableScaling', 'DrawableSkewX', 'DrawableSkewY', 'DrawableStrokeAntialias', 'DrawableStrokeColor', 'DrawableStrokeDashArray', 'DrawableStrokeDashOffset', 'DrawableStrokeLineCap', 'DrawableStrokeLineJoin', 'DrawableStrokeOpacity', 'DrawableStrokeWidth', 'DrawableText', 'DrawableTextAntialias', 'DrawableTextDecoration', 'DrawableTextUnderColor', 'DrawableTranslation', 'DrawableViewbox', 'Exception', 'FilterType', 'Geometry', 'GravityType', 'Image', 'PathArcAbs', 'PathArcArgs', 'PathArcRel', 'PathClosePath', 'PathCurvetoAbs', 'PathCurvetoArgs', 'PathCurvetoRel', 'PathLinetoAbs', 'PathLinetoHorizontalAbs', 'PathLinetoHorizontalRel', 'PathLinetoRel', 'PathLinetoVerticalAbs', 'PathLinetoVerticalRel', 'PathMovetoAbs', 'PathMovetoRel', 'PathQuadraticCurvetoAbs', 'PathQuadraticCurvetoArgs', 'PathQuadraticCurvetoRel', 'PathSmoothCurvetoAbs', 'PathSmoothCurvetoRel', 'PathSmoothQuadraticCurvetoAbs', 'PathSmoothQuadraticCurvetoRel', 'Pixels', 'TypeMetric', 'VPath', '_PythonMagick', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__']
这个是PythonMagick.Image下的函数
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__instance_size__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'adaptiveThreshold', 'addNoise', 'adjoin', 'affineTransform', 'alpha', 'animationDelay', 'animationIterations', 'annotate', 'attribute', 'backgroundColor', 'backgroundTexture', 'baseColumns', 'baseFilename', 'baseRows', 'blur', 'border', 'borderColor', 'boundingBox', 'boxColor', 'channel', 'channelDepth', 'charcoal', 'chop', 'chromaBluePrimary', 'chromaGreenPrimary', 'chromaRedPrimary', 'chromaWhitePoint', 'classType', 'colorFuzz', 'colorMap', 'colorMapSize', 'colorSpace', 'colorize', 'columns', 'comment', 'compare', 'compose', 'composite', 'compressType', 'contrast', 'convolve', 'crop', 'cycleColormap', 'debug', 'defineSet', 'defineValue', 'density', 'depth', 'despeckle', 'directory', 'display', 'draw', 'edge', 'emboss', 'endian', 'enhance', 'equalize', 'erase', 'fileName', 'fileSize', 'fillColor', 'fillPattern', 'fillRule', 'filterType', 'flip', 'floodFillAlpha', 'floodFillColor', 'floodFillTexture', 'flop', 'font', 'fontPointsize', 'fontTypeMetrics', 'format', 'frame', 'gamma', 'gaussianBlur', 'geometry', 'gifDisposeMethod', 'iccColorProfile', 'implode', 'interlaceType', 'iptcProfile', 'isValid', 'label', 'magick', 'magnify', 'map', 'matteColor', 'meanErrorPerPixel', 'medianFilter', 'minify', 'modifyImage', 'modulate', 'modulusDepth', 'monochrome', 'montageGeometry', 'negate', 'normalize', 'normalizedMaxError', 'normalizedMeanError', 'oilPaint', 'opaque', 'page', 'ping', 'pixelColor', 'process', 'profile', 'quality', 'quantize', 'quantizeColorSpace', 'quantizeColors', 'quantizeDither', 'quantizeTreeDepth', 'raise', 'read', 'readPixels', 'reduceNoise', 'renderingIntent', 'resize', 'resolutionUnits', 'roll', 'rotate', 'rows', 'sample', 'scale', 'scene', 'segment', 'shade', 'sharpen', 'shave', 'shear', 'signature', 'size', 'solarize', 'spread', 'statistics', 'stegano', 'stereo', 'strip', 'strokeAntiAlias', 'strokeColor', 'strokeDashOffset', 'strokeLineCap', 'strokeLineJoin', 'strokeMiterLimit', 'strokePattern', 'strokeWidth', 'subImage', 'subRange', 'swirl', 'syncPixels', 'textEncoding', 'texture', 'threshold', 'totalColors', 'transformOrigin', 'transformReset', 'transformRotation', 'transformScale', 'transformSkewX', 'transformSkewY', 'transparent', 'trim', 'type', 'unsharpmask', 'verbose', 'wave', 'write', 'writePixels', 'x11Display', 'xResolution', 'yResolution', 'zoom']
=============================================================================================
上面都是介绍,下面才是正文
=============================================================================================
经过测试,加载icc文件使用的是'iccColorProfile'这个函数,如下是内置的帮助,可以看到,有2中用法,
1,传入blob类型参数,就可以设置icc文件
2,不传入参数,可以返回一个blob类型,意思是获取这个图片当前的icc文件
blob是内存中的二进制文件类型
Help on built-in function iccColorProfile: iccColorProfile(...) iccColorProfile( (Image)arg1, (Blob)arg2) -> None : C++ signature : void iccColorProfile(class Magick::Image {lvalue},class Magick::Blob) iccColorProfile( (Image)arg1) -> Blob : C++ signature : class Magick::Blob iccColorProfile(class Magick::Image {lvalue})
官方的文档提供了blob类型的设置
Blob::Blob(); Blob::Blob(void* data, unsigned int size); // explicitly specifies a memory area to be // associated with the new Blob object Blob::operator=(const Blob& blob); // Examples of using Blobs in conjuction with Images Blob my_blob; // create a blob Image my_image("my_image.gif"); // create an image form a GIF image file my_image.magick("JPEG"); // set JPEG output format my_image.write(&my_blob); // encode 'my_image' in JPEG format, // and store the encoded image in my_blob Image image_from_blob(my_blob); // create an image from the JPEG blob // (use the Blob-based Image constructor) image_from_blob.magick("BMP"); // set the image format to bitmap image_from_blob.write("image_from_blob.bmp"); // save the image on disk in BMP format
使用image打开一个文件,然后写入到一个新的Blob类型即可,这边写了一个函数,调用即可得到一个Blob类型的实例,内容是相应的icc文件
#获取对应颜色空间的blob(例如cmyk,rgb) def get_color_blob(file_name): color = PythonMagick.Blob() im = PythonMagick.Image(file_name) im.write(color,"icc") return color
cmyk = get_color_blob("CMYK.icc") temp = PythonMagick.Image("rgb.jpg") temp.iccColorProfile(cmyk) temp.write("cmyk.jpg")
这样就能完成从rgb到cmyk的转换了,经测试,转换效果完美,和ps转换出来是一摸一样的
2 PPI设置
使用pythonmagick的话无需设置PPI,原始是300PPI,保存下来就是300PPI,但是使用PIL打开300PPI的图片,进行操作,然后保存,就会变成蜜汁72PPI(这个在印刷制品上是
不行的,印刷出来会模糊),pythonmagick完美的避开了这个坑
3 图片缩放
img = PythonMagick.Image("test.jpg") #不保持比例 img.sample('298x412!') #保持比例 img.sample('298x412')
4 图片覆盖
有时候需要把一个透明的PNG覆盖到底图的某个位置,比如说一个印章
back_img = PythonMagick.Image("back.jpg") logo_img = PythonMagick.Image("logo.png") #此操作会出现logo的底图是黑色的 back_img.composite(logo_img,x,y) #使用over模式,具体可以查看magick++的文档,对应查找pythonmagick下面的函数,其他模式类似 back_img.composite(logo_img,x,y,PythonMagick.CompositeOperator.OverCompositeOp)
5 文字书写
系统提供DrawableFont,Image.annotate来在图片上书写字体
这里介绍使用ttf字体进行书写,并且使用奇葩的方法来实现粗体,字间距
Image.annotate查看系统的说明如下
Help on built-in function annotate: annotate(...) annotate( (Image)arg1, (str)arg2, (Geometry)arg3) -> None : C++ signature : void annotate(class Magick::Image {lvalue},class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class Magick::Geometry) annotate( (Image)arg1, (str)arg2, (Geometry)arg3, (GravityType)arg4) -> None : C++ signature : void annotate(class Magick::Image {lvalue},class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class Magick::Geometry,enum MagickCore::GravityType) annotate( (Image)arg1, (str)arg2, (Geometry)arg3, (GravityType)arg4, (float)arg5) -> None : C++ signature : void annotate(class Magick::Image {lvalue},class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class Magick::Geometry,enum MagickCore::GravityType,double) annotate( (Image)arg1, (str)arg2, (GravityType)arg3) -> None : C++ signature : void annotate(class Magick::Image {lvalue},class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,enum MagickCore::GravityType)
测试代码如下,注意:如果图片是cmyk模式,请先转换成rgb再书写文字,最后再保存cmyk,否则会变色
#打开底图 img = PythonMagick.Image("test.jpg") #设置ttf字体 img.font("W3.TTF") #设置字体大小 img.fontPointsize(60) #设置颜色 img.fillColor(PythonMagick.Color("#ff00ff")) #书写字体(100,100是xy坐标) img.annotate("你好",PythonMagick.Geometry("+100+100")) #保存 img.write("000.jpg")
测试写字(下图是使用了奇葩方法实现的粗体,因为找不到粗体的选项)
之前使用PIL写字可以加粗,但是他写出来的字体比ps里面粗,而且无法调整粗的程度
这里就自己写了个加粗方法,原理很简单,写5次文字,
首先x,y坐标写一次
然后x-1,y写一次,x+1,y写一次,x,y-1写一次,x,y+1写一次
就是上下左右各偏一个像素写一遍,这样写出来的文字和ps里面的粗体可以说能达到99%的相似度
可以看出,书写出来的文字还是有一些差异的,不过这样的效果完全是OK的
#书写粗体字(默认间隔) def img_text_bold(img,font_name,size,text,postion): img.font(font_name) img.fontPointsize(size) #img.annotate(text,PythonMagick.Geometry("+%s+%s"%(postion[0],postion[1]))) img.annotate(text,PythonMagick.Geometry("+%s+%s"%(postion[0]+1,postion[1]))) img.annotate(text,PythonMagick.Geometry("+%s+%s"%(postion[0]-1,postion[1]))) img.annotate(text,PythonMagick.Geometry("+%s+%s"%(postion[0],postion[1]+1))) img.annotate(text,PythonMagick.Geometry("+%s+%s"%(postion[0],postion[1]-1)))
#书写粗体字(自定义间隔) def img_text_bold_space(img,font_name,size,space,text,postion): zz = 0 for i in text: postion_new = [postion[0]+int(round(zz*space)),postion[1]] img_text_bold(img,font_name,size,i,postion_new) zz = zz + 1
因为找不到行间距这个参数,所以只能写一个奇葩的方法来实现字间距,实现原理就是一个个字写,写完一个字偏移一定的像素,如此来达到设置字间距的效果
如下效果
通过这一系列奇葩实现,可以达到设计稿的95%相似度,这样就完成了任务
6 图片对象引用
back_img = PythonMagick.Image("back.png") temp1 = back_img #对temp1进行各种操作,比如写字,剪裁,反转等 temp1.xxx() temp1.xxx() temp1.write("001.png") temp2 = back_img #对temp2进行各种操作,比如写字,剪裁,反转等 temp2.xxx() temp2.xxx() temp2.write("002.png")
上面的引用是不行的,temp2保存下来会带有temp1的操作效果,他这里 = 操作只是引用,不是复制
正确的做法是先新建一个Blob对象,把底图放在里面,然后temp去读取这个Blob对象来获得一个图片对象
#获取一个图片的Blob对象 def get_img_blob(file_name): last = file_name.split(".")[-1] img = PythonMagick.Image(file_name) blob = PythonMagick.Blob() img.write(blob,last) return blob
back_img_blob = get_img_blob("back.png") temp1 = PythonMagick.Image(back_img_blob) #对temp1进行各种操作,比如写字,剪裁,反转等 temp1.xxx() temp1.xxx() temp1.write("001.png") temp2 = PythonMagick.Image(back_img_blob) #对temp2进行各种操作,比如写字,剪裁,反转等 temp2.xxx() temp2.xxx() temp2.write("002.png")