#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 测试图案全屏投屏窗口(tkinter): 纯色/棋盘格/网格线 + 自定义图片 1:1 居中 ================================================================ 投屏窗口为无边框黑底 Toplevel, ESC 或点击退出; 测试卡区域可选 640x360 / 800x600 / full 铺满(常量与图片加载配置在 config)。 """ from config import DEFAULT_AREA, HAS_PIL, Image, ImageTk, PAT_W, PAT_H PATTERN_NAMES = {'Red': '纯红', 'Green': '纯绿', 'Blue': '纯蓝', 'Checker': '黑白棋盘格', 'Grid': '网格线'} # 纯色测试值(位交换测试用, 与 assets/solid_*.png 对应): # 每通道 55/AA 互为镜像对(检测对), FF 全高基准 SOLID_ROWS = ( ('R', (('55', '#550000'), ('AA', '#AA0000'), ('FF', '#FF0000'))), ('G', (('55', '#005500'), ('AA', '#00AA00'), ('FF', '#00FF00'))), ('B', (('55', '#000055'), ('AA', '#0000AA'), ('FF', '#0000FF'))), ) def load_image_raw(path): """按原始像素加载图片(不缩放不裁剪; PIL 仅用于支持 JPG/BMP/WebP 等格式)""" import tkinter as tk if HAS_PIL: return ImageTk.PhotoImage(Image.open(path).convert('RGB')) return tk.PhotoImage(file=path) def _area_box(spec, w, h): """区域选项 -> (x0, y0, aw, ah): 'full' 铺满整个屏幕, 'WxH' 黑底居中""" if spec == 'full': return 0, 0, w, h try: aw, ah = (int(v) for v in spec.lower().split('x', 1)) except Exception: aw, ah = PAT_W, PAT_H return (w - aw) // 2, (h - ah) // 2, aw, ah def _open_fullscreen(parent, rect, auto_close=0): """创建覆盖 rect(x,y,w,h) 的无边框全屏黑底窗口, 返回 (top, cv) 只支持 ESC 退出: overrideredirect 窗口必须 focus_force 强取键盘焦点, 否则收不到按键(Windows 上该组合有效)。 """ import tkinter as tk rx, ry, rw, rh = rect top = tk.Toplevel(parent) top.overrideredirect(True) top.geometry(f'{rw}x{rh}+{rx}+{ry}') top.attributes('-topmost', True) top.configure(bg='black') cv = tk.Canvas(top, bg='black', highlightthickness=0, bd=0) cv.pack(fill='both', expand=True) def close(_evt=None): top.destroy() top.bind('', close) if auto_close > 0: top.after(auto_close * 1000, close) top.focus_force() return top, cv def show_pattern(parent, rect, kind, auto_close=0, area=DEFAULT_AREA): """在 rect 指定的屏幕上全屏出居中测试卡(无任何 OSD 修饰), 区域由 area 指定""" top, cv = _open_fullscreen(parent, rect, auto_close) def draw(_evt=None): cv.delete('all') w, h = max(cv.winfo_width(), rect[2]), max(cv.winfo_height(), rect[3]) x0, y0, aw, ah = _area_box(area, w, h) if kind.startswith('#'): # 纯色测试值: 直接填充指定颜色 cv.create_rectangle(x0, y0, x0 + aw, y0 + ah, fill=kind, outline='') elif kind == 'Red': cv.create_rectangle(x0, y0, x0 + aw, y0 + ah, fill='#FF0000', outline='') elif kind == 'Green': cv.create_rectangle(x0, y0, x0 + aw, y0 + ah, fill='#00FF00', outline='') elif kind == 'Blue': cv.create_rectangle(x0, y0, x0 + aw, y0 + ah, fill='#0000FF', outline='') elif kind == 'Checker': # 40px 黑白格, 格数随区域尺寸自适应(640x360 = 16x9 格) for gy in range((ah + 39) // 40): for gx in range((aw + 39) // 40): if (gx + gy) % 2 == 0: cv.create_rectangle(x0 + gx * 40, y0 + gy * 40, x0 + (gx + 1) * 40, y0 + (gy + 1) * 40, fill='#FFFFFF', outline='') elif kind == 'Grid': # 20px 白色网格线, 黑底, 无其它修饰 for k in range(aw // 20 + 1): cv.create_line(x0 + k * 20, y0, x0 + k * 20, y0 + ah, fill='#FFFFFF') for k in range(ah // 20 + 1): cv.create_line(x0, y0 + k * 20, x0 + aw, y0 + k * 20, fill='#FFFFFF') top.bind('', draw) top.after(50, draw) top.wait_window(top) def show_pattern_image(parent, rect, path, auto_close=0, area=DEFAULT_AREA): """全屏黑底, 图片 1:1 居中于 area 指定的测试卡区域: 小于区域 -> 真实像素居中, 四周黑边; 大于区域 -> 等效从中心截取(区域外用黑矩形遮挡)""" top, cv = _open_fullscreen(parent, rect, auto_close) def draw(_evt=None): cv.delete('all') w, h = max(cv.winfo_width(), rect[2]), max(cv.winfo_height(), rect[3]) x0, y0, aw, ah = _area_box(area, w, h) photo = load_image_raw(path) top._photo = photo # 挂在窗口上防止被垃圾回收 iw, ih = photo.width(), photo.height() cv.create_image(x0 + (aw - iw) // 2, y0 + (ah - ih) // 2, image=photo, anchor='nw') # 四块黑矩形盖住测试卡区域之外 -> 大图等效中心裁剪 cv.create_rectangle(-2, -2, w + 2, y0, fill='black', outline='') cv.create_rectangle(-2, y0 + ah, w + 2, h + 2, fill='black', outline='') cv.create_rectangle(-2, y0, x0, y0 + ah, fill='black', outline='') cv.create_rectangle(x0 + aw, y0, w + 2, y0 + ah, fill='black', outline='') top.bind('', draw) top.after(50, draw) top.wait_window(top)