Python: 生成带用户昵称的头像

本文介绍如何使用Python的PIL库生成带昵称的头像,避免中间图片、减少第三方库依赖并解决字体大小问题。通过调整代码,实现了直接在新建图片上绘制文字,不再需要读取底片,同时根据字符类型动态调整字体大小。最终简化代码,生成的头像效果与预期一致。
摘要由CSDN通过智能技术生成

Python: 生成带用户昵称的头像

需求

新建用户后,根据用户输入的昵称生成图片。(例:注册"钉钉"用户后,头像根据输入的名字生成)

开发环境

  • Windows 10
  • Python 3.8
  • Pillow 8.1.2

实现

蛇皮皮蛋:Python创建文字图片(居中)/多图片合并(PIL),参考这篇文章,实现了新建一个图片,并把文字渲染到图片上。代码如下:

import cv2
import numpy as np
from PIL import ImageFont, ImageDraw, Image


def new_image(size, color, name):
    img = Image.new('RGB', size, color)  # 生成图片
    img.show()  # 展示图片
    img.save(name)  # 保存图片


def create_font_img(value, file_name, path):
    img = cv2.imread(path)  # 打开底片
    font_path = "simsun/simsun.ttc"  # 设置需要显示的字体
    font = ImageFont.truetype(font_path, size=100)  # 字体大小为 100
    img_pil = Image.fromarray(img)
    draw = ImageDraw.Draw(img_pil)
    text_width, text_height = draw.textsize(value, font)  # 获取字体宽高
    # 绘制文字信息,img.shape 求图片的宽和高,字体绘制的开始位置(x, y),(255,255,255) 为白色字体
    draw.text(((img.shape[1] - text_width) / 2, (img.shape[0] - text_height) / 2), value, fill=(255, 255, 255), font=font)
    bk_img = np.array(img_pil)
    # cv2.imshow("add_text", bk_img)
    # cv2.waitKey()
    cv2.imwrite(file_name + "_font.png", bk_img)

new_image((128, 128), (192, 202, 208), "new.jpg")
create_font_img("张", "name", "new.jpg")

代码执行后得到两张图片,如下:

底图

底图

成果图

成果图1

根据上文提到的文章,简化后得到了以上代码,第一个函数 new_image() 新建一个图片,没有异议。

第二个函数 create_font_img() 将文字渲染到图片上。首先是 cv2.imread() 读取图片,之后设置文字的字体与大小,draw.text() 将文字画到图片上。注意到此函数的最后三行代码,cv2.imshow() 的意思应该非常明显,即直接显示图片,cv2.imwrite() 保存图片,那么 cv2.waitKey() 是什么意思呢?

这不是此文章重点,可以看看下面这篇文章:山上有强强:cv2.waitKey的入门级理解

注意 cv2 需要安装 opencv-python 使用,命令是 pip install opencv-python

事情到这里并没有结束,我对以上代码并不满意。主要问题如下:

  • 需要新建一张图片为底片,再读取此图片,渲染文字。如果能不保存,直接使用此图片就更好了。
  • 需要读字体文件,一个字体文件非常大,至少此处用到的 simsun.ttc 字体文件就有 10 MB 之大。如果能从系统读取,或者不使用字体文件会更好。
  • 所用第三方库太多,达到三个。三个库看起来不多,但是如非必要勿增实体(包括上面的字体文件),特别是部分库版本不一,用在项目中,越少越好,以免出现什么问题。

基于这三个问题,必需简化代码。

进阶

经过搜索,发现了下面这篇文章:amigo1226:自动生成带昵称的头像(仿照钉钉头像)。此文章,实现了不用底片直接生成图片。不过这篇文章是用 Java 写的,为了验证是否成功,我特意用 IDEA 运行了代码,确实可以成功,得到的图与上文的成果图1一样。那么,Python 理论上也是可以的。

