利用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实现根据图标提取分类应用程序实例
Sep 28 Python
在ironpython中利用装饰器执行SQL操作的例子
May 02 Python
python统计文本文件内单词数量的方法
May 30 Python
Python中的一些陷阱与技巧小结
Jul 10 Python
Python如何抓取天猫商品详细信息及交易记录
Feb 23 Python
编写多线程Python服务器 最适合基础
Sep 14 Python
pthon贪吃蛇游戏详细代码
Jan 27 Python
Python字符串内置函数功能与用法总结
Apr 16 Python
Python TCP通信客户端服务端代码实例
Nov 21 Python
从pandas一个单元格的字符串中提取字符串方式
Dec 17 Python
Python for循环通过序列索引迭代过程解析
Feb 07 Python
基于Python制作一副扑克牌过程详解
Oct 19 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实现获取url地址中顶级域名的方法示例
2019/06/05 PHP
获取URL地址中的文件名和参数的javascript代码
2009/09/02 Javascript
ASP.NET jQuery 实例10 动态修改hyperlink的URL值
2012/02/03 Javascript
分析Node.js connect ECONNREFUSED错误
2013/04/09 Javascript
js 浏览本地文件夹系统示例代码
2013/10/24 Javascript
jquery索引在使用中的一些困惑
2013/10/24 Javascript
Javascript中3个需要注意的运算符
2015/04/02 Javascript
js改变embed标签src值的方法
2015/04/10 Javascript
js模拟淘宝网的多级选择菜单实现方法
2015/08/18 Javascript
jQuery实现默认是闭合的FAQ展开效果菜单
2015/09/14 Javascript
详解js图片轮播效果实现原理
2015/12/17 Javascript
JavaScript实现ASC转汉字及汉字转ASC的方法
2016/01/23 Javascript
JavaScript统计字符串中每个字符出现次数完整实例
2016/01/28 Javascript
vue2.0路由切换后页面滚动位置不变BUG的解决方法
2018/03/14 Javascript
vue.js实现格式化时间并每秒更新显示功能示例
2018/07/07 Javascript
vue实现动态显示与隐藏底部导航的方法分析
2019/02/11 Javascript
js 计数排序的实现示例(升级版)
2020/01/12 Javascript
Vue+webpack实现懒加载过程解析
2020/02/17 Javascript
JavaScript实现单点登录的示例
2020/09/23 Javascript
[01:57]2018DOTA2亚洲邀请赛赛前采访-iG
2018/04/03 DOTA
python三元运算符实现方法
2013/12/17 Python
python自动化测试实例解析
2014/09/28 Python
python获取酷狗音乐top500的下载地址 MP3格式
2018/04/17 Python
浅谈python写入大量文件的问题
2018/11/09 Python
Python 实现还原已撤回的微信消息
2019/06/18 Python
Python 字符串、列表、元组的截取与切片操作示例
2019/09/17 Python
Python使用urlretrieve实现直接远程下载图片的示例代码
2020/08/17 Python
处理textarea中的换行和空格
2019/12/12 HTML / CSS
澳洲在线厨具商店:Kitchen Style
2018/05/05 全球购物
公司业务主管岗位职责
2013/12/07 职场文书
党风廉政建设责任书
2014/04/14 职场文书
大型演出策划方案
2014/05/28 职场文书
政工例会汇报材料
2014/08/26 职场文书
餐厅服务员岗位职责
2015/02/09 职场文书
投资申请报告
2015/05/19 职场文书
Python四款GUI图形界面库介绍
2022/06/05 Python