利用Python如何生成便签图片详解


Posted in Python onJuly 09, 2018

前言

最近有文字转图片的需求,但是不太想下载 APP,就使用 Python Pillow 实现了一个,效果如下:

利用Python如何生成便签图片详解

PIL 提供了 PIL.ImageDraw.ImageDraw.text 方法,可以方便的把文字写到图片上,简单示例如下:

from PIL import Image, ImageDraw, ImageFont
# get an image
base = Image.open('Pillow/Tests/images/hopper.png').convert('RGBA')

# make a blank image for the text, initialized to transparent text color
txt = Image.new('RGBA', base.size, (255,255,255,0))

# get a font
fnt = ImageFont.truetype('Pillow/Tests/fonts/FreeMono.ttf', 40)
# get a drawing context
d = ImageDraw.Draw(txt)

# draw text, half opacity
d.text((10,10), "Hello", font=fnt, fill=(255,255,255,128))
# draw text, full opacity
d.text((10,60), "World", font=fnt, fill=(255,255,255,255))

out = Image.alpha_composite(base, txt)

out.show()

为什么要计算文字的宽高呢?把文字直接写到背景图不可以么?

Pillow PIL.ImageDraw.ImageDraw.text 写文字是按换行符 \n 换行的,如果个字符串特别长,文字部分就会超出背景图的宽度,所以第一步我们需要先把文本按固定的宽度计算出高度。

像图上写的这样,文字转图片分三步:

  • 计算文字宽高
  • 生成响应尺寸背景图
  • 把文字写到图片上

计算文字宽高

这里背景图宽度是固定的,所以文字的宽可以不用计算。 PIL.ImageDraw.ImageDraw.text 是通过 \n 来换行的,那我们只需要在文字合适的位置加上 \n 就可以了。

第一个想到的是 textwrap 方法,textwrap 可以实现通过调整换行符的位置来格式化文本。但 textwrap 还有一个问题就是它是根据字符长度来分隔的,但文本中的字符并不是等宽的,通过 textwrap 格式化后的文字写到图片上效果可能是这样的:

利用Python如何生成便签图片详解

使用这种方式,如果我们要调整字体大小,每一行的长度都还需要再重新调整。

为了保证每一行宽度尽可能的一致,这里使用 PIL.ImageDraw.ImageDraw.textsize 获取字符宽高,然后按约定宽度把长文本分隔成文本列表,然后把列表每行文字写到图片上。

def get_paragraph(text, note_width):
 # 把每段文字按约定宽度分隔成几行
 txt = Image.new('RGBA', (100, 100), (255, 255, 255, 0))
 # get a drawing context
 draw = ImageDraw.Draw(txt)
 paragraph, sum_width = '', 0
 line_numbers, line_height = 1, 0
 for char in text:
 w, h = draw.textsize(char, font)
 sum_width += w
 if sum_width > note_width:
  line_numbers += 1
  sum_width = 0
  paragraph += '\n'
 paragraph += char
 line_height = max(h, line_height)
 if not paragraph.endswith('\n'):
 paragraph += '\n'
 return paragraph, line_height, line_numbers


def split_text(text):
 # 将文本按规定宽度分组
 max_line_height, total_lines = 0, 0
 paragraphs = []
 for t in text.split('\n'):
 # 先按 \n 把文本分段
 paragraph, line_height, line_numbers = get_paragraph(t)
 max_line_height = max(line_height, max_line_height)
 total_lines += line_numbers
 paragraphs.append((paragraph, line_numbers))
 line_height = max_line_height
 total_height = total_lines * line_height
 # 这里返回分好的段,文本总高度以及行高
 return paragraphs, total_height, line_height

这是按字符宽度分隔文本写到图片的效果:

利用Python如何生成便签图片详解

由于文本长度不固定,生成得到的文本高度也不固定,背景图我们也需要动态生成

根据文本高度生成背景图

利用Python如何生成便签图片详解

通过图片我们可以看到,头部和尾部是固定的,变化的是文字部分,那么背景图片的高度计算公式为

