#!/usr/bin/env python3 """复刻 PHPWind 9.x PwGDCode 渲染逻辑,生成代表性验证码。 依据 alibaba/phpwind: upload/src/library/utility/verifycode/PwGDCode.php + PwBaseCode.php """ import math import random import sys import numpy as np from PIL import Image, ImageDraw, ImageFont W, H = 150, 60 CHARSET = "1234567890" # PwBaseCode verifyType=1 FONT = "/System/Library/Fonts/Supplemental/Arial.ttf" def rand_color(): return (random.randint(0, 255), random.randint(0, 120), random.randint(0, 255)) def rand_noise_color(): return (random.randint(50, 255), random.randint(50, 200), random.randint(25, 200)) def gen(out_path, code=None): if code is None: code = "".join(random.choice(CHARSET) for _ in range(4)) # 背景:白底 (PwGDCode isRandBackground=false -> 255) img = Image.new("RGB", (W, H), (255, 255, 255)) d = ImageDraw.Draw(img) # 逐字符绘制:随机字号14-20、随机角度-20~10、随机色 codeX = (W - 20) / 4 codeY = H // 2 + random.randint(5, 10) for i, ch in enumerate(code): size = random.randint(14, 20) angle = random.randint(-20, 10) font = ImageFont.truetype(FONT, size) # 在更大的画布上画字再旋转,避免裁剪 tmp = Image.new("RGBA", (40, 40), (0, 0, 0, 0)) td = ImageDraw.Draw(tmp) td.text((2, 2), ch, font=font, fill=rand_color() + (255,)) tmp = tmp.rotate(angle, expand=True, fillcolor=(0, 0, 0, 0)) x = int(codeX * i + 10) y = int(codeY - tmp.height / 2) img.paste(tmp.convert("RGB"), (x, y), tmp.split()[3]) # 噪声:随机 线/点/弧 (PwGDCode _setRandGraph) d = ImageDraw.Draw(img) mode = random.randint(1, 3) if mode == 1: # 30-40 条随机线 for _ in range(random.randint(30, 40)): x1, y1 = random.randint(0, W), random.randint(0, H) d.line([(x1, y1), (random.randint(x1 - 10, x1 + 5), random.randint(y1 + 5, y1 + 20))], fill=rand_noise_color(), width=1) elif mode == 2: # 600-800 随机点 for _ in range(random.randint(600, 800)): d.point((random.randint(0, W - 1), random.randint(0, H - 1)), fill=rand_noise_color()) else: # 5-10 条弧 for _ in range(random.randint(5, 10)): d.arc([random.randint(0, W), random.randint(10, H), random.randint(10, W), random.randint(H, H * 2)], random.randint(0, 90), random.randint(0, 90), fill=rand_noise_color()) # 正弦扭曲:x += sin(y/height*2π-0.6)*5 arr = np.array(img) out = np.zeros_like(arr) for y in range(H): shift = int(math.sin(y / H * 2 * math.pi - 0.6) * 5) for x in range(W): sx = x + shift if 0 <= sx < W: out[y, x] = arr[y, sx] Image.fromarray(out).save(out_path) return code if __name__ == "__main__": import os outdir = sys.argv[1] if len(sys.argv) > 1 else "/tmp/sp_pw9" os.makedirs(outdir, exist_ok=True) for i in range(6): code = gen(f"{outdir}/pw9_{i}.png") print(f"pw9_{i}.png code={code}")