利用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 数据结构之队列的实现
Jan 22 Python
python使用两种发邮件的方式smtp和outlook示例
Jun 02 Python
Python基于回溯法子集树模板解决野人与传教士问题示例
Sep 11 Python
python获取多线程及子线程的返回值
Nov 15 Python
Python 用Redis简单实现分布式爬虫的方法
Nov 23 Python
python3学习之Splash的安装与实例教程
Jul 09 Python
Python中list的交、并、差集获取方法示例
Aug 01 Python
Python中six模块基础用法
Dec 08 Python
python3光学字符识别模块tesserocr与pytesseract的使用详解
Feb 26 Python
python 发送邮件的四种方法汇总
Dec 02 Python
详解Python Celery和RabbitMQ实战教程
Jan 20 Python
Python编程super应用场景及示例解析
Oct 05 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
ThinkPHP中处理表单中的注意事项
2014/11/22 PHP
php获取flash尺寸详细数据的方法
2016/11/12 PHP
PHP判断数组是否为空的常用方法(五种方法)
2017/02/08 PHP
swoole和websocket简单聊天室开发
2017/11/18 PHP
PHP排序算法之简单选择排序(Simple Selection Sort)实例分析
2018/04/20 PHP
用倒置滤镜把div倒置,再把table倒置。
2007/07/31 Javascript
Cookie 注入是怎样产生的
2009/04/08 Javascript
Javascript仿PHP $_GET获取URL中的参数
2014/05/12 Javascript
JavaScript中使用Object.create()创建对象介绍
2014/12/30 Javascript
详解jQuery Mobile自定义标签
2016/01/06 Javascript
jQuery+CSS实现简单切换菜单示例
2016/07/27 Javascript
Webpack实现按需打包Lodash的几种方法详解
2017/05/08 Javascript
vue.js框架实现表单排序和分页效果
2017/08/09 Javascript
使用yeoman构建angular应用的方法
2017/08/14 Javascript
详解AngularJS1.x学习directive 中‘& ’‘=’ ‘@’符号的区别使用
2017/08/23 Javascript
js 图片转base64的方式(两种)
2018/04/24 Javascript
结合Vue控制字符和字节的显示个数的示例
2018/05/17 Javascript
Javascript中绑定click事件的四种方式介绍
2018/10/26 Javascript
前端vue-cli项目中使用img图片和background背景图的几种方法
2019/11/13 Javascript
微信小程序 自定义弹窗实现过程(附代码)
2019/12/05 Javascript
Vue关于组件化开发知识点详解
2020/05/13 Javascript
python中的&&及||的实现示例
2019/08/07 Python
Python常用模块os.path之文件及路径操作方法
2019/12/03 Python
pycharm软件实现设置自动保存操作
2020/06/08 Python
Pycharm创建python文件自动添加日期作者等信息(步骤详解)
2021/02/03 Python
完美解决IE8下不兼容rgba()的问题
2017/03/31 HTML / CSS
本科应届生自荐信
2014/06/29 职场文书
刑事辩护授权委托书
2014/09/13 职场文书
2014年企业团支部工作总结
2014/12/10 职场文书
认真学习保证书
2015/02/26 职场文书
抢劫罪辩护词
2015/05/21 职场文书
2016年度继续教育学习心得体会
2016/01/19 职场文书
django注册用邮箱发送验证码的实现
2021/04/18 Python
Mysql中where与on的区别及何时使用详析
2021/08/04 MySQL
Apache Hudi集成Spark SQL操作hide表
2022/03/31 Servers
一篇文章带你掌握SQLite3基本用法
2022/06/14 数据库