aboutsummaryrefslogtreecommitdiffstats
path: root/jm2l/captcha.py
blob: 65ab5db905aac06df64a526da240678989a26ac9 (plain)
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# Stolen from tgcaptcha/plugins
# http://code.google.com/p/tgcaptcha/source/browse/trunk/tgcaptcha/plugins/image/vanasco_dowty/captcha.py

import random
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import StringIO
import math
from pyramid.view import view_config
from .words import TabMots
from pyramid.response import Response

class Captcha_Img(object):
    def __init__( self, width, height):
        self.width = width
        self.height = height
        self._layers = [
            _PyCaptcha_SineWarp(amplitudeRange = (4, 8) , periodRange=(0.65,0.73) ),
        ]
        
    def getImg(self):
        """Get a PIL image representing this CAPTCHA test, creating it if necessary"""
        if not self._image:
            self._image = self.render()
        return self._image

    def render(self):
        """Render this CAPTCHA, returning a PIL image"""
        size = (self.width,self.height)
        #img = Image.new("RGB", size )
        img = self._image
        for layer in self._layers:
            img = layer.render( img ) or img
        self._image = img
        return self._image

class _PyCaptcha_WarpBase(object):
    """Abstract base class for image warping. Subclasses define a
       function that maps points in the output image to points in the input image.
       This warping engine runs a grid of points through this transform and uses
       PIL's mesh transform to warp the image.
       """
    filtering = Image.BILINEAR
    resolution = 40

    def get_transform(self, image):
        """Return a transformation function, subclasses should override this"""
        return lambda x, y: (x, y)

    def render(self, image):
        r = self.resolution
        xPoints = image.size[0] / r + 2
        yPoints = image.size[1] / r + 2
        f = self.get_transform(image)

        # Create a list of arrays with transformed points
        xRows = []
        yRows = []
        for j in xrange(yPoints):
            xRow = []
            yRow = []
            for i in xrange(xPoints):
                x, y = f(i*r, j*r)

                # Clamp the edges so we don't get black undefined areas
                x = max(0, min(image.size[0]-1, x))
                y = max(0, min(image.size[1]-1, y))

                xRow.append(x)
                yRow.append(y)
            xRows.append(xRow)
            yRows.append(yRow)

        # Create the mesh list, with a transformation for
        # each square between points on the grid
        mesh = []
        for j in xrange(yPoints-1):
            for i in xrange(xPoints-1):
                mesh.append((
                    # Destination rectangle
                    (i*r, j*r,
                     (i+1)*r, (j+1)*r),
                    # Source quadrilateral
                    (xRows[j  ][i  ], yRows[j  ][i  ],
                     xRows[j+1][i  ], yRows[j+1][i  ],
                     xRows[j+1][i+1], yRows[j+1][i+1],
                     xRows[j  ][i+1], yRows[j  ][i+1]),
                    ))

        return image.transform(image.size, Image.MESH, mesh, self.filtering)

class _PyCaptcha_SineWarp(_PyCaptcha_WarpBase):
    """Warp the image using a random composition of sine waves"""

    def __init__(self,
                 amplitudeRange = (1,1),#(2, 6),
                 periodRange    = (1,1)#(0.65, 0.73),
                 ):
        self.amplitude = random.uniform(*amplitudeRange)
        self.period = random.uniform(*periodRange)
        self.offset = (random.uniform(0, math.pi * 2 / self.period),
                       random.uniform(0, math.pi * 2 / self.period))
                       
    def get_transform(self, image):
        return (lambda x, y,
                a = self.amplitude,
                p = self.period,
                o = self.offset:
                (math.sin( (y+o[0])*p )*a + x,
                 math.sin( (x+o[1])*p )*a + y))

@view_config(route_name='captcha')
def DoCaptcha(request):
    ImgSize = (230,100)
    WorkImg = Image.new( 'RGBA', ImgSize, (255, 255, 255, 0) )
    Xmax, Ymax = WorkImg.size
    # Write something on it
    draw = ImageDraw.Draw(WorkImg)

    # use a truetype font
    #font = ImageFont.truetype("/var/lib/defoma/gs.d/dirs/fonts/LiberationMono-Regular.ttf", 40)
    # use it
    font = ImageFont.truetype("jm2l/static/fonts/LiberationMono-Regular.ttf",40)
    # Re-position
    # Choose a word for captcha
    text = random.choice(TabMots)
    Xt, Yt = font.getsize(text)
    OrX, OrY = (ImgSize[0]-Xt)/2, (ImgSize[1]-Yt)/2
    draw.text((OrX, OrY), text, font=font, fill="#000000")
    # Apply a Blur
    # WorkImg=WorkImg.filter(ImageFilter.BLUR)
    # Apply a DETAIL
    WorkImg=WorkImg.filter(ImageFilter.DETAIL)
    # randomize parameters for perspective
    ax, ay = (random.uniform(0.9,1.2) , random.uniform(0.9,1.2)) 
    tx, ty = (random.uniform(0,0.0003),random.uniform(0,0.0003))
    bx, by = (random.uniform(0.5,0.8),random.uniform(0,0.2))
    # Apply perspective to Captcha
    WorkImg= WorkImg.transform(ImgSize, Image.PERSPECTIVE, (ax, bx, -25, by, ay, -10, tx, ty))
    # Apply SinWarp to Captcha
    tr = Captcha_Img(Xmax, Ymax)
    tr._image = WorkImg
    WorkImg = tr.render()
    # Apply a Smooth on it
    WorkImg=WorkImg.filter(random.choice([ImageFilter.SMOOTH, ImageFilter.SMOOTH_MORE]))
    # Save Result
    request.session['Captcha'] = text
    #session.save()
    ImgHandle = StringIO.StringIO()
    WorkImg.save(ImgHandle,'png')
    ImgHandle.seek(0)
    return Response(app_iter=ImgHandle, content_type = 'image/png')