Fix captcha part 1: more human readable (but less cool)

This commit is contained in:
simon987 2019-02-03 08:47:27 -05:00
parent 32c1c861ad
commit e8965497d4

View File

@ -1,20 +1,10 @@
import os
import random
import string
import numpy
import pylab
from PIL import Image, ImageDraw, ImageFont
import mpl_toolkits.mplot3d.axes3d as axes3d
import io
from wand.image import Image as WImage
from flask import request, session
import config
from common import logger
SIZE = (60, 20)
with open("words.txt") as f:
WORDS = f.read().splitlines(keepends=False)
def get_code():
@ -51,41 +41,175 @@ def verify():
return False
cfg = {
"image": {
"size": (200, 72),
"supersampling": 2
},
"noise": {
"min": 100,
"max": 250
},
"colors": {
"green": [(1, 51, 1), (34, 204, 34)],
"yellow": [(67, 67, 1), (221, 221, 0)],
"cyan": [(17, 51, 85), (85, 187, 254)],
"magenta": [(51, 1, 51), (254, 0, 254)],
"red": [(67, 1, 1), (254, 68, 68)],
"orange": [(68, 51, 1), (255, 153, 0)]
},
"lines": {
"back_thin": {"n": 3, "w": 5},
"back_thick": {"n": 3, "w": 6},
"back_positions": [
{
"ax": (0, 10),
"ay": (0, 36),
"bx": (150, 200),
"by": (18, 50)
},
{
"ax": (0, 10),
"ay": (18, 50),
"bx": (150, 200),
"by": (0, 17)
}
],
"front_horizontal_thin": {"n": 2, "w": 3},
"front_horizontal_thick": {"n": 2, "w": 4},
"front_horizontal_positions": [
{
"ax": (0, 20),
"ay": (0, 34),
"bx": (150, 200),
"by": (18, 50)
},
{
"ax": (0, 20),
"ay": (18, 72),
"bx": (140, 200),
"by": (0, 36)
},
],
"front_vertical": {"n": 2, "w": 4},
"front_vertical_positions": {
"outside": 5,
"font_width": 13,
"ay": (0, 16),
"by": (54, 72)
}
},
"text": {
"font": {
"path": "static/Hack-Regular.ttf",
"size": 60,
"outline": [1, 2]
},
"letters": {
"3": {
"count": 3,
"x_min": 35,
"x_max": 50,
"y_min": -5,
"y_max": 8
},
"4": {
"count": 4,
"x_min": 20,
"x_max": 35,
"y_min": -5,
"y_max": 8
},
"5": {
"count": 5,
"x_min": 5,
"x_max": 20,
"y_min": -5,
"y_max": 8
}
}
}
}
size = cfg["image"]["size"]
c = cfg["image"]["supersampling"]
# Additional config
letter_count = "4"
def horizontal_lines(draw, c, line_par, line_pos, fill):
for _ in range(line_par["n"]):
pos = random.randrange(0, len(line_pos))
ax = random.randint(*line_pos[pos]["ax"])
ay = random.randint(*line_pos[pos]["ay"])
bx = random.randint(*line_pos[pos]["bx"])
by = random.randint(*line_pos[pos]["by"])
draw.line([(ax*c, ay*c), (bx*c, by*c)], width=line_par["w"]*c, fill=fill)
def make_captcha():
word = random.choice(WORDS)
path = get_path(word)
logger.info("generating CAPTCHA: " + word)
color_name, color = random.choice(list(cfg["colors"].items()))
text = ''.join(random.choices(string.ascii_uppercase + string.digits, k=cfg["text"]["letters"][letter_count]["count"]))
if os.path.exists(path):
os.remove(path)
path = get_path(text)
image = Image.new('L', SIZE, 255)
image_draw = ImageDraw.Draw(image)
font = ImageFont.truetype("static/Hack-Regular.ttf", 12)
w = size[0]*c
h = size[1]*c
image_draw.text((5, 3), word, font=font)
img = Image.new('RGB', (w, h))
pixels = img.load()
x, y = numpy.meshgrid(range(SIZE[0]), range(SIZE[1]))
z = 1 - numpy.asarray(image) / 255
# noise
for x in range(w):
for y in range(h):
rcol = random.randint(cfg["noise"]["min"], cfg["noise"]["max"])
pixels[x, y] = (rcol, rcol, rcol)
fig = pylab.figure()
ax = axes3d.Axes3D(fig)
ax.plot_wireframe(x, -y, z, rstride=1, cstride=1)
ax.set_zlim((0, 20))
ax.set_axis_off()
pylab.close(fig)
# background lines
draw = ImageDraw.Draw(img)
buf = io.BytesIO()
fig.savefig(buf, dpi=150)
buf.seek(0)
image.close()
horizontal_lines(draw, c, cfg["lines"]["back_thin"], cfg["lines"]["back_positions"], color[0])
horizontal_lines(draw, c, cfg["lines"]["back_thick"], cfg["lines"]["back_positions"], color[0])
with WImage(blob=buf.read()) as img:
img.trim()
img.save(filename=path)
# text
ctx = cfg["text"]["font"]
font = ImageFont.truetype(ctx["path"], ctx["size"]*c)
outline = random.choice(ctx["outline"])
return word
ctx = cfg["text"]["letters"][letter_count]
x = random.randint(ctx["x_min"], ctx["x_max"])
y = random.randint(ctx["y_min"], ctx["y_max"])
draw.text((x*c-outline*c, y*c-outline*c), text, color[0], font=font)
draw.text((x*c-outline*c, y*c), text, color[0], font=font)
draw.text((x*c-outline*c, y*c+outline*c), text, color[0], font=font)
draw.text((x*c, y*c-outline*c), text, color[0], font=font)
draw.text((x*c, y*c+outline*c), text, color[0], font=font)
draw.text((x*c+outline*c, y*c-outline*c), text, color[0], font=font)
draw.text((x*c+outline*c, y*c), text, color[0], font=font)
draw.text((x*c+outline*c, y*c+outline*c), text, color[0], font=font)
draw.text((x*c, y*c), text, color[1], font=font)
# foreground lines
horizontal_lines(draw, c, cfg["lines"]["front_horizontal_thin"], cfg["lines"]["front_horizontal_positions"], color[1])
horizontal_lines(draw, c, cfg["lines"]["front_horizontal_thick"], cfg["lines"]["front_horizontal_positions"], color[1])
# vertical lines
line_par = cfg["lines"]["front_vertical"]
line_pos = cfg["lines"]["front_vertical_positions"]
for _ in range(line_par["n"]):
ax = random.randint(x-line_pos["outside"], x+line_pos["outside"] + cfg["text"]["letters"][letter_count]["count"]*line_pos["font_width"])
bx = ax + random.randint(-line_pos["font_width"], line_pos["font_width"])
ay = random.randint(*line_pos["ay"])
by = random.randint(*line_pos["by"])
draw.line([(ax*c, ay*c), (bx*c, by*c)], width=line_par["w"]*c, fill=color[1])
img.thumbnail(cfg["image"]["size"], Image.ANTIALIAS)
img.save(path, "png")
return text
if __name__ == "__main__":