mirror of https://github.com/tp4a/teleport
187 lines
5.4 KiB
Python
187 lines
5.4 KiB
Python
"""
|
|
"""
|
|
|
|
import random
|
|
|
|
from wheezy.captcha.comp import Draw
|
|
from wheezy.captcha.comp import Image
|
|
from wheezy.captcha.comp import ImageFilter
|
|
from wheezy.captcha.comp import getrgb
|
|
from wheezy.captcha.comp import truetype
|
|
from wheezy.captcha.comp import xrange
|
|
|
|
|
|
def captcha(drawings, width=200, height=75):
|
|
def render(text):
|
|
image = Image.new('RGB', (width, height), (255, 255, 255))
|
|
for drawing in drawings:
|
|
image = drawing(image, text)
|
|
assert image
|
|
return image
|
|
return render
|
|
|
|
|
|
# region: captcha drawers
|
|
|
|
def background(color='#EEEECC'):
|
|
color = getrgb(color)
|
|
|
|
def drawer(image, text):
|
|
Draw(image).rectangle([(0, 0), image.size], fill=color)
|
|
return image
|
|
return drawer
|
|
|
|
|
|
def smooth():
|
|
def drawer(image, text):
|
|
return image.filter(ImageFilter.SMOOTH)
|
|
return drawer
|
|
|
|
|
|
def curve(color='#5C87B2', width=4, number=6):
|
|
from wheezy.captcha.bezier import make_bezier
|
|
if not callable(color):
|
|
c = getrgb(color)
|
|
|
|
def color():
|
|
return c
|
|
|
|
def drawer(image, text):
|
|
dx, height = image.size
|
|
dx = dx / number
|
|
path = [(dx * i, random.randint(0, height))
|
|
for i in range(1, number)]
|
|
bcoefs = make_bezier(number - 1)
|
|
points = []
|
|
for coefs in bcoefs:
|
|
points.append(tuple(sum([coef * p for coef, p in zip(coefs, ps)])
|
|
for ps in zip(*path)))
|
|
draw = Draw(image)
|
|
draw.line(points, fill=color(), width=width)
|
|
return image
|
|
return drawer
|
|
|
|
|
|
def noise(number=50, color='#EEEECC', level=2):
|
|
if not callable(color):
|
|
c = getrgb(color)
|
|
|
|
def color():
|
|
return c
|
|
|
|
def drawer(image, text):
|
|
width, height = image.size
|
|
dx = width / 10
|
|
width = width - dx
|
|
dy = height / 10
|
|
height = height - dy
|
|
draw = Draw(image)
|
|
for i in xrange(number):
|
|
x = int(random.uniform(dx, width))
|
|
y = int(random.uniform(dy, height))
|
|
draw.line(((x, y), (x + level, y)), fill=color(), width=level)
|
|
return image
|
|
return drawer
|
|
|
|
|
|
def text(fonts, font_sizes=None, drawings=None, color='#5C87B2',
|
|
squeeze_factor=0.8):
|
|
fonts = tuple([truetype(name, size)
|
|
for name in fonts
|
|
for size in font_sizes or (65, 70, 75)])
|
|
if not callable(color):
|
|
c = getrgb(color)
|
|
|
|
def color():
|
|
return c
|
|
|
|
def drawer(image, text):
|
|
draw = Draw(image)
|
|
char_images = []
|
|
for c in text:
|
|
font = random.choice(fonts)
|
|
c_width, c_height = draw.textsize(c, font=font)
|
|
char_image = Image.new('RGB', (c_width, c_height), (0, 0, 0))
|
|
char_draw = Draw(char_image)
|
|
char_draw.text((0, 0), c, font=font, fill=color())
|
|
char_image = char_image.crop(char_image.getbbox())
|
|
for drawing in drawings:
|
|
char_image = drawing(char_image)
|
|
char_images.append(char_image)
|
|
width, height = image.size
|
|
offset = int((width - sum(int(i.size[0] * squeeze_factor)
|
|
for i in char_images[:-1]) -
|
|
char_images[-1].size[0]) / 2)
|
|
for char_image in char_images:
|
|
c_width, c_height = char_image.size
|
|
mask = char_image.convert('L').point(lambda i: i * 1.97)
|
|
image.paste(char_image,
|
|
(offset, int((height - c_height) / 2)),
|
|
mask)
|
|
offset += int(c_width * squeeze_factor)
|
|
return image
|
|
return drawer
|
|
|
|
|
|
# region: text drawers
|
|
|
|
def warp(dx_factor=0.27, dy_factor=0.21):
|
|
def drawer(image):
|
|
width, height = image.size
|
|
dx = width * dx_factor
|
|
dy = height * dy_factor
|
|
x1 = int(random.uniform(-dx, dx))
|
|
y1 = int(random.uniform(-dy, dy))
|
|
x2 = int(random.uniform(-dx, dx))
|
|
y2 = int(random.uniform(-dy, dy))
|
|
image2 = Image.new('RGB',
|
|
(width + abs(x1) + abs(x2),
|
|
height + abs(y1) + abs(y2)))
|
|
image2.paste(image, (abs(x1), abs(y1)))
|
|
width2, height2 = image2.size
|
|
return image2.transform(
|
|
(width, height), Image.QUAD,
|
|
(x1, y1,
|
|
-x1, height2 - y2,
|
|
width2 + x2, height2 + y2,
|
|
width2 - x2, -y1))
|
|
return drawer
|
|
|
|
|
|
def offset(dx_factor=0.1, dy_factor=0.2):
|
|
def drawer(image):
|
|
width, height = image.size
|
|
dx = int(random.random() * width * dx_factor)
|
|
dy = int(random.random() * height * dy_factor)
|
|
image2 = Image.new('RGB', (width + dx, height + dy))
|
|
image2.paste(image, (dx, dy))
|
|
return image2
|
|
return drawer
|
|
|
|
|
|
def rotate(angle=25):
|
|
def drawer(image):
|
|
return image.rotate(
|
|
random.uniform(-angle, angle), Image.BILINEAR, expand=1)
|
|
return drawer
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import string
|
|
captcha_image = captcha(drawings=[
|
|
background(),
|
|
text(fonts=[
|
|
'fonts/CourierNew-Bold.ttf',
|
|
'fonts/LiberationMono-Bold.ttf'],
|
|
drawings=[
|
|
warp(),
|
|
rotate(),
|
|
offset()
|
|
]),
|
|
curve(),
|
|
noise(),
|
|
smooth()
|
|
])
|
|
image = captcha_image(random.sample(string.uppercase + string.digits, 4))
|
|
image.save('sample.jpg', 'JPEG', quality=75)
|