#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 生成 640x360 黑白测试图, 供 HDMI 源端工具的自定义栏位使用 ================================================================ 输出到 ../assets/(仓库资产目录, 发布打包时随 zip 分发); 预置栏位配置 release/pattern_slots.json 指向前 3 张, 其余两张可用 "⚙" 换上。 图案清单(均为 640x360 黑白, 1:1 投屏无缩放): bw_star.png 西门子星: 60 楔放射星, 看极限分辨率与中心偏移 bw_sweep.png 频率扫描条纹: 周期 24px 渐减到 3px(上竖线/下横线) bw_checker_sweep.png 渐变棋盘: 格子尺寸 2px 递增到 32px 共 9 段 bw_zoneplate.png 同心波带片: 环距向中心递减, 看各向混叠 bw_testcard.png 综合测试卡: 灰阶/刻度/细棋盘/粗细线/标记 用法: python gen_bw_patterns.py """ import math import os from PIL import Image, ImageDraw W, H = 640, 360 SRC = os.path.dirname(os.path.abspath(__file__)) OUT = os.path.join(os.path.dirname(SRC), 'assets') def save(img, name): os.makedirs(OUT, exist_ok=True) path = os.path.join(OUT, name) img.save(path) print(f'写出 {path}') def gen_star(): """西门子星: 60 个黑白交替楔形, 边缘楔宽约 17px, 向中心无限加密""" img = Image.new('L', (W, H), 255) d = ImageDraw.Draw(img) cx, cy, R = W // 2, H // 2, 165 n = 60 for i in range(0, n, 2): # 黑楔在偶数位, 底为白 d.pieslice([cx - R, cy - R, cx + R, cy + R], i * 360.0 / n, (i + 1) * 360.0 / n, fill=0) d.ellipse([cx - R - 6, cy - R - 6, cx + R + 6, cy + R + 6], outline=0, width=3) d.rectangle([0, 0, W - 1, H - 1], outline=0, width=2) save(img, 'bw_star.png') def gen_sweep(): """频率扫描条纹: 左到右黑白条纹周期 24px 渐减到 3px; 上半竖线, 下半横线""" img = Image.new('L', (W, H), 255) px = img.load() # 上半: 竖条纹 ph = 0.0 for x in range(W): t = x / (W - 1) ph += 1.0 / (24 - 21 * t) if (ph % 1.0) < 0.5: for y in range(H // 2): px[x, y] = 0 # 下半: 横条纹 ph = 0.0 for y in range(H // 2, H): t = (y - H // 2) / (H // 2 - 1) ph += 1.0 / (24 - 21 * t) if (ph % 1.0) < 0.5: for x in range(W): px[x, y] = 0 d = ImageDraw.Draw(img) d.line([0, H // 2, W, H // 2], fill=0, width=2) d.rectangle([0, 0, W - 1, H - 1], outline=0, width=2) d.text((8, 6), 'H sweep 24px->3px', fill=0) d.text((8, H // 2 + 6), 'V sweep 24px->3px', fill=0) save(img, 'bw_sweep.png') def gen_checker_sweep(): """渐变棋盘: 格子尺寸从 2px 逐段加倍到 32px, 看多大格子开始可分辨""" img = Image.new('L', (W, H), 0) d = ImageDraw.Draw(img) sizes = [2, 3, 4, 6, 8, 12, 16, 24, 32] seg = W / len(sizes) for i, s in enumerate(sizes): x0, x1 = int(round(i * seg)), int(round((i + 1) * seg)) for yy in range(0, H, s): for xx in range(x0, x1, s): if ((xx // s) + (yy // s)) % 2 == 0: d.rectangle([xx, yy, min(xx + s, x1) - 1, min(yy + s, H) - 1], fill=255) d.line([x1, 0, x1, H], fill=255, width=2) d.text((4, 4), 'checker 2..32px', fill=255) save(img, 'bw_checker_sweep.png') def gen_zoneplate(): """同心波带片: 环距中心处约 31px、边缘约 3.5px, 观察各方向混叠""" img = Image.new('L', (W, H), 255) px = img.load() cx, cy, k = W / 2, H / 2, 0.0008 for y in range(H): dy = y - cy for x in range(W): dx = x - cx if math.cos(k * (dx * dx + dy * dy)) > 0: px[x, y] = 0 d = ImageDraw.Draw(img) d.rectangle([0, 0, W - 1, H - 1], outline=0, width=2) save(img, 'bw_zoneplate.png') def gen_testcard(): """综合测试卡: 灰阶条 + 刻度 + 细棋盘 + 粗细线 + 角标""" img = Image.new('L', (W, H), 0) d = ImageDraw.Draw(img) # 外框与 40px 刻度(边缘短刻线) d.rectangle([0, 0, W - 1, H - 1], outline=255, width=2) for x in range(40, W, 40): d.line([x, 0, x, 10], fill=255, width=1) d.line([x, H, x, H - 10], fill=255, width=1) for y in range(40, H, 40): d.line([0, y, 10, y], fill=255, width=1) d.line([W, y, W - 10, y], fill=255, width=1) # 顶部 16 级灰阶条 gw = W // 16 for i in range(16): v = round(i * 255 / 15) d.rectangle([i * gw, 20, (i + 1) * gw - 1, 44], fill=v, outline=255) # 左下: 1px 棋盘块 (640/4=160 宽 x 72 高) bx, by, bs = 16, H - 96, 2 for yy in range(by, by + 72, bs): for xx in range(bx, bx + 160, bs): if ((xx // bs) + (yy // bs)) % 2 == 0: d.rectangle([xx, yy, xx + bs - 1, yy + bs - 1], fill=255) d.text((bx, by - 14), '2px checker', fill=255) # 右下: 1/2/4px 白线组(横线), 看线条再现 lx = W - 210 for row, lw in enumerate((1, 2, 4)): y0 = H - 100 + row * 26 for off in range(0, 13, lw * 2): d.line([lx, y0 + off, lx + 190, y0 + off], fill=255, width=lw) d.text((lx, H - 114), '1/2/4px lines', fill=255) # 中心十字(1px) cx, cy = W // 2, H // 2 d.line([cx, 60, cx, H - 60], fill=255, width=1) d.line([W // 4, cy, W - W // 4, cy], fill=255, width=1) # 四角 L 标记 m, L = 16, 28 for sx, sy in ((m, m), (W - 1 - m, m), (m, H - 1 - m), (W - 1 - m, H - 1 - m)): dx = -1 if sx > cx else 1 dy = -1 if sy > cy else 1 d.line([sx, sy, sx + dx * L, sy], fill=255, width=2) d.line([sx, sy, sx, sy + dy * L], fill=255, width=2) d.text((W // 2 - 60, 52), '640x360 TEST', fill=255) save(img, 'bw_testcard.png') if __name__ == '__main__': gen_star() gen_sweep() gen_checker_sweep() gen_zoneplate() gen_testcard()