利用Python如何生成便签图片详解
作者:这个人很懒 发布时间:2021-01-02 18:09:52
前言
最近有文字转图片的需求,但是不太想下载 APP,就使用 Python Pillow 实现了一个,效果如下:
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 格式化后的文字写到图片上效果可能是这样的:
使用这种方式,如果我们要调整字体大小,每一行的长度都还需要再重新调整。
为了保证每一行宽度尽可能的一致,这里使用 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
这是按字符宽度分隔文本写到图片的效果:
由于文本长度不固定,生成得到的文本高度也不固定,背景图我们也需要动态生成
根据文本高度生成背景图
通过图片我们可以看到,头部和尾部是固定的,变化的是文字部分,那么背景图片的高度计算公式为
背景图片高度=头部高度+尾部高度+文本高度
实现代码如下:
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 ]
执行后效果如图:
遇到的问题
为了能方便使用,我把这个做成了公号的一个功能,然后遇到了一个严重问题, 太慢了!
使用 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。
来源:http://blog.gusibi.com/post/python-text-to-image/


猜你喜欢
- 前言A货:什么!你不会背圆周率(鄙夷的眼神) 3.1415926535 8979323846 26433... 桥哥:我会算呀 !
- tags faker 随机 虚拟faker文档链接代码程序:# -*- coding=utf-8 -*-import sysfrom fak
- python2.7安装opencv-python很慢且总是失败当直接使用pip安装opencv-python时,且总是报错,找了好久,发现是
- Python 处理数据的实例详解最近用python(3.2的版本)写了根据特定规则,处理数据的一个小程序,用到了一些python常用的基础知
- 超文本传输协议http构成了万维网的基础,它利用URI(统一资源标识符)来识别Internet上的数据,而指定文档地址的URI被称为URL(
- 前言这篇来学习正则表达式的替换和分组功能,替换很好理解,分组有点复杂,特别是如何写有效的分组的正则表达式。这篇通过几个练习,简单了解下替换和
- 在前面的<如何使用PHP计算上一个月的今天>一文中, 我们提到strtotime函数在使用strtotime(”-1 month
- 下面就是我们的authenticate.asp页面,在这里,将用户的信息收集起来,连同最初的URL一起传到一个识别用户身份的页面中。我们可用
- 一个不错的绿色下划线的简洁CSS导航代码,纯css不用图片,效果图如下:<html> <head> <meta
- 昨天还和裕波沟通我没有准备ppt,想以另一种互动的方式来交流。后来想想,每个人都准备了,我不准备也不太好意思,呵呵。今天在webrebuil
- phpMyAdmin可以管理整个MySQL服务器(需要超级用户),也可以管理单个数据库。为了实现后一种,你将需要合理设置MySQL用户,他只
- Pytorch转ONNX的意义一般来说转ONNX只是一个手段,在之后得到ONNX模型后还需要再将它做转换,比如转换到TensorRT上完成部
- 如何在页面中实现对电子信箱的访问?emaile.htm<HTML><HEAD><META NAME=
- 使用MySQL,安全问题不能不注意。以下是MySQL提示的23个注意事项:1.如果客户端和服务器端的连接需要跨越并通过不可信任的网络,那么就
- Python 语言里有许多(而且是越来越多)的高级特性,是 Python 发烧友们非常喜欢的。在这些人的眼里,能够写出那些一般开发者看不懂的
- 在编写Web自动化测试用例的时候,如何写断言使新手不解,严格意义上来讲,没有断言的自动化脚本不能叫测试用例。就像功能测试一样,当测试人员做了
- 问题:如何在报表中每隔N行显示一条粗线如何为报表增加一个行号列?回答:1、在设计模式里打开该报表,在报表主体里面加一个TextBox,把Na
- 第一种,fitimport kerasfrom keras.models import Sequentialfrom keras.layer
- Python对于json数据键值对遍历Python中可以使用json模块来解析JSON格式的数据,将其转换成Python中的字典或者列表对象
- 首先,建一个文件夹,把你要隐藏的文件放在里面,你的所有黑客程序什么的。假设你建的文件夹叫hacker然后呢,我们给这个文件夹加个扩展名(后缀