mirror of
https://github.com/lepture/captcha.git
synced 2026-03-13 09:43:22 +08:00
Add ImageCaptcha
This commit is contained in:
135
captcha/image.py
135
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)
|
||||
|
||||
Reference in New Issue
Block a user