最近做了一个需求,主要是对接一个公众号的活动部分,要求将前端传过来的相关数据生成一个带二维码的静态图片以便用户保存分享。经过一些搜索,最后决定使用Python的PIL(Pillow)包的Image, ImageFont, ImageDraw模块实现。
设计思路:由于是服务器端代码,希望图片都是在内存中,所以代码中对于存储图片都采用了BytesIO。好在Image.open()方法支持file-like的类型,所以用起来并不复杂。
随便做了一个(可以把你的设计师同事气到脑中风的)示例图,大致展示了这个项目可以提供的功能。
先放项目地址:https://github.com/HectorLI36/sharing_picture_generator
用法请见上面的GitHub里的README,本文详细说一下思路和注意事项,避免朋友们和我踩同样的坑。
下面说一下几个比较重要的模块。
1.生成二维码
这部分是相对来说比较简单的,只需要Python中的qrcode包即可。
import qrcode
qr_pil_obj = qrcode.make(url, border=0)
qr_code_pic = BytesIO()
qr_pil_obj.save(qr_code_pic, format='PNG')
参数 broder指的是二维码周围白圈的宽度,默认为4.
qrcode.make()方法返回的是一个PIL.Image对象。存成BytesIO以后,直接传到函数中就OK了。
2.设置字体对齐
PIL中ImageDraw模块的text()方法是在背景图片上用文本框的左上角定位的,但有时候我们需要文本居中,或文本的最后侧处于特定的位置,这就提出了一个问题。经过一番搜索,我决定自己算。。。
_font = ImageFont.truetype(*font_args, index=text_args['index'])
draw = ImageDraw.Draw(self.bk_im)
if text_args['align'] == 'center':
# w, h = _font.getsize(text_args['text'])
# os = _font.getoffset(text_args['text']) 错误方法
w, h = draw.textsize(text_args['text'], font=_font)
# w0 = len(text_args['text']) * font_args[-1]
new_xy = ((W - w) / 2, text_args['xy'][-1])
self.text_list.append(tuple([new_xy, unicode(text_args['text']), _font]))
elif text_args['align'] == 'right':
# 由右上角计算出左上角
w, h = draw.textsize(text_args['text'], font=_font)
new_xy = ((text_args['xy'][0] - w), text_args['xy'][-1])
self.text_list.append(tuple([new_xy, unicode(text_args['text']), _font]))
else:
self.text_list.append(tuple([text_args['xy'], unicode(text_args['text']), _font]))
代码中w为背景图宽度,h为背景图高度。
关键的方法:
w, h = draw.textsize(text_args['text'], font=_font)
在执行这行代码前,需要初始化_font对象和背景图片的Image对象,特别是输出中文时。否则拿到的预估的字体的长和宽将会有很大的偏差。
这个函数可以自动确认文本框的位置,只需输入对其方式即可。详见代码中README.md文件。
几个坑:
1.要粘贴的图片不是标准的矩形怎么办?
对于ImageDraw,对于paste()这个方法,它在背景图片定位的时候采用的是一个四边的box,如果我们要粘贴的图片不是矩形的话,就会在粘贴的图片周围有白边。此时可以对将要粘贴的图片这样处理:
pic.putalpha(255)
然后再进行粘贴
paste(pic, region_box, musk=pic)
结合最后一个musk=pic,就可以把要粘贴的图片中不是矩形的部分设成“透明的”
2.image file is truncated (XX bytes not processed) 的解决办法
有时候会遇到上面的报错,此时只要在代码的开头加上下面两句就可以了:
from PIL import ImageFile ImageFile.LOAD_TRUNCATED_IMAGES = True
3.ImageDraw.text()的index参数和对于PingFang字体的研究
我们在提到一种字体,比如PingFang(苹方)字体时,我们其实提到的是一个font_famliy。在这个font_family中,其实有各种字体,对于中文,常见的有中国大陆字体、香港繁体、台湾繁体等,它们之间有一点细微的区别。此外,基本所有的PIL添加文本的加粗、斜体、细体的实现都依靠这个index。对于某种特定字体的index,网络上很少有相关的资料,建议大家写程序的时候,先写个for循环遍历一下。
下面是作者用for循环遍历的结果。PingFang这种字体还是比较常用的,这里列出的字体可以供大家参考。
0-PingFang HK-Regular 1-PingFang TC-Regular 2-PingFang SC-Regular 3-PingFang HK-Medium 4-PingFang TC-Medium 5-PingFang SC-Medium 6-PingFang HK-Semibold 7-PingFang TC-Semibold 8-PingFang SC-Semibold 9-PingFang HK-Light 10-PingFang TC-Light 11-PingFang SC-Light 12-PingFang HK-Thin 13-PingFang TC-Thin 14-PingFang SC-Thin 15-PingFang HK-Ultralight 16-PingFang TC-Ultralight 17-PingFang SC-Ultralight