Source

wheezy.captcha / src / wheezy / captcha / image.py

"""
"""

import random
import Image
import ImageFilter

from ImageColor import getrgb
from ImageDraw import Draw
from ImageFont import truetype


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)
        color = lambda: 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)
        color = lambda: 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)
        color = lambda: 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, (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)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.