下载依赖模块
pip install pillow
# 我用的是10.1.0版本的pillow
pip install pillow==10.1.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
基本使用
1. 创建图片
这里相当于要先搞出来一个白板图片,我们后续才好方便在这个白板图片上刻画我们想要的效果。
from PIL import Image
img = Image.new(
mode='RGB', # 像的模式,一般都用RGB
size=(120, 30), # 图像的尺寸,一个二位元组,(宽,高)
# 关于color参数,如果没有给这个参数设置值,默认是黑色背景。如果需要给出的话,根据图像的模式,给出不同通道数的值
# 如果是RGB图像,可以使用字符串直接表示
# 我们知道,一般彩色图像,是三个通道的,红绿蓝三个通道。所以,我们如果要创建白色图像的话,第三个参数,用元组表示为(255, 255, 255)
# 你可以参考这个网站的常用颜色对照表:https://tool.oschina.net/commons?type=3
color=(255, 255, 255)
)
# 在图片查看器中打开
# img.show()
# 保存到本地,方式1
with open('code.png', 'wb') as f:
img.save(f, format='png') # 只能生成png的图片
# 保存到本地,方式2
# img.save('code.png')
运行代码,就会在本地脚本同级目录生成一个code.png
白板图片。
放大后的截图效果:
2. 创建画笔
创建画笔,用于在白板图片上刻画任意内容。
from PIL import Image, ImageDraw
# 创建白板图片
img = Image.new(mode='RGB',size=(120, 30),color=(255, 255, 255))
# 创建画笔对象
draw = ImageDraw.Draw(img, mode='RGB')
画点
from PIL import Image, ImageDraw
# 创建白板图片
img = Image.new(mode='RGB',size=(120, 30),color=(255, 255, 255))
# 创建画笔对象
draw = ImageDraw.Draw(img, mode='RGB')
# 画点,使用画笔工具进行画线
# 第一个参数是x,y轴坐标,第二个参数是表示在第一个参数的坐标点上画点的颜色
# 注意,坐标点的范围要在白板图片的范围内
# fill参数有三种写法
# rgb:(0, 100, 0)
# 16色:#FFFAFA
# 英文: red
draw.point([0, 0], fill=(0, 100, 0))
draw.point([10, 10], fill='red')
draw.point([20, 20], fill=(0, 0, 0))
# 最终保存图片
img.save('code.png')
放大后的截图效果:
画线
from PIL import Image, ImageDraw
# 创建白板图片
img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255))
# 创建画笔对象
draw = ImageDraw.Draw(img, mode='RGB')
# # 画点,使用画笔工具进行画线
# draw.point([0, 0], fill=(0, 100, 0))
# draw.point([10, 10], fill='red')
# draw.point([20, 20], fill=(0, 0, 0))
# 画线,使用画笔工具进行画线
# 第一个参数是起始坐标和结束坐标,一共四个元素
# 第二个参数是线的颜色
draw.line([5, 5, 5, 10], fill='red')
draw.line([15, 15, 15, 20], fill=(0, 0, 0))
draw.line([25, 25, 30, 30], fill=(0, 100, 0))
img.save('code.png')
放大后的截图效果:
画圆
from PIL import Image, ImageDraw
# 创建白板图片
img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255))
# 创建画笔对象
draw = ImageDraw.Draw(img, mode='RGB')
# 画圆,使用画笔工具进行画圆
# 画圆的思路是相当于我们在正方形中画圆,然后拿到两个斜对角的坐标即可
# 第一个参数是两个斜对角的坐标起始位置和结束位置
# 第二,三个参数表示开始角度和结束角度,这个圆的角度是 右0°下90°左180°上270°右360°
# 第四个参数表示圆的颜色
# 下面这俩点是帮我们点出来斜对角的
draw.point([5, 5], fill="red")
draw.point([20, 20], fill="red")
draw.arc([5, 5, 20, 20], 0, 90, fill='red')
draw.arc([5, 5, 20, 20], 90, 180, fill=(0, 100, 0))
draw.arc([5, 5, 20, 20], 180, 270, fill=(0, 0, 0))
draw.arc([5, 5, 20, 20], 270, 360, fill=(0, 0, 128))
# 下面这俩点是帮我们点出来斜对角的
draw.point([45, 5], fill="red")
draw.point([55, 15], fill="red")
draw.arc([45, 5, 55, 15], 0, 180, fill=(0, 0, 0))
draw.arc([45, 5, 55, 15], 180, 360, fill=(0, 139, 69))
img.save('code.png')
放大后的截图效果:
写英文文本
from PIL import Image, ImageDraw
# 创建白板图片
img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255))
# 创建画笔对象
draw = ImageDraw.Draw(img, mode='RGB')
draw.point([5, 5], fill="black")
draw.point([5, 18], fill="black")
# 写文本
# 第一个参数:表示起始坐标,用列表和元组都行,不过这里推荐使用元组
# 第二个参数:表示要写入的内容
# 第三个参数:文字颜色
draw.text((5, 5), 'zhangkai', 'red') # 英文是没问题的
draw.text((5, 18), '张开', 'red') # 中文就乱码了,稳住,后面我们能解决这个问题
img.save('code.png')
放大后的截图效果:
写入特殊字体和中文
写入特殊字体和中文就需要多做一些工作了。引入特殊字体和中文,就需要提前下载好对应的字体,我们这里可以从网上下载对应的免费字体。
- Google fonts:https://fonts.google.com/, 有免费的字体可供下载,不过访问该网站需要翻墙。
- github上也有很多,不过这个网站国内一般也不太好直接访问。
- 国内能直接访问的:
关于不同字体文件后缀的解释,摘自:https://www.cnblogs.com/hackermi/p/9291087.html
TTF(TrueType Font)是Apple公司和Microsoft公司共同推出的字体文件格式,随着windows的流行,已经变成最常用的一种字体文件表示方式。 而OTF(OpenType Font)是 TTF 的升级版,而 OTF 是采用的是 PostScript 曲线,支持 OpenType 高级特性的更高级字体。 TTC全称是TrueType Collection,它是TrueType字体集成文件(. TTC文件),是在一单独文件结构中包含多种字体,以便更有效地共享轮廓数据,当多种字体共享同一笔画时,TTC技术可有效地减小字体文件的大小。 说白了,TTC就是几个TTF合成的字库,安装后字体列表中会看到两个以上的字体。两个字体中大部分字都一样时,可以将两种字体做成一个TTC文件,常见的TTC字体,因为共享笔划数据,所以大多这个集合中的字体区别只是字符宽度不一样,以便适应不同的版面排版要求。而TTF字体则只包含一种字型。
我这里以阿里妈妈数黑体为例,下载地址:https://www.iconfont.cn/fonts/detail?spm=a313x.fonts_index.i1.d9df05512.62e03a81JN735u&cnid=a9fXc2HD9n7s
然后将字体文件AlimamaShuHeiTi-Bold.otf
放到脚本文件同级目录内。
上代码:
from PIL import Image, ImageDraw, ImageFont
# 创建白板图片
img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255))
# 创建画笔对象
draw = ImageDraw.Draw(img, mode='RGB')
# 拿到字体对象
# 第一个参数,填写字体文件的路径,如果是Django项目,建议:你这个路径要拼接出来一个绝对路径,因为你的Python代码
# 和字体文件基本上不在同一个目录内,所以必须用os拼接出来一个绝对路径,保证无论字体文件在哪,都能找到它
# 第二个参数是字体大小
font = ImageFont.truetype('./AlimamaShuHeiTi-Bold.otf', 10)
# 写文本
# 第一个参数:表示起始坐标,用列表和元组都行,不过这里推荐使用元组
# 第二个参数:表示要写入的内容
# 第三个参数:文字颜色
draw.text((5, 5), 'zhangkai', 'red', font=font)
draw.text((5, 18), '张开', 'red', font=font)
img.save('code.png')
放大后的截图效果:
集成示例
import random
import string
from io import BytesIO
from PIL import Image, ImageDraw, ImageFont, ImageFilter
class PILCode(object):
"""
基于pillow模块,实现的网站登录页面常见的验证码图片
"""
# 生成的随机验证码就是从这个字符串中随机选出来的
chars = string.digits + string.ascii_letters # 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
def __init__(self, font_path, width=120, height=30, code_length=4, font_size=24, pointer=True, line=True, arc=True):
self.code = '' # 最终生成的随机验证码
self.mode = 'RGB'
self.width = width # 图片宽度
self.height = height # 图片高度
self.code_length = code_length # 生成几位的验证码,不传默认生成4为验证码
self.font_path = font_path # 必须传递字体文件的绝对路径
self.font_size = font_size # 文字大小,文字最终呈现效果要结合实际文字大小结合手动调试
# 拿到图片对象,你也可以控制color值来决定背景颜色,255,255,255是白色背景
self.img = Image.new(self.mode, size=(self.width, self.height), color=(255, 255, 255))
self.font_obj = ImageFont.truetype(self.font_path, self.font_size) # 拿到字体对象
self.draw_obj = ImageDraw.Draw(self.img, mode=self.mode) # 拿到画笔对象
# 干扰点的数量密度,值越大,干扰点越多
self._pointer_num = 20 # 这个值不宜过大,因为画圆圈那里也会有情况画出来的圆最终是个点,跟这个点一样了,所以,这里的值不宜过大
# 干扰线的数量密度,值越大,干扰线越多
self._line_num = 3
# 干扰圆圈的数量密度,值越大,干扰线越多
self._arc_num = 30
self._arc_r = 4 # 干扰圆圈的直径,值越大,圆圈越大
# 默认生成的验证码是包含干扰点、干扰线、干扰圆圈的,你可以为这三个值传值False来控制不加这些选项
if pointer:
self.gen_pointer()
if line:
self.gen_line()
if arc:
self.gen_arc()
def gen_code(self):
""" 根据self.code_length值生成指定位数的验证码 """
for i in range(self.code_length):
self.code += random.choice(self.chars)
@property
def gen_color(self):
""" 生成随机颜色 """
return tuple(random.sample(range(0, 256), k=3))
def gen_pointer(self):
""" 在图片上画干扰点 """
for i in range(self._pointer_num): # 循环生成指定数量的干扰点
self.draw_obj.point(
[random.randint(0, self.width), random.randint(0, self.height)],
fill=self.gen_color
)
def gen_line(self):
""" 在图片上画干扰线 """
for i in range(self._line_num): # 循环生成指定数量的干扰线
start_x, start_y = random.randint(0, self.width), random.randint(0, self.height)
end_x, end_y = random.randint(0, self.width), random.randint(0, self.height)
self.draw_obj.line([start_x, start_y, end_x, end_y], fill=self.gen_color)
def gen_arc(self):
""" 在图片上画干扰圆圈 """
for i in range(self._arc_num): # 循环生成指定数量的干扰圆圈
# self.draw_obj.point([random.randint(0, self.width), random.randint(0, self.height)], self.gen_color)
x, y = random.randint(0, self.width), random.randint(0, self.height)
# random.choice(range(0, 360)) 这个值表示画的是否是完整的圆
self.draw_obj.arc((x, y, x + self._arc_r, y + self._arc_r), 0, random.choice(range(0, 360)),
fill=self.gen_color)
def gen_char(self):
""" 将验证码写入到图片中 """
# 生成随机验证码
self.gen_code()
# 逻辑就是根据图片宽度将图片根据验证码的长度等分,然后后每份写入一个字符
for index, char in enumerate(self.code, 0):
# 由于第一个文字的width是0,所以很靠边,所以通过random.randint(1, 2)让每个文字都往右移动两个1到2个像素,就好看一些
w = index * self.width / self.code_length + random.randint(1, 2)
# 控制高度不同,让验证码的每个文字上下有波动,文字的位置要和结合蚊子大小手动测试调整
h = random.randint(0, 2)
self.draw_obj.text((w, h), text=char, font=self.font_obj, fill=self.gen_color)
def gen_img(self):
""" 最终生成验证码图片 """
# 将随机验证码写入到白板图片中
self.gen_char()
# 滤镜,边界加强
self.img = self.img.filter(ImageFilter.EDGE_ENHANCE_MORE)
def save_local_file(self, file_path):
"""
写入到本地图片中, 必须是png类型的图片
file_path: 图片保存位置
"""
# 生成图片
self.gen_img()
# 保存图片到本地
self.img.save(file_path)
def save_to_memory(self):
""" 写入到内存中 """
# 开辟内存空间并写入图片
stream = BytesIO()
self.img.save(stream, 'png')
# 将内存中的二进制图片数据返回,如果是Django项目,你可以调用这个方法,然后将数据流返回给前端
return stream.getvalue()
def show_img(self):
""" 用你系统的图片查看器打开图片 """
# 生成图片
self.gen_img()
# 展示
self.img.show()
if __name__ == '__main__':
obj = PILCode(font_path='./AlimamaShuHeiTi-Bold.otf', code_length=6)
# obj.show_img()
obj.save_local_file('./code.png')
在Django项目中集成图片验证码
https://www.luffycity.com/play/21366