-
Notifications
You must be signed in to change notification settings - Fork 4
/
captcha.py
119 lines (91 loc) · 2.99 KB
/
captcha.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#!/usr/bin/env python3
import re
import random
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
from PIL import ImageOps
from PIL import ImageFilter
CAPTCHA_CHARS = '345ACEFGHKLMNPRSTWXZacdefhkmnpswx'
CAPTCHA_REGEX = re.compile('[0-9A-z]{4}')
WIDTH = 180
HEIGHT = 75
ROTATE = 30
FONT = ImageFont.truetype('resources/Cantarell-Bold.otf', 36)
def gen_captcha():
'''Generate a captcha string
@return str
'''
return ''.join(random.choice(CAPTCHA_CHARS) for i in range(0, 4))
def gen_image(content):
'''Generate a PNG image of the captcha with text content `content`
@param str content
@return PIL.Image
'''
# ======================================================================
# Snippet modified from https://github.com/lepture/captcha
# LICENSE: New BSD License
image = Image.new('RGB', (WIDTH, HEIGHT))
draw = ImageDraw.Draw(image)
def draw_character(c, font=FONT):
w, h = draw.textsize(c, font=font)
dx = random.randint(0, 4)
dy = random.randint(0, 6)
im = Image.new('RGBA', (w + dx, h + dy))
ImageDraw.Draw(im).text((dx, dy), c, font=font)
# rotate
im = im.crop(im.getbbox())
im = im.rotate(
random.uniform(-ROTATE, ROTATE),
Image.BILINEAR,
expand=1
)
# warp
dx = w * random.uniform(0.1, 0.3)
dy = h * random.uniform(0.2, 0.3)
x1 = int(random.uniform(-dx, dx))
y1 = int(random.uniform(-dy, dy))
x2 = int(random.uniform(-dx, dx))
y2 = int(random.uniform(-dy, dy))
w2 = w + abs(x1) + abs(x2)
h2 = h + abs(y1) + abs(y2)
data = (
x1, y1,
-x1, h2 - y2,
w2 + x2, h2 + y2,
w2 - x2, -y1,
)
im = im.resize((w2, h2))
im = im.transform((w, h), Image.QUAD, data)
return im
images = []
for c in content:
images.append(draw_character(c))
text_width = sum([im.size[0] for im in images])
width = max(text_width, WIDTH)
image = image.resize((width, HEIGHT))
average = int(text_width / len(content))
rand = int(0.25 * average)
offset = int(average * 0.1) + 20
for im in images:
w, h = im.size
mask = im.convert('L').point(lambda i: i * 1.97)
image.paste(im, (offset, int((HEIGHT - h) / 2)), mask)
offset = offset + w + random.randint(-rand, 0) + 20
# ======================================================================
def rand_points():
return [
(random.uniform(0, WIDTH), 0),
(random.uniform(0, WIDTH), HEIGHT)
]
for i in range(0, 20):
draw.ellipse(rand_points(), outline='black')
for i in range(0, 5):
draw.line(
rand_points(),
fill = random.choice(['black', 'white']),
width = random.choice([2, 3])
)
image = ImageOps.invert(image)
image = image.filter(ImageFilter.SMOOTH)
return image