仔细阅读此文章中的代码,发现一句代码:BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);, BufferedImage 这意思似乎是说图片存在缓存中?那么就看看 PIL 库有没有类似的方法把。果然,Image 类中还真有一个方法,是 frombuffer(),不过在多次尝试后,此方法似乎并不行。仔细思考我们的代码,第一个函数中用了 img.save() 来保存图片。

那么我们不能立即保存图片,其次 create_font_img() 中使用了 cv2.imread() 读取图片,既然不用读取图片,这句代码也就可以删除了。此时 cv2 的用法只剩 cv2.imwrite() 保存图片了,既然打算不使用那么多库,PIL 也是有保存方法的(img.save()),如此一来便可以将 cv2 库的用法从代码中删除。

def create_font_img(value, file_name, path):
    # img = cv2.imread(path)  # 打开底片
    img = Image.new('RGB', (128, 128), (192, 202, 208))  # 生成图片
    font_path = "simsun/simsun.ttc"  # 设置需要显示的字体
    font = ImageFont.truetype(font_path, size=100)  # 字体大小为 100
    img_pil = Image.fromarray(img)
    draw = ImageDraw.Draw(img_pil)
    text_width, text_height = draw.textsize(value, font)  # 获取字体宽高
    # 绘制文字信息,img.shape 求图片的宽和高,字体绘制的开始位置(x, y),(255,255,255) 为白色字体
    draw.text(((img.shape[1] - text_width) / 2, (img.shape[0] - text_height) / 2), value, fill=(255, 255, 255), font=font)
    bk_img = np.array(img_pil)
    # cv2.imwrite(file_name + "_font.png", bk_img)
    img.save(file_name + "_font.png")  # 保存图片

初步简化代码如上,这样的代码并不能正确执行,问题很多,首先是,cv2.imread() 读取图片文件得到的是 <class 'numpy.ndarray'> 类型。这个类型下面 Image.fromarray() 创建图像,和 img.shape 要读取图片像素大小。而 Image.new() 创建的图片并不是此种类型,而是 <class 'PIL.Image.Image'>。本来打算看看是否可以将 <class 'PIL.Image.Image'> 转为 <class 'numpy.ndarray'>(其实就是 np.array() 这个方法),但转念一想,不打算用 numpy 库,也就没有转类型的必要,直接看能否只使用 PIL 库作图。

整理一下,那么整个作图流程如下:新建一张图,求出图的像素大小(其实大小在新建图时已给出),设置需要画上去的文字的字体与大小,求出文字的像素大小(设置字体时已给出),将文字画在图片上;保存图片。

那么由此得出的代码如下:

def create_font_img(name):
    img = Image.new('RGB', (128, 128), (192, 202, 208))  # 生成一张像素 128*128 图片
    font_path = "simsun/simsun.ttc"  # 设置需要显示的字体
    font = ImageFont.truetype(font_path, size=100)  # 字体大小为 100
    draw = ImageDraw.Draw(img)  # 绘图
    # 绘制文字信息,字体绘制的开始位置(x, y),(255,255,255) 为白色字体
    draw.text(((128 - 100) / 2, (128 - 100) / 2), name, fill=(255, 255, 255), font=font)
    # img.show()  # 展示图片
    img.save("name.png")  # 保存图片

执行以上的代码,得到的图与成果图1一样。

那么图片的像素大小与文字的像素大小是否有相应的方法获取呢?既然 cv2.imread()img.shape()) 和 ImageDraw.Draw()draw.textsize())可以读取图片和文字像素大小,理论上 Image.new()ImageFont.truetype() 应该有对应的方法。通过查找该对象所拥有的函数,发现了两个方法 size 和 getsize,分别使用 img.sizefont.getsize() 可以得到图片像素和文字像素。

修改后的代码如下:

