-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathmake_captcha.py
executable file
·110 lines (82 loc) · 3.39 KB
/
make_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
# -*- coding: utf-8 -*-
#
# Draw a Captchas ... not meant to be hard, but easier to replace out, and challenging
# to read image itself..
#
import random, os, io
from PIL import Image, ImageDraw, ImageFont
# Avoid similar-looking letters/numbers.
TOKEN_CHARS = 'abcdefghkmnpqrstuvwxyz23456789'
class CaptchaMaker:
size = (256, 64) # limited by iphone case. esp. when entering value
def __init__(self, seed=None):
self.rng = random.Random(seed)
def draw(self, token):
# draw some crazy captcha for indicated token and return as a
# tuple: extension, raw_data
raise NotImplementedError
def get_font(self, size=40, which='nova'):
fn = { 'ransom': 'static/fonts/ransom-note.ttf',
'nova': 'static/fonts/proximanova-semibold.ttf'
}
return ImageFont.truetype(fn[which], size)
class RansomCaptcha(CaptchaMaker):
#
# Just the simple ransom note that most people expect today.
#
def draw(self, token, foreground='#000', seed=None):
fn = self.get_font(size=40, which='ransom')
w,h = self.size
_,_,dx,dy = fn.getbbox('W')
im = Image.new('RGBA', self.size)
dr = ImageDraw.Draw(im)
# some of the lower-case letters are confusing, so replace them
remap = dict(x = 'X', s='S', a='A', g='G', q='Q')
x = 10
ix = (w - (x*2)) * (len(token)-2) / dx
for ch in token:
ch = remap.get(ch, ch)
y = self.rng.randint(-5, h-dy+5)
dr.text( (x, y), ch, fill=foreground, font=fn)
x += ix
data = io.BytesIO()
im.save(data, format='png')
return 'png', data.getvalue()
class MegaGifCaptcha(CaptchaMaker):
# These are fun, but too large to be practical? Keep in toolbox for later.
def draw(self, token, foreground='#fff', background='white'):
token = ' '.join(token.upper())
fn = self.get_font(size=30, which='nova')
w,h = self.size
_,_,dx,dy = fn.getbbox('W')
randint = self.rng.randint
sample = self.rng.sample
actual = []
pass_thru = set()
frames = []
ans_y = self.rng.randint(3, h-dy-3)
ans_w = fn.getbbox(token)[2]
count = 15
for fr_num in range(count):
im = Image.new('P', self.size, background)
dr = ImageDraw.Draw(im)
# give them noise chars, except they are mostly correct so that
# the order is not so clear at all. don't want to just be able to
# pick the most common chars observed
charset = set(token)
while len(charset) < len(token) + 4:
charset.add(sample(TOKEN_CHARS, 1)[0])
for k in range(int(w * 1.5/dx)):
#ch = ''.join(self.rng.sample(TOKEN_CHARS, 1)).upper()
ch = ''.join(sample(list(charset), 1)).upper()
x = randint(-dx, w)
y = ans_y + randint(int(-dy*3/4), int(dy*3/4))
dr.text( (x,y), ch, fill=foreground, font=fn)
frames.append(im)
x = (w-ans_w)*fr_num / count
dr.text( (x, ans_y), token, fill=foreground, font=fn)
data = io.BytesIO()
frames[0].save(data, format='gif', save_all=True, loop=0,
append_images=frames + list(reversed(frames[1:-1])))
return 'gif', data.getvalue()
# EOF