背景图片高度=头部高度+尾部高度+文本高度

实现代码如下:

NOTE_HEADER_IMG = path.normpath(path.join(
 path.dirname(__file__), 'note_header_660.png'))
NOTE_BODY_IMG = path.normpath(path.join(
 path.dirname(__file__), 'note_body_660.png'))
NOTE_FOOTER_IMG = path.normpath(path.join(
 path.dirname(__file__), 'note_footer_660.png'))
NOTE_WIDTH = 660
NOTE_TEXT_WIDTH = 460
body_height = NOTE_BODY_HEIGHT = 206
header_height = NOTE_HEADER_HEIGHT = 89
footer_height = NOTE_FOOTER_HEIGHT = 145
font = ImageFont.truetype(NOTE_OTF, 24)


def get_images(note_height):
 numbers = note_height // body_height + 1
 images = [(NOTE_HEADER_IMG, header_height)]
 images.extend([(NOTE_BODY_IMG, body_height)] * numbers)
 images.append((NOTE_FOOTER_IMG, footer_height))
 return images


def make_backgroud():
 # 将图片拼接到一起
 images = get_images()
 total_height = sum([height for _, height in images])
 # 最终拼接完成后的图片
 backgroud = Image.new('RGB', (body_width, total_height))
 left, right = 0, 0
 background_img = '/tmp/%s_backgroud.png' % total_height
 # 判断背景图是否存在
 if path.exists(background_img):
 return background_img
 for image_file, height in images:
 image = Image.open(image_file)
 # (0, left, self.body_width, right+height)
 # 分别为 左上角坐标 0, left
 # 右下角坐标 self.body_width, right+height
 backgroud.paste(image, (0, left, body_width, right+height))
 left += height # 从上往下拼接,左上角的纵坐标递增
 right += height # 左下角的纵坐标也递增
 backgroud.save(background_img, quality=85)
 return background_img

将文字写到图片

现在我们得到了背景图以及分隔好的文本,就可以直接将文本写到图片上了

def draw_text(paragraphs, height):
 background_img = make_backgroud()
 note_img = Image.open(background_img).convert("RGBA")
 draw = ImageDraw.Draw(note_img)
 # 文字开始位置坐标,需要根据背景图的大小做调整
 x, y = 80, 100
 for paragraph, line_numbers in paragraphs:
 for line in paragraph.split('\n')[:-1]:
  draw.text((x, y), line, fill=(110, 99, 87), font=font)
  y += line_height
 # draw.text((x, y), paragraph, fill=(110, 99, 87), font=font)
 # y += self.line_height * line_numbers
 note_img.save(filename, "png", quality=1, optimize=True)
 return filename

