Files
HW-Debug-Tools/DLPilot/src/patterns.py
T

157 lines
6.2 KiB
Python

#!/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
# 当前打开的投屏窗口集合: 支持"新图案替换旧图案"与 close_all() 一键恢复桌面
_OPEN = set()
def close_all():
"""关闭所有打开的投屏窗口, 恢复桌面"""
for top in list(_OPEN):
try:
top.destroy()
except Exception:
pass
_OPEN.clear()
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 上该组合有效)。
窗口登记进 _OPEN: 打开新投屏前会关旧的(替换语义), close_all() 一键全关。
"""
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):
_OPEN.discard(top)
top.destroy()
def _on_destroy(e):
if e.widget is top: # 子控件销毁也冒泡到此, 只认窗口本身
_OPEN.discard(top)
top.bind('<Escape>', close)
top.bind('<Destroy>', _on_destroy)
_OPEN.add(top)
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 指定。
打开前先关闭已有投屏窗口(替换语义, 不会多层叠罗汉)。"""
close_all()
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('<Configure>', 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 指定的测试卡区域:
小于区域 -> 真实像素居中, 四周黑边;
大于区域 -> 等效从中心截取(区域外用黑矩形遮挡)。
打开前先关闭已有投屏窗口(替换语义)。"""
close_all()
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('<Configure>', draw)
top.after(50, draw)
top.wait_window(top)