效果图
我们先来看一下大致的效果图。
以上图形都是用非常基础的元素随机构成的:点,线,曲线,文本。而pillow模块远远不止这些功能,如果学好了它,真的就是你想怎么花就怎么花。 那么现在我们就去学习一下它的简单使用吧!
pillow的基本使用
安装pillow模块
pillow属第三方模块,我们需要使用pip进行下载。值得注意的是,早期该模块名为PIL,后来改成pillow。
pip install pillow
创建画布
创建画布前需要导入pillow中的Image。该模块既能生成一张图片在上面做修改,也能导入一张图片在上面做修改。在图片验证码的示例中,我们生成一张图片即可。生成图片我们使用的是Image的new方法,其包含三个参数:
- mode:图片模式(具体可见下图)
- size:图片尺寸
- color:图片颜色
实操:
from PIL import Image, ImageDraw, ImageFont
if __name__ == '__main__':
img = Image.new(mode="RGB", size=(400, 200), color="white")
我们应该如何查看生成的那张图片呢?使用show方法查看!
img.show()
因为我们定义这个画布的颜色是白色,所以使用show方法看不出效果,如需查看,将color改成其他颜色即可。
点击这里可查看常用的RGB颜色对照表。
使用画笔
画笔可牛了,在上面的验证码中,点,线,曲线,文字都是画笔生成的。因此在本次案例中,我们讲解画笔中的几个图案,点,直线,圆以及文字。
讲图案前,我们得有一个画笔。使用ImageDraw的Draw方法创建画笔,指定在哪张图片上使用(Image对象)以及模式,ImageDraw需要导入才能使用。
pen = ImageDraw.Draw(img, mode="RGB")
点
点(point)有两个参数:位置和颜色。实际上就是将某个像素改成指定颜色。这里位置指的是相对于图片左上角的xy坐标。 这里点的颜色不再是用color声明,取而代之的是fill,表示填充。使用时坐标在前,颜色在后,可以不写参数名。
pen.point((200, 100), fill="black")
因为要生成图片验证码,因此,点的位置和颜色都应该是随机的,所以我们需要使用random.randint函数。一般的图片验证码中的点有两种形式:一种是很多不同颜色的点随机分布在图片的各个位置,另一种是为图片上的每一个点都设置上随机的颜色。很显然,第二种效率更低,我们选择第一种。
另外,在后续的绘图中,都要涉及随机点坐标,如果我们每次绘图都得用randint生成随机x值,生成随机y值的话非常不方便,所以我们可以定义一个方法,每次调用该方法直接返回一个随机点坐标即可。
注意: 坐标的第一个值是在横轴方向(也就是图形的长),第二个值是在纵轴方向(也就是图形的高),随机生成的横轴坐标不能大于图形的最大长度,纵轴坐标不能大于图形的最大高度。
from random import randint
def randomPoint():
return randint(0, img.width), randint(0, img.height)
相应的,简化生成随机点坐标步骤,我们也可以简化生成随机颜色的步骤,因为后续直线,圆,文字同样要用到。不过这里的颜色更有讲究了,要更好的生成随机颜色,我们不能使用颜色名称来定义颜色了,应该使用RGB值来定义;除此之外,图案类型的不同,需要的颜色范围也不同。验证码颜色应该要深一点,点的颜色要浅一点。 如果点的颜色与验证码的颜色差不多,那么我们是看不到验证码的。
因此在定义函数时可以使用默认参数,特殊情况将特殊值传入即可,否则默认在0~255之间选取。
def randomColor(start=0, end=255):
return randint(start, end), randint(start, end), randint(start, end)
RGB:R表示红色,G表示绿色,B表示蓝色,每一元素取值范围都在0~255之间,值越低颜色深,值越高颜色越浅。
办理完上面的手续之后就是要生成点了,那我们该生成多少个点好呢?这就看个人喜好了,没有硬性要求。可以看看上面的三个验证码,不同的点数带来的视觉效果会不同,从左往右点数一直减少。下面的案例中我取的点数是总像素数 / 8,也就是图片长度 x 图片宽度 // 8 。
for i in range(img.width * img.height // 8):
pen.point(randomPoint(), randomColor(150))
因为点是图片的背景,所以颜色一定不要太深,RGB值越高,颜色越浅,所以我选取的范围是150~255之间。
生成点之后的图片是这样的。
直线
直线(line)有四个参数,我们只讲前三个参数。
- xy:第一个参数是元组形式的起始坐标与终点坐标。(SX,SY,FX,FY)或 ((SX,SY),(FX,FY))
- fill:第二个参数是直线的颜色。
- width:第三个参数是直线的粗细。
现在就来试试吧!
pen.line(((100, 100), (120, 180)), fill="blue", width=3)
表示起始坐标(100,100)与终点坐标(120,180)连线,颜色为蓝色,宽度为3的一条直线。
已经知道了如何生成直线,那么要随机生成直线也不成问题了,前面我们已经定义了随机生成坐标的方法,但是在这里来看会不会还是有点麻烦呢?因为要写起始坐标和终点坐标,用前面的方法的话我们就需要使用两次才能确定一条直线。那我们能不能在定义一个方法直接生成一个起始坐标和终点坐标呢?显然是可以的。在这个新生成的方法中调用两次之前的生成随机点坐标方法即可。
def randomPoints():
return randomPoint(), randomPoint()
有了他我们就能更好办事了,因为在后面的圆中同样需要确定起点和终点坐标。
接下来的问题就是,在图形验证码中我们该生成多少条直线好呢?这个也是看个人,我的话是选择随机生成10 ~ 15条直线,因为直线的起始坐标与终点坐标都是随机的,有长有短,短的话甚至看不见。因此我觉得10 ~ 15挺合适的。
for i in range(randint(10, 16)):
pen.line(randomPoints(), fill=randomColor(), width=randint(1, 3))
直线的粗细同样能够指定,我这里定义粗细是在1~3之间,直线的颜色就是用默认的就好了。
现在我们看一下效果。
好像这次直线有点多,问题不大,可以根据需求减少一些。
圆
圆(arc)有四个参数:
- xy:圆同样需要有起始坐标与终点坐标。不同的是圆是在 以一个坐标为左上角,另一个坐标为右下角的四边形中。 而且可以生成椭圆也能生成正圆。
- start:圆的起始角度,顺时针旋转。
- end:圆的终点角度。
- fill:圆边框的颜色
- width:圆边框的粗细
单看这几个参数可能还不明白怎么用。实践才是硬道理!
首先我们应该要知道两点的具体位置,但如果画点的话我们很难看清,所以可以先用线将两点连起来,接着我们就能看到直线始终位置,就能够画圆了。
看完这三张图,你是否有些启发呢?除了修改坐标值外,你也可以试试修改角度噢!
正圆样例:
pen.line((50, 50, 100, 100), "red")
pen.arc((50, 50, 100, 100), 0, 360, "red")
要生成随机圆我们可以生成随机坐标,随机颜色,随机角度以及随机粗细。坐标颜色我们都已经定义好了,可以直接调用,角度的话不能太大(我定义的范围是0~180之间),不然看起来有点奇怪,圆粗细的话我这里就不定义了,需要的话可以自行定义。接着的问题就是该生成多少圆合适?不用考虑这么多,直接丢进上面直线的循环就完事!
pen.arc(randomPoints(), 0, randint(0, 180), fill=randomColor())
丢没丢进去呢?
至此,我们图案就告一段落了,看一下现在能够生成怎样的图片。
验证码
内容
一张图片验证码的精髓莫过于是里面的验证码,验证码的内容同样是由画笔使用text()方法画出来的。他的参数有很多,我就挑一些常用的讲讲。
- xy:内容左上角的起始坐标(一个坐标点)
- text:文本内容
- fill:字体颜色
- font:字体样式
…
我们根据以上参数,在原有图案的基础上写点文字看看有什么效果。
pen.text((100, 100), "abcd", "red")
字体
发现有什么问题?内容没有居中;字体太小了。前者容易解决,调一下位置就好了,可字体如何解决呢?text中没有size的参数,似乎没有办法调整字体大小?这里要引入另外一个概念——字体。ImageFont模块能够很好解决与文字相关的问题,它下面的truetype方法能帮助我们个性化文字,了解一下他部分常用参数吧:
- font:指定要使用的字体。(写入ttf格式字体的存放路径)
- size:指定字体大小。
- encoding:指定字体的编码格式。默认为Unicode.
像开头第一张验证码我就用了比较特别的字体,你也去试试呗。
爱给网有超多字体,而且还是免费!猛戳这里进入~
我们回顾一下现在讲了pillow下面的那些模块?有生成图片对象的Image模块,有生成画笔的ImageDraw模块,刚又学了生成字体对象的ImageFont模块。
我选了一个叫hanshand的字体,并将其放在与py文件的同级目录上,这样的话直接输入名字就能调用了。注意: 如果你下载了英文字体,他是不支持中文的,输入中文会显示不出来,这样的话你下载中文字体即可。
myfont = ImageFont.truetype("hanshand.ttf", size=50)
换上字体并调整适当位置和大小就能正常显示了! 每种字体占的位置都不太一样,位置与大小都需要自行调整。
pen.text((100, 75), "abcdeqw", "red", font=myfont)
位置调整
到这一步之后会不会感觉还是差点啥?没错,太紧凑了,不够美观。我们可以根据验证码长度来填充适当的位置,比如说:如果要生成一个长度为5的图片验证码,我们可以把一张图片分成七份,左右两份不显示内容,在中间五份中各显示一个字符,这样他就能居中排开了!
首先定义一个变量用来确定验证码长度,我这里的值为5(可自行调整),然后用总长度除以验证码长度加2得出每部分长度。最后从第二部分开始依次填入验证码即可。
total = 5
part = img.width // (total + 2)
生成随机验证码
全篇倒数第二个问题。如何生成随机验证码?最常见的图片验证码莫过于字母,数字亦或者字母和数字混合。我们就了解一下字母和数字混合是如何实现的。第一种思路是先生成所有字母和数字,然后每次遍历在其中选取一个;另外一种思路是直接在每次遍历中随机生成一个字母和数字。相较而言后者效率高点,我们就选后者。随机生成一个字母的话可以利用chr函数将整数转换成ASCII码对应的字母。接着使用random的choice方法即可随机选取一个字母或数字。(记得导入choice模块)
与chr函数对应的是ord函数,可以得到字符对应的ASCII码。
total = 5
part = img.width // (total + 2)
pos = 0
for i in range(total):
pos += part
r = choice((chr(randint(65, 90)), str(randint(0, 9))))
pen.text((pos, 3 * img.height // 8), text=r, fill=randomColor(30, 200), font=myfont)
这里有几个需要注意的地方:choice是在序列中选择一个元素,所以我们要用括号将生成的一个字母和一个数字括起来将其变为元组序列;文本内容应该是字符串类型,所以生成随机数字时要将其转换成字符串类型;文本内容颜色不宜过浅。
此时效果如下图所示:
匹对验证码
最后一个问题,要不思考一下还缺点啥?现在我们虽然已经生成显示了验证码,但是我们还不知道验证码是多少,实际应用中应当知道验证码的具体值才能进行校验。因此在生成验证码的同时我们记录一下每个字符,在原有的基础上添加如下三行代码即可。 当然记录验证码具体值也可以直接使用字符串相加。
保存图片
走到这里任务已经算完成了,就还差最最最最最后一步——保存图片。直接上代码!
with open(r"C:\Users\17591\Desktop\t.png", "wb") as fp:
img.save(fp, format="png")
两个需要注意的地方:保存媒体文件需要使用wb形式进行保存——表示以二进制格式覆盖写入文件。另外图片需要保存为png格式。
总结
本篇我们学会了pillow画笔的基本使用,更重要的是能够理解各部分功能的衔接以及内部逻辑如何实现。希望大家学完本篇文章能够有一点点收获收获吧!
我是WA,一直在努力,希望七月份能得到一个心仪的python web offer,接下来的日子与你一起加油!
完整代码
from random import randint, choice
from PIL import Image, ImageDraw, ImageFont
def randomPoint():
return randint(0, img.width), randint(0, img.height)
def randomColor(start=0, end=255):
return randint(start, end), randint(start, end), randint(start, end)
def randomPoints():
return randomPoint(), randomPoint()
if __name__ == '__main__':
img = Image.new(mode="RGB", size=(400, 200), color="white")
pen = ImageDraw.Draw(img, mode="RGB")
pen.point((200, 100), fill="black")
for i in range(img.width * img.height // 8):
pen.point(randomPoint(), randomColor(150))
for i in range(randint(10, 16)):
pen.line(randomPoints(), fill=randomColor(), width=randint(1, 3))
pen.arc(randomPoints(), 0, randint(0, 180), fill=randomColor())
myfont = ImageFont.truetype("hanshand.ttf", size=50)
total = 5
part = img.width // (total + 2)
pos = 0
res = []
for i in range(total):
pos += part
r = choice((chr(randint(65, 90)), str(randint(0, 9))))
res.append(r)
pen.text((pos, 3 * img.height // 8), text=r, fill=randomColor(30, 200), font=myfont)
res = ''.join(res)
img.show()
print(res)
with open(r"C:\Users\17591\Desktop\t.png", "wb") as fp:
img.save(fp, format="png")
注: 字体过小可自行调大。
|