feat(HDMI-Tool): CustomTkinter 双栏仪表盘界面重构(feature/ctk-ui)
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
#!/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('<Escape>', 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('<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 指定的测试卡区域:
|
||||
小于区域 -> 真实像素居中, 四周黑边;
|
||||
大于区域 -> 等效从中心截取(区域外用黑矩形遮挡)"""
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user