Skip to content

下载依赖模块

bash
pip install pillow

# 我用的是10.1.0版本的pillow
pip install pillow==10.1.0 -i https://pypi.tuna.tsinghua.edu.cn/simple

基本使用

参考:https://www.cnblogs.com/wupeiqi/articles/5812291.html

1. 创建图片

这里相当于要先搞出来一个白板图片,我们后续才好方便在这个白板图片上刻画我们想要的效果。

python
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白板图片。

放大后的截图效果:

1832669389430718464.png

2. 创建画笔

创建画笔,用于在白板图片上刻画任意内容。

python
from PIL import Image, ImageDraw

# 创建白板图片
img = Image.new(mode='RGB',size=(120, 30),color=(255, 255, 255))

# 创建画笔对象
draw = ImageDraw.Draw(img, mode='RGB')

画点

python
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')

放大后的截图效果:

1832669389535576064.png

画线

python
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')

放大后的截图效果:

1832669391334932480.png

画圆

python
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')

放大后的截图效果:

1832669391519481856.png

写英文文本

python
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')

放大后的截图效果:

1832669391829860352.png

写入特殊字体和中文

写入特殊字体和中文就需要多做一些工作了。引入特殊字体和中文,就需要提前下载好对应的字体,我们这里可以从网上下载对应的免费字体。

关于不同字体文件后缀的解释,摘自: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

1832669392006021120.png

然后将字体文件AlimamaShuHeiTi-Bold.otf放到脚本文件同级目录内。

上代码:

python
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')

放大后的截图效果:

1832669392660332544.png

集成示例

python
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')

1832669392870047744.png

在Django项目中集成图片验证码

https://www.luffycity.com/play/21366

python
python