完整版代码请查看 [ https://github.com/gusibi/momo/blob/master/momo/note.py ]

执行后效果如图:

利用Python如何生成便签图片详解

遇到的问题

为了能方便使用,我把这个做成了公号的一个功能,然后遇到了一个严重问题, 太慢了!

使用 line_profiler 分析可以发现,大部分时间都消耗在了图片保存这一步,

note_img.save(filename, "png", quality=1, optimize=True)

性能分析工具也会占用时间,测试完成后需要关闭分析

解决这个问题可能的方法:

  • 减小背景图片大小
  • 减小字体大小

通过测试,发现把背景图宽度从990减到660,字体大小从40px 调整到24px,生成的图片大小体积缩小了接近1倍,生成速度也比原来快了2/5。

相同代码,相同文本,使用 python3 只用了2.3s,而 Python2 用时却是5.3 s,还从来没在其它功能上遇到过 Python2 和 Python3 有这么大的差别。

具体差异可以使用源码测试一下

还是有问题

优化完图片生成速度后,发现在长文本状态下,公号还是会超时报错。经过检查发现是图片上传到公众平台太慢了(服务器只有1M 带宽,没有办法.)。

解决方法,把图片上传到腾讯云(文件上传使用的是内网带宽,不受限制),返回图片 url。

利用Python如何生成便签图片详解

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
python定时检查启动某个exe程序适合检测exe是否挂了
Jan 21 Python
Python批量修改文本文件内容的方法
Apr 29 Python
Django进阶之CSRF的解决
Aug 01 Python
python utc datetime转换为时间戳的方法
Jan 15 Python
Python BeautifulSoup [解决方法] TypeError: list indices must be integers or slices, not str
Aug 07 Python
Django之使用celery和NGINX生成静态页面实现性能优化
Oct 08 Python
基于Python新建用户并产生随机密码过程解析
Oct 08 Python
使用python 对验证码图片进行降噪处理
Dec 18 Python
基于python实现地址和经纬度转换
May 19 Python
python怎么判断模块安装完成
Jun 19 Python
利用python汇总统计多张Excel
Sep 22 Python
单身狗福利?Python爬取某婚恋网征婚数据
Jun 03 Python
Caffe均值文件mean.binaryproto转mean.npy的方法
Jul 09 #Python
使用Python更换外网IP的方法
Jul 09 #Python
使用Python AIML搭建聊天机器人的方法示例
Jul 09 #Python
Python迭代器与生成器用法实例分析
Jul 09 #Python
numpy的文件存储.npy .npz 文件详解
Jul 09 #Python
Python实现聊天机器人的示例代码
Jul 09 #Python
Atom的python插件和常用插件说明
Jul 08 #Python
You might like
PHP合并静态文件详解
2014/11/14 PHP
php基于GD库画五星红旗的方法
2015/02/24 PHP
Yii2创建表单(ActiveForm)方法详解
2016/07/23 PHP
把JS与CSS写在同一个文件里的书写方法
2007/06/02 Javascript
IE6弹出“已终止操作”的解决办法
2010/11/27 Javascript
JS.elementGetStyle(element, style)应用示例
2013/09/24 Javascript
Jquery实现点击按钮,连续地向textarea中添加值的实例代码
2014/03/08 Javascript
JavaScript原生对象之String对象的属性和方法详解
2015/03/13 Javascript
jQuery延迟加载图片插件Lazy Load使用指南
2015/03/25 Javascript
JQuery插件Quicksand实现超炫的动画洗牌效果
2015/05/03 Javascript
关于数据与后端进行交流匹配(点亮星星)
2016/08/03 Javascript
jQuery Validate验证框架详解(推荐)
2016/12/17 Javascript
js判断是否是手机页面
2017/03/17 Javascript
JavaScript变量类型以及变量作用域详解
2017/08/14 Javascript
vue 解决uglifyjs-webpack-plugin打包出现报错的问题
2020/08/04 Javascript
PYTHON正则表达式 re模块使用说明
2011/05/19 Python
Python多版本开发环境管理工具介绍
2019/07/03 Python
django认证系统 Authentication使用详解
2019/07/22 Python
python 根据字典的键值进行排序的方法
2019/07/24 Python
python将四元数变换为旋转矩阵的实例
2019/12/04 Python
Python开发企业微信机器人每天定时发消息实例
2020/03/17 Python
PyQT5速成教程之Qt Designer介绍与入门
2020/11/02 Python
实例讲解使用HTML5 Canvas绘制阴影效果的方法
2016/03/25 HTML / CSS
德国柯吉澳趣味家居:Koziol
2017/08/24 全球购物
JYSK加拿大:购买家具、床垫、家居装饰等
2020/02/14 全球购物
什么是就业协议书
2014/04/17 职场文书
征兵宣传标语
2014/06/20 职场文书
在职党员进社区活动总结
2014/07/05 职场文书
人力资源管理专业自荐书
2014/07/07 职场文书
国家奖学金获奖感言
2014/08/16 职场文书
护林员个人总结
2015/03/04 职场文书
员工手册董事长致辞
2015/07/29 职场文书
大学生学习十八届五中全会精神心得体会
2016/01/05 职场文书
mysql中整数数据类型tinyint详解
2021/12/06 MySQL
python中出现invalid syntax报错的几种原因分析
2022/02/12 Python
Python selenium绕过webdriver监测执行javascript
2022/04/12 Python