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 random
import string
import numpy
import pylab
from PIL import Image, ImageDraw, ImageFont 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 from flask import request, session
import config import config
from common import logger
SIZE = (60, 20)
with open("words.txt") as f:
WORDS = f.read().splitlines(keepends=False)
def get_code(): def get_code():
@ -51,41 +41,175 @@ def verify():
return False 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(): 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): path = get_path(text)
os.remove(path)
image = Image.new('L', SIZE, 255) w = size[0]*c
image_draw = ImageDraw.Draw(image) h = size[1]*c
font = ImageFont.truetype("static/Hack-Regular.ttf", 12)
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])) # noise
z = 1 - numpy.asarray(image) / 255 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() # background lines
ax = axes3d.Axes3D(fig) draw = ImageDraw.Draw(img)
ax.plot_wireframe(x, -y, z, rstride=1, cstride=1)
ax.set_zlim((0, 20))
ax.set_axis_off()
pylab.close(fig)
buf = io.BytesIO() horizontal_lines(draw, c, cfg["lines"]["back_thin"], cfg["lines"]["back_positions"], color[0])
fig.savefig(buf, dpi=150) horizontal_lines(draw, c, cfg["lines"]["back_thick"], cfg["lines"]["back_positions"], color[0])
buf.seek(0)
image.close()
with WImage(blob=buf.read()) as img: # text
img.trim() ctx = cfg["text"]["font"]
img.save(filename=path) 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__": if __name__ == "__main__":