def create_font_img(name):
    img = Image.new('RGB', (128, 128), (192, 202, 208))  # 生成图片
    img_width, img_height  = img.size
    font_path = "simsun/simsun.ttc"  # 设置需要显示的字体
    font = ImageFont.truetype(font_path, size=100)  # 字体大小为 100
    text_width, text_height = font.getsize(name)  # 获取字体宽高
    draw = ImageDraw.Draw(img)
    # 绘制文字信息,img.shape 求图片的宽和高,字体绘制的开始位置(x, y),(255,255,255) 为白色字体
    draw.text(((img_width - text_width) / 2, (img_height - text_height) / 2), name, fill=(255, 255, 255), font=font)
    # img.show()
    img.save("name.png")  # 保存图片

用户昵称可能是中文也可能是英文,这样字体的大小为一个不变的值不太好,同时将需要更改的数值提取出来,优化代码后如下:

from PIL import Image, ImageFont, ImageDraw

def create_font_img(username, img_size, img_bg_color, file_name):
    img = Image.new('RGB', img_size, img_bg_color)  # 生成图片
    img_width, img_height  = img.size
    font_path = "simsun/simsun.ttc"  # 设置需要显示的字体
    if '\u4e00' <= username <= '\u9fff':
        font = ImageFont.truetype(font_path, size=100)  # 中文字符字体大小为 100
    elif 'a' <= username <= 'z' or 'A' <= username <= 'Z':
        username = username.upper()  # 将英文字母转为大写
        font = ImageFont.truetype(font_path, size=60)  # 英文字符字体大小为 60
    else:
        font = ImageFont.truetype(font_path, size=32)  # 其他字符字体大小为 32
    text_width, text_height = font.getsize(username)  # 获取字体宽高
    draw = ImageDraw.Draw(img)
    # 绘制文字信息,字体绘制的开始位置(x, y),(255,255,255) 为白色字体
    draw.text(((img_width - text_width) / 2, (img_height - text_height) / 2), username, fill=(255, 255, 255), font=font)
    img.save(file_name + ".png")  # 保存图片

create_font_img('张', (128, 128), (192, 202, 208), 'zhang')

到此,解决了需要中间图片和第三方库多的问题,只剩下一个字体文件的问题?能否不使用字体文件呢?或者使用系统中的文件呢?

经过尝试,如果使用默认的字体文件,无法获取字体的像素大小,因为设置默认字体的方法是 ImageFont.load_default().font,并没有指定像素大小,将 draw.text() 中的 char_width_height 设置定值,但是 draw.text() 中的 font 出现了中文字符的编解码问题,英文倒是可以正确执行。不过没有设置字体大小,导致一张 128*128 像素的图片中,字符的大小非常小,几乎不可见,如图:

成果图2

成果图2

经过搜索,在这个问答中 如何使用Python ImageDraw库更改字体大小 发现了一句话,如下:

每个PIL’s docs,ImageDraw的默认字体是位图字体,因此无法缩放。要进行缩放,需要选择真正的字体类型。我希望不难找到一个不错的truetype字体“看起来有点像”默认字体在您想要的字体大小!

意思应该非常明显了,如果想要缩放字体的大小,那么就必需指定字体文件了。在搜索中了解到,也可以从系统中,加载字体文件,windows 系统中有许多字体文件,目录为 C:\Windows\Fonts,可以从这里获取字体文件。在项目中没有 simsun.ttc 时,需要先下载一个,或者先使用 Fonts 下的文件,Fonts 中的文件如下图:

Fonts 中的字体文件

Fonts 中的字体文件

将上文代码中 font_path = "simsun/simsun.ttc" 修改为 font_path = "C:\\Windows\\Fonts\\STXINGKA.TTF",字体为"华文行楷 常规",得到的图片如下:

成果图3

成果图3

总结

本文介绍了如何生成一张图片,并加以文字(用户昵称),探索了一些 PIL 的用法。

生成的图片可能并不完美,比如色彩的搭配,图片的圆角设置,以及文字字符过多如何割舍等问题,留待读者自行探索。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值