Add ImageCaptcha

This commit is contained in:
Hsiaoming Yang
2014-11-26 19:27:17 +08:00
parent 714d9f6572
commit 4758051836

View File

@@ -5,3 +5,138 @@
Generate Image CAPTCHAs, just the normal image CAPTCHAs you are using.
"""
import random
from PIL import Image
from PIL import ImageFilter
from PIL.ImageDraw import Draw
from PIL.ImageFont import truetype
from io import BytesIO
class ImageCaptcha(object):
def __init__(self, width=160, height=60, fonts=None):
self._width = width
self._height = height
self._fonts = fonts
self._truefonts = []
@property
def truefonts(self):
if self._truefonts:
return self._truefonts
self._truefonts = tuple([
truetype(n, s)
for n in self._fonts
for s in (46, 58, 68)
])
return self._truefonts
@staticmethod
def create_noise_curve(image, color):
w, h = image.size
x1 = random.randint(0, w / 5)
x2 = random.randint(w - w / 5, w)
y1 = random.randint(h / 5, h - h / 5)
y2 = random.randint(y1, h - h / 5)
points = [(x1, y1), (x2, y2)]
end = random.randint(160, 200)
start = random.randint(0, 20)
Draw(image).arc(points, start, end, fill=color)
return image
@staticmethod
def create_noise_dots(image, color, width=3, number=30):
draw = Draw(image)
w, h = image.size
while number:
x1 = random.randint(0, w)
y1 = random.randint(0, h)
draw.line(((x1, y1), (x1 - 1, y1 - 1)), fill=color, width=width)
number -= 1
return image
def create_captcha_image(self, chars, color, background):
image = Image.new('RGB', (self._width, self._height), background)
draw = Draw(image)
def _draw_character(c):
font = random.choice(self.truefonts)
w, h = draw.textsize(c, font=font)
dx = random.randint(0, 4)
dy = random.randint(0, 6)
im = Image.new('RGB', (w + dx, h + dy), background)
Draw(im).text((dx, dy), c, font=font, fill=color)
# rotate
im = im.crop(im.getbbox())
im = im.rotate(random.uniform(-30, 30), 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 chars:
images.append(_draw_character(c))
text_width = sum([im.size[0] for im in images])
width = max(text_width, self._width)
image = image.resize((width, self._height))
average = int(text_width / len(chars))
rand = int(0.15 * average)
offset = int(average * 0.1)
for im in images:
w, h = im.size
mask = im.convert('L').point(lambda i: i * 1.97)
image.paste(im, (offset, int((self._height - h) / 2)), mask)
offset = offset + w + random.randint(-rand, rand)
return image
def generate_image(self, chars):
background = random_color(238, 255)
color = random_color(0, 200, random.randint(220, 255))
im = self.create_captcha_image(chars, color, background)
self.create_noise_dots(im, color)
self.create_noise_curve(im, color)
im = im.filter(ImageFilter.SMOOTH)
return im
def generate(self, chars):
im = self.generate_image(chars)
out = BytesIO()
im.save(out, format='png')
out.seek(0)
return out
def write(self, chars, output):
im = self.generate_image(chars)
return im.save(output, format='png')
def random_color(start, end, opacity=None):
red = random.randint(start, end)
green = random.randint(start, end)
blue = random.randint(start, end)
if opacity is None:
return (red, green, blue)
return (red, green, blue, opacity)