From 475805183626f54967f428a7d105edd09ae6e80c Mon Sep 17 00:00:00 2001 From: Hsiaoming Yang Date: Wed, 26 Nov 2014 19:27:17 +0800 Subject: [PATCH] Add ImageCaptcha --- captcha/image.py | 135 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/captcha/image.py b/captcha/image.py index 2f37fb3..eee7e5c 100644 --- a/captcha/image.py +++ b/captcha/image.py @@ -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)