From e8965497d42e3372474a4063235beeee116d05c4 Mon Sep 17 00:00:00 2001 From: simon987 Date: Sun, 3 Feb 2019 08:47:27 -0500 Subject: [PATCH] Fix captcha part 1: more human readable (but less cool) --- captcha.py | 196 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 160 insertions(+), 36 deletions(-) diff --git a/captcha.py b/captcha.py index 9f1c8ae..f2227a4 100644 --- a/captcha.py +++ b/captcha.py @@ -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__":