diff --git a/app/export/image_exporter.py b/app/export/image_exporter.py index f45eb6c..f5dd3a4 100644 --- a/app/export/image_exporter.py +++ b/app/export/image_exporter.py @@ -2,6 +2,21 @@ import os +_EXPORT_BG_COLOR = "#FFFFFF" + + +def _save_with_light_background(fig, path, *, dpi=300, bbox_inches=None): + """导出统一浅色背景,避免深色主题下图片背景变暗。""" + kwargs = { + "dpi": dpi, + "facecolor": _EXPORT_BG_COLOR, + "edgecolor": _EXPORT_BG_COLOR, + } + if bbox_inches is not None: + kwargs["bbox_inches"] = bbox_inches + fig.savefig(path, **kwargs) + + def _gamut_refs_for_type(test_type): """按测试类型返回需要导出的参考色域列表。""" if test_type == "sdr_movie": @@ -70,7 +85,7 @@ def save_result_images(result_dir, current_test_type, selected_items, continue per_ref_name = f"色域测试结果_{ref}.png" path = os.path.join(result_dir, per_ref_name) - fig.savefig(path, dpi=300) + _save_with_light_background(fig, path, dpi=300) log(f"已保存: {per_ref_name}") finally: ref_var.set(original_ref) @@ -82,7 +97,7 @@ def save_result_images(result_dir, current_test_type, selected_items, continue path = os.path.join(result_dir, filename) if default_bbox: - fig.savefig(path, dpi=300) + _save_with_light_background(fig, path, dpi=300) else: - fig.savefig(path, dpi=300, bbox_inches="tight") + _save_with_light_background(fig, path, dpi=300, bbox_inches="tight") log(f"已保存: {filename}") diff --git a/app/plots/plot_accuracy.py b/app/plots/plot_accuracy.py index 1f017c4..6ffb9d1 100644 --- a/app/plots/plot_accuracy.py +++ b/app/plots/plot_accuracy.py @@ -75,7 +75,7 @@ def _xy_to_uv(x: float, y: float): # 子图:左侧 Calman 风格面板 # ============================================================ -def _draw_left_panel(ax, color_patches, delta_e_values, font_scale=1.0): +def _draw_left_panel(ax, color_patches, delta_e_values, font_scale=1.0, dark_mode=False): """左侧仅保留大条形图。""" ax.clear() @@ -98,19 +98,23 @@ def _draw_left_panel(ax, color_patches, delta_e_values, font_scale=1.0): zorder=3, ) + text_color = "#F3F5F7" if dark_mode else "#111111" + bg_color = "#0F1115" if dark_mode else "#FFFFFF" + spine_color = "#8C8F94" if dark_mode else "#9A9A9A" + ax.set_yticks(y_pos) - ax.set_yticklabels(color_patches, fontsize=max(5, 7 * font_scale)) + ax.set_yticklabels(color_patches, fontsize=max(5, 7 * font_scale), color=text_color) ax.invert_yaxis() x_max = max(15.0, max(delta_e_values) * 1.15) ax.set_xlim(0, x_max) - ax.tick_params(axis="x", labelsize=max(6, 8 * font_scale)) + ax.tick_params(axis="x", labelsize=max(6, 8 * font_scale), colors=text_color) ax.grid(axis="x", linestyle="-", linewidth=0.6, alpha=0.3, zorder=0) ax.grid(axis="y", linestyle=":", linewidth=0.35, alpha=0.15, zorder=0) - ax.set_facecolor("#FFFFFF") + ax.set_facecolor(bg_color) for spine in ax.spines.values(): - spine.set_color("#9A9A9A") + spine.set_color(spine_color) spine.set_linewidth(0.9) @@ -118,7 +122,7 @@ def _draw_left_panel(ax, color_patches, delta_e_values, font_scale=1.0): # 子图:CIE 1976 u'v' 色度图(目标 vs 实测) # ============================================================ -def _draw_uv_diagram(ax, color_patches, measurements, standards, font_scale=1.0): +def _draw_uv_diagram(ax, color_patches, measurements, standards, font_scale=1.0, dark_mode=False): """绘制 CIE 1976 u'v' 上的色准对比。""" ax.clear() try: @@ -135,15 +139,23 @@ def _draw_uv_diagram(ax, color_patches, measurements, standards, font_scale=1.0) ax.set_xlim(0.0, 0.65) ax.set_ylim(0.0, 0.60) - ax.set_facecolor("#000") + text_color = "#F3F5F7" if dark_mode else "#111111" + sub_text_color = "#D3D7DD" if dark_mode else "#222222" + tick_color = "#D3D7DD" if dark_mode else "#222222" + legend_label_color = "#FFF" if dark_mode else "#111" + legend_bg = "#111" if dark_mode else "#FFFFFF" + legend_edge = "#FFF" if dark_mode else "#333" + outer_edge = "#FFFFFF" if dark_mode else "#333333" + + ax.set_facecolor("#000" if dark_mode else "#FFFFFF") ax.set_aspect("equal", adjustable="box") ax.set_title("CIE 1976 u'v'", fontsize=max(8, 11 * font_scale), fontweight="bold", - color="#111", pad=4) - ax.set_xlabel("u'", fontsize=max(7, 9 * font_scale), color="#222", labelpad=1) - ax.set_ylabel("v'", fontsize=max(7, 9 * font_scale), color="#222", labelpad=1) - ax.tick_params(axis="both", labelsize=max(6, 8 * font_scale), colors="#222") + color=text_color, pad=4) + ax.set_xlabel("u'", fontsize=max(7, 9 * font_scale), color=sub_text_color, labelpad=1) + ax.set_ylabel("v'", fontsize=max(7, 9 * font_scale), color=sub_text_color, labelpad=1) + ax.tick_params(axis="both", labelsize=max(6, 8 * font_scale), colors=tick_color) for sp in ax.spines.values(): - sp.set_color("#666") + sp.set_color(outer_edge) sp.set_linewidth(0.9) for name, meas in zip(color_patches, measurements): @@ -164,14 +176,14 @@ def _draw_uv_diagram(ax, color_patches, measurements, standards, font_scale=1.0) ax.scatter( [s_u], [s_v], s=56, marker="s", - facecolors="none", edgecolors="#FFFFFF", + facecolors="none", edgecolors=outer_edge, linewidths=1.25, zorder=18, ) # 实测点:白色外圈 + 内层圆点 ax.scatter( [m_u], [m_v], s=52, marker="o", - facecolors="none", edgecolors="#FFFFFF", + facecolors="none", edgecolors=outer_edge, linewidths=1.0, zorder=19, ) ax.scatter( @@ -183,7 +195,7 @@ def _draw_uv_diagram(ax, color_patches, measurements, standards, font_scale=1.0) legend_handles = [ Line2D([0], [0], marker="s", linestyle="none", - markerfacecolor="#CCCCCC", markeredgecolor="#FFFFFF", + markerfacecolor="#CCCCCC", markeredgecolor=outer_edge, markersize=7, label="目标 (Target)"), Line2D([0], [0], marker="o", linestyle="none", markerfacecolor="#CCCCCC", markeredgecolor="#000000", @@ -192,15 +204,15 @@ def _draw_uv_diagram(ax, color_patches, measurements, standards, font_scale=1.0) leg = ax.legend( handles=legend_handles, loc="lower right", fontsize=max(6, 8 * font_scale), - framealpha=0.88, labelcolor="#FFF", + framealpha=0.88, labelcolor=legend_label_color, ) if leg is not None: - leg.get_frame().set_facecolor("#111") - leg.get_frame().set_edgecolor("#FFF") + leg.get_frame().set_facecolor(legend_bg) + leg.get_frame().set_edgecolor(legend_edge) leg.set_zorder(50) -def _draw_result_judgement(ax, accuracy_data, font_scale=1.0): +def _draw_result_judgement(ax, accuracy_data, font_scale=1.0, dark_mode=False): """底部结果条""" ax.clear() ax.set_xlim(0, 1) @@ -210,24 +222,28 @@ def _draw_result_judgement(ax, accuracy_data, font_scale=1.0): avg = accuracy_data.get("avg_delta_e", 0.0) mx = accuracy_data.get("max_delta_e", 0.0) + panel_bg = "#1A1E24" if dark_mode else "#FFFFFF" + panel_edge = "#4A5058" if dark_mode else "#C6C6C6" + text_color = "#F3F5F7" if dark_mode else "#111111" + ax.add_patch(Rectangle( (0.0, 0.10), 1.0, 0.80, transform=ax.transAxes, - facecolor="#FFFFFF", edgecolor="#C6C6C6", linewidth=1.0, + facecolor=panel_bg, edgecolor=panel_edge, linewidth=1.0, )) ax.text( 0.03, 0.50, f"Avg dE2000: {avg:.2f}", ha="left", va="center", - fontsize=max(11, 20 * font_scale), fontweight="normal", color="#111111", + fontsize=max(11, 20 * font_scale), fontweight="normal", color=text_color, transform=ax.transAxes, ) ax.text( 0.52, 0.50, f"Max dE2000: {mx:.2f}", ha="left", va="center", - fontsize=max(11, 20 * font_scale), fontweight="normal", color="#111111", + fontsize=max(11, 20 * font_scale), fontweight="normal", color=text_color, transform=ax.transAxes, ) @@ -242,6 +258,14 @@ def plot_accuracy(self: "PQAutomationApp", accuracy_data, test_type): fig = self.accuracy_fig fig.clear() + try: + from app.views.theme_manager import is_dark + dark_mode = is_dark() + except Exception: + dark_mode = False + + fig.patch.set_facecolor("#1B1F24" if dark_mode else "#FFFFFF") + # 根据当前画布像素尺寸动态缩放字体,避免窗口缩小时文字挤压重叠。 font_scale = 1.0 try: @@ -271,12 +295,13 @@ def plot_accuracy(self: "PQAutomationApp", accuracy_data, test_type): else: title = f"{test_type_name} - 色准测试(全 29色 | Gamma {target_gamma})" + title_color = "#F3F5F7" if dark_mode else "#111" fig.suptitle( title, fontsize=max(8, 11 * font_scale), y=0.975, fontweight="bold", - color="#111", + color=title_color, ) gs = fig.add_gridspec( @@ -295,7 +320,13 @@ def plot_accuracy(self: "PQAutomationApp", accuracy_data, test_type): # 兼容外部对 self.accuracy_ax 的引用 self.accuracy_ax = ax_judge - _draw_left_panel(ax_left, color_patches, delta_e_values, font_scale=font_scale) + _draw_left_panel( + ax_left, + color_patches, + delta_e_values, + font_scale=font_scale, + dark_mode=dark_mode, + ) try: standards = get_accuracy_color_standards(test_type) @@ -308,8 +339,14 @@ def plot_accuracy(self: "PQAutomationApp", accuracy_data, test_type): measurements, standards, font_scale=font_scale, + dark_mode=dark_mode, + ) + _draw_result_judgement( + ax_judge, + accuracy_data, + font_scale=font_scale, + dark_mode=dark_mode, ) - _draw_result_judgement(ax_judge, accuracy_data, font_scale=font_scale) try: self.update_accuracy_result_table(accuracy_data, standards) diff --git a/app/plots/plot_gamut.py b/app/plots/plot_gamut.py index a1249c9..2dd7585 100644 --- a/app/plots/plot_gamut.py +++ b/app/plots/plot_gamut.py @@ -148,35 +148,41 @@ def _draw_measured_triangle(ax, vertices, *, uv_space=False): # ) -def _draw_coverage_box(ax, x_pos, y_pos, current_ref, coverage): +def _draw_coverage_box(ax, x_pos, y_pos, current_ref, coverage, *, dark_mode): + text_color = "#FFF" if dark_mode else "#111" + box_face = "#111" if dark_mode else "#FFFFFF" + box_edge = "#FFF" if dark_mode else "#333" ax.text( x_pos, y_pos, f"{current_ref}\n覆盖率: {coverage:.1f}%", ha="right", va="bottom", fontsize=11, fontweight="bold", - color="#FFF", + color=text_color, bbox=dict( boxstyle="round,pad=0.38", - facecolor="#111", - edgecolor="#FFF", + facecolor=box_face, + edgecolor=box_edge, linewidth=1.7, alpha=0.98, ), zorder=30, ) -def _style_axes(ax, *, title, xlabel, ylabel, xlim, ylim): - ax.set_facecolor("#000") - ax.set_title(title, fontsize=12, fontweight="bold", color="#FFF", pad=8) - ax.set_xlabel(xlabel, fontsize=10, color="#FFF") - ax.set_ylabel(ylabel, fontsize=10, color="#FFF") +def _style_axes(ax, *, title, xlabel, ylabel, xlim, ylim, dark_mode): + text = "#F4F6F8" if dark_mode else "#111" + grid = "#444" if dark_mode else "#B8BDC3" + spine_color = "#888" if dark_mode else "#666" + ax.set_facecolor("#000" if dark_mode else "#FFFFFF") + ax.set_title(title, fontsize=12, fontweight="bold", color=text, pad=8) + ax.set_xlabel(xlabel, fontsize=10, color=text) + ax.set_ylabel(ylabel, fontsize=10, color=text) ax.set_xlim(*xlim) ax.set_ylim(*ylim) ax.set_aspect("equal", adjustable="datalim") - ax.grid(True, linestyle=":", linewidth=0.7, color="#444", alpha=0.32) - ax.tick_params(axis="both", labelsize=9, colors="#FFF") + ax.grid(True, linestyle=":", linewidth=0.7, color=grid, alpha=0.32) + ax.tick_params(axis="both", labelsize=9, colors=text) for spine in ax.spines.values(): - spine.set_color("#888") + spine.set_color(spine_color) spine.set_linewidth(0.8) ax.set_clip_on(False) @@ -201,13 +207,19 @@ def _blit_background(ax, background, bbox): def plot_gamut(self: "PQAutomationApp", results, coverage, test_type): """绘制色域图(图像层 + 框架层分离架构)。""" + try: + from app.views.theme_manager import is_dark + dark_mode = is_dark() + except Exception: + dark_mode = False + ax_xy = self.gamut_ax_xy ax_uv = self.gamut_ax_uv ax_xy.clear() ax_uv.clear() - # 全局黑色背景 - self.gamut_fig.patch.set_facecolor("#000") + # 全局背景跟随浅/深色主题 + self.gamut_fig.patch.set_facecolor("#0D1014" if dark_mode else "#FFFFFF") # ========== 读取用户选择的参考标准 ========== if test_type == "screen_module": @@ -265,6 +277,7 @@ def plot_gamut(self: "PQAutomationApp", results, coverage, test_type): xlabel="x", ylabel="y", xlim=(bbox_xy[0], bbox_xy[1]), ylim=(bbox_xy[2], bbox_xy[3]), + dark_mode=dark_mode, ) for ref_name in other_refs: @@ -289,7 +302,8 @@ def plot_gamut(self: "PQAutomationApp", results, coverage, test_type): _draw_measured_triangle(ax_xy, measured_xy, uv_space=False) _draw_coverage_box( - ax_xy, bbox_xy[1] - 0.02, bbox_xy[2] + 0.02, current_ref, xy_coverage + ax_xy, bbox_xy[1] - 0.02, bbox_xy[2] + 0.02, current_ref, xy_coverage, + dark_mode=dark_mode, ) # 暗化三角形外部区域(黑色半透明遮罩) @@ -303,18 +317,19 @@ def plot_gamut(self: "PQAutomationApp", results, coverage, test_type): codes = [Path.MOVETO] + [Path.LINETO]*3 + [Path.CLOSEPOLY] codes += [Path.MOVETO] + [Path.LINETO]*2 + [Path.CLOSEPOLY] path = Path(verts, codes) - patch = PathPatch(path, facecolor=(0,0,0,0.65), lw=0, zorder=7) + mask_face = (0, 0, 0, 0.65) if dark_mode else (1, 1, 1, 0.50) + patch = PathPatch(path, facecolor=mask_face, lw=0, zorder=7) ax_xy.add_patch(patch) legend = ax_xy.legend( loc="upper right", fontsize=8.5, framealpha=0.0, edgecolor="#000", fancybox=True, - labelcolor="#FFF" + labelcolor="#FFF" if dark_mode else "#111" ) legend.set_zorder(200) - legend.get_frame().set_facecolor("#000") - legend.get_frame().set_alpha(0.5) - legend.get_frame().set_edgecolor("#FFF") + legend.get_frame().set_facecolor("#000" if dark_mode else "#FFFFFF") + legend.get_frame().set_alpha(0.5 if dark_mode else 0.78) + legend.get_frame().set_edgecolor("#FFF" if dark_mode else "#333") ax_xy.add_artist(legend) except Exception as e: @@ -334,6 +349,7 @@ def plot_gamut(self: "PQAutomationApp", results, coverage, test_type): xlabel="u'", ylabel="v'", xlim=(bbox_uv[0], bbox_uv[1]), ylim=(bbox_uv[2], bbox_uv[3]), + dark_mode=dark_mode, ) measured_uv = None @@ -367,7 +383,8 @@ def plot_gamut(self: "PQAutomationApp", results, coverage, test_type): _draw_measured_triangle(ax_uv, measured_uv, uv_space=True) _draw_coverage_box( - ax_uv, bbox_uv[1] - 0.015, bbox_uv[2] + 0.015, current_ref, uv_coverage + ax_uv, bbox_uv[1] - 0.015, bbox_uv[2] + 0.015, current_ref, uv_coverage, + dark_mode=dark_mode, ) u0, u1 = bbox_uv[0], bbox_uv[1] @@ -379,18 +396,19 @@ def plot_gamut(self: "PQAutomationApp", results, coverage, test_type): codes = [Path.MOVETO] + [Path.LINETO]*3 + [Path.CLOSEPOLY] codes += [Path.MOVETO] + [Path.LINETO]*2 + [Path.CLOSEPOLY] path = Path(verts, codes) - patch = PathPatch(path, facecolor=(0,0,0,0.65), lw=0, zorder=7) + mask_face = (0, 0, 0, 0.65) if dark_mode else (1, 1, 1, 0.50) + patch = PathPatch(path, facecolor=mask_face, lw=0, zorder=7) ax_uv.add_patch(patch) legend_uv = ax_uv.legend( loc="upper right", fontsize=8.5, framealpha=0.0, edgecolor="#000", fancybox=True, - labelcolor="#FFF" + labelcolor="#FFF" if dark_mode else "#111" ) legend_uv.set_zorder(200) - legend_uv.get_frame().set_facecolor("#000") - legend_uv.get_frame().set_alpha(0.72) - legend_uv.get_frame().set_edgecolor("#FFF") + legend_uv.get_frame().set_facecolor("#000" if dark_mode else "#FFFFFF") + legend_uv.get_frame().set_alpha(0.72 if dark_mode else 0.82) + legend_uv.get_frame().set_edgecolor("#FFF" if dark_mode else "#333") ax_uv.add_artist(legend_uv) except Exception as e: diff --git a/app/views/chart_frame.py b/app/views/chart_frame.py index 6af3e21..22cfeba 100644 --- a/app/views/chart_frame.py +++ b/app/views/chart_frame.py @@ -16,6 +16,45 @@ if TYPE_CHECKING: from pqAutomationApp import PQAutomationApp +def _result_bg_color() -> str: + """根据当前主题返回结果图背景色。""" + try: + from app.views.theme_manager import is_dark + return "#1B1F24" if is_dark() else "#FFFFFF" + except Exception: + return "#FFFFFF" + + +def apply_result_chart_theme(self: "PQAutomationApp"): + """统一刷新结果图画布背景,使其跟随浅/深色主题。""" + bg = _result_bg_color() + + chart_pairs = [ + ("gamut_fig", "gamut_canvas"), + ("gamma_fig", "gamma_canvas"), + ("eotf_fig", "eotf_canvas"), + ("cct_fig", "cct_canvas"), + ("contrast_fig", "contrast_canvas"), + ("accuracy_fig", "accuracy_canvas"), + ] + + for fig_attr, canvas_attr in chart_pairs: + fig = getattr(self, fig_attr, None) + canvas = getattr(self, canvas_attr, None) + if fig is not None: + fig.patch.set_facecolor(bg) + if canvas is not None: + try: + widget = canvas.get_tk_widget() + widget.configure(bg=bg, highlightthickness=0) + except Exception: + pass + try: + canvas.draw_idle() + except Exception: + pass + + def init_gamut_chart(self: "PQAutomationApp"): """初始化色域图表 - 手动设置subplot位置,完全避免重叠""" container = ttk.Frame(self.gamut_chart_frame) @@ -988,6 +1027,7 @@ def create_result_chart_frame(self: "PQAutomationApp"): self.init_cct_chart() self.init_contrast_chart() self.init_accuracy_chart() + self.apply_result_chart_theme() # 绑定Tab切换事件 self.chart_notebook.bind("<>", self.on_chart_tab_changed) @@ -1026,6 +1066,7 @@ class ChartFrameMixin: init_cct_chart = init_cct_chart init_contrast_chart = init_contrast_chart init_accuracy_chart = init_accuracy_chart + apply_result_chart_theme = apply_result_chart_theme _init_accuracy_result_table = _init_accuracy_result_table clear_accuracy_result_table = clear_accuracy_result_table update_accuracy_result_table = update_accuracy_result_table diff --git a/app/views/panels/ai_image_panel.py b/app/views/panels/ai_image_panel.py index 8d9d4a7..58a2ef2 100644 --- a/app/views/panels/ai_image_panel.py +++ b/app/views/panels/ai_image_panel.py @@ -20,6 +20,21 @@ if TYPE_CHECKING: from pqAutomationApp import PQAutomationApp +def _theme_colors(): + style = ttk.Style() + colors = style.colors + return { + "bg": colors.bg, + "fg": colors.fg, + "muted": colors.secondary, + "input_bg": colors.inputbg, + "input_fg": colors.inputfg, + "select_bg": colors.selectbg, + "select_fg": colors.selectfg, + "border": colors.border, + } + + @@ -50,6 +65,7 @@ def create_ai_image_panel(self: "PQAutomationApp"): container.columnconfigure(0, weight=0) container.columnconfigure(1, weight=1) container.rowconfigure(0, weight=1) + palette = _theme_colors() left = ttk.Frame(container, width=360) left.grid(row=0, column=0, sticky=tk.NS, padx=(0, 10)) @@ -72,10 +88,12 @@ def create_ai_image_panel(self: "PQAutomationApp"): bd=1, relief=tk.FLAT, highlightthickness=1, - highlightbackground="#d8d8d8", - highlightcolor="#4a90e2", - selectbackground="#2b6cb0", - selectforeground="#ffffff", + bg=palette["input_bg"], + fg=palette["input_fg"], + highlightbackground=palette["border"], + highlightcolor=palette["select_bg"], + selectbackground=palette["select_bg"], + selectforeground=palette["select_fg"], yscrollcommand=scroll.set, ) scroll.config(command=self.ai_image_listbox.yview) @@ -135,7 +153,7 @@ def create_ai_image_panel(self: "PQAutomationApp"): preview_frame.pack(fill=tk.BOTH, expand=True) self.ai_image_canvas = tk.Canvas( - preview_frame, bg="#1e1e1e", highlightthickness=0 + preview_frame, bg=palette["bg"], highlightthickness=0 ) self.ai_image_canvas.pack(fill=tk.BOTH, expand=True) self.ai_image_canvas.bind("", lambda e: _redraw_preview(self)) @@ -145,14 +163,21 @@ def create_ai_image_panel(self: "PQAutomationApp"): self.ai_image_meta_var = tk.StringVar(value="未选择图片") ttk.Label( meta_row, textvariable=self.ai_image_meta_var, - foreground="#666", font=("微软雅黑", 9), + foreground=palette["muted"], font=("微软雅黑", 9), ).pack(side=tk.LEFT) # 输入区 input_frame = ttk.LabelFrame(right, text="提示输入(Ctrl+Enter 发送)", padding=6) input_frame.pack(fill=tk.X, pady=(4, 0)) - self.ai_image_input = tk.Text(input_frame, height=3, wrap=tk.WORD) + self.ai_image_input = tk.Text( + input_frame, + height=3, + wrap=tk.WORD, + bg=palette["input_bg"], + fg=palette["input_fg"], + insertbackground=palette["input_fg"], + ) self.ai_image_input.pack(fill=tk.X, side=tk.TOP) self.ai_image_input.bind("", lambda e: (_send_prompt(self), "break")) @@ -161,7 +186,7 @@ def create_ai_image_panel(self: "PQAutomationApp"): self.ai_image_status_var = tk.StringVar(value="就绪") ttk.Label( send_row, textvariable=self.ai_image_status_var, - foreground="#888", font=("微软雅黑", 9), + foreground=palette["muted"], font=("微软雅黑", 9), ).pack(side=tk.LEFT) self.ai_image_progress = ttk.Progressbar( send_row, @@ -220,6 +245,7 @@ def reload_ai_image_list(self: "PQAutomationApp", auto_select_first=True): 其下列出该轮生成的所有图片。会话按"最近使用"倒序,组内按时间倒序。 auto_select_first: 是否自动选中第一张图片(默认 True)。 """ + palette = _theme_colors() self.ai_image_records = _svc.list_records(base_dir=_get_app_base_dir(self)) self.ai_image_listbox.delete(0, tk.END) # 维护行号 → 记录索引的映射;分隔头处为 None @@ -236,8 +262,8 @@ def reload_ai_image_list(self: "PQAutomationApp", auto_select_first=True): # 头部行:禁用选中(视觉上变灰) last = self.ai_image_listbox.size() - 1 self.ai_image_listbox.itemconfig( - last, foreground="#888", selectforeground="#888", - background="#f5f5f5", selectbackground="#f5f5f5", + last, foreground=palette["muted"], selectforeground=palette["muted"], + background=palette["bg"], selectbackground=palette["bg"], ) self._ai_image_row_map.append(None) self._ai_image_row_session_map.append(sid) @@ -331,6 +357,7 @@ def _select_record(self: "PQAutomationApp", rec: _svc.AIImageRecord): def _redraw_preview(self: "PQAutomationApp"): rec = getattr(self, "ai_image_current", None) canvas = self.ai_image_canvas + palette = _theme_colors() canvas.delete("all") if rec is None or not os.path.isfile(rec.image_path): return @@ -340,7 +367,7 @@ def _redraw_preview(self: "PQAutomationApp"): img = Image.open(rec.image_path) img.load() except Exception as exc: - canvas.create_text(cw // 2, ch // 2, text=f"加载失败: {exc}", fill="#f66") + canvas.create_text(cw // 2, ch // 2, text=f"加载失败: {exc}", fill=palette["select_bg"]) return iw, ih = img.size scale = min(cw / iw, ch / ih, 1.0) diff --git a/app/views/panels/cct_panel.py b/app/views/panels/cct_panel.py index 4f89bb5..87907bb 100644 --- a/app/views/panels/cct_panel.py +++ b/app/views/panels/cct_panel.py @@ -14,6 +14,14 @@ if TYPE_CHECKING: from pqAutomationApp import PQAutomationApp +def _theme_colors(): + style = ttk.Style() + colors = style.colors + return { + "muted": colors.secondary, + } + + def create_cct_params_frame(self: "PQAutomationApp"): """创建色度参数设置区域 - 屏模组、SDR、HDR 独立(增加色域参考标准选择 + 单步调试按钮)""" @@ -122,7 +130,7 @@ def create_cct_params_frame(self: "PQAutomationApp"): self.cct_params_frame, text="提示: 清空输入框将恢复默认值", font=("SimHei", 8), - foreground="gray", + foreground=_theme_colors()["muted"], ).grid(row=5, column=0, columnspan=4, sticky=tk.W, padx=5, pady=5) # ==================== SDR 色度参数 Frame ==================== @@ -227,7 +235,7 @@ def create_cct_params_frame(self: "PQAutomationApp"): self.sdr_cct_params_frame, text="提示: 清空输入框将恢复默认值", font=("SimHei", 8), - foreground="gray", + foreground=_theme_colors()["muted"], ).grid(row=5, column=0, columnspan=4, sticky=tk.W, padx=5, pady=5) # ==================== HDR 色度参数 Frame ==================== @@ -332,7 +340,7 @@ def create_cct_params_frame(self: "PQAutomationApp"): self.hdr_cct_params_frame, text="提示: 清空输入框将恢复默认值", font=("SimHei", 8), - foreground="gray", + foreground=_theme_colors()["muted"], ).grid(row=5, column=0, columnspan=4, sticky=tk.W, padx=5, pady=5) diff --git a/app/views/panels/main_layout.py b/app/views/panels/main_layout.py index edb9825..3e22630 100644 --- a/app/views/panels/main_layout.py +++ b/app/views/panels/main_layout.py @@ -718,6 +718,11 @@ def _on_toggle_theme(self: "PQAutomationApp") -> None: from app.views.theme_manager import toggle_theme toggle_theme() _refresh_theme_toggle_label(self) + if hasattr(self, "apply_result_chart_theme"): + try: + self.apply_result_chart_theme() + except Exception: + pass # 同步刷新侧栏选中态(高亮样式跟随新色板) if hasattr(self, "update_sidebar_selection"): try: diff --git a/app/views/pq_debug_panel.py b/app/views/pq_debug_panel.py index 50977bc..997f33a 100644 --- a/app/views/pq_debug_panel.py +++ b/app/views/pq_debug_panel.py @@ -10,6 +10,18 @@ import threading import time +def _theme_colors(): + style = ttk.Style() + colors = style.colors + return { + "fg": colors.fg, + "muted": colors.secondary, + "info": colors.info, + "warning": colors.warning, + "error": colors.danger, + } + + class PQDebugPanel: """PQ 单步调试面板 - 支持 Gamma/EOTF/色准单步测试""" @@ -72,7 +84,7 @@ class PQDebugPanel: self.screen_gamma_frame, text="测试完成后可用,选择灰阶进行单步调试", font=("SimHei", 9), - foreground="gray", + foreground=_theme_colors()["muted"], ).grid(row=0, column=0, columnspan=3, pady=(0, 10)) # 灰阶选择 @@ -137,7 +149,7 @@ class PQDebugPanel: self.screen_rgb_frame, text="测试完成后可用,选择颜色进行单步调试", font=("SimHei", 9), - foreground="gray", + foreground=_theme_colors()["muted"], ).grid(row=0, column=0, columnspan=3, pady=(0, 10)) # RGB 颜色选择 @@ -210,7 +222,7 @@ class PQDebugPanel: self.sdr_gamma_frame, text="测试完成后可用,选择灰阶进行单步调试", font=("SimHei", 9), - foreground="gray", + foreground=_theme_colors()["muted"], ).grid(row=0, column=0, columnspan=3, pady=(0, 10)) ttk.Label(self.sdr_gamma_frame, text="选择灰阶:").grid( @@ -272,7 +284,7 @@ class PQDebugPanel: self.sdr_accuracy_frame, text="测试完成后可用,选择色块进行单步调试", font=("SimHei", 9), - foreground="gray", + foreground=_theme_colors()["muted"], ).grid(row=0, column=0, columnspan=3, pady=(0, 10)) ttk.Label(self.sdr_accuracy_frame, text="选择色块:").grid( @@ -334,7 +346,7 @@ class PQDebugPanel: self.sdr_rgb_frame, text="测试完成后可用,选择颜色进行单步调试", font=("SimHei", 9), - foreground="gray", + foreground=_theme_colors()["muted"], ).grid(row=0, column=0, columnspan=3, pady=(0, 10)) # RGB 颜色选择 @@ -407,7 +419,7 @@ class PQDebugPanel: self.hdr_eotf_frame, text="测试完成后可用,选择灰阶进行单步调试", font=("SimHei", 9), - foreground="gray", + foreground=_theme_colors()["muted"], ).grid(row=0, column=0, columnspan=3, pady=(0, 10)) ttk.Label(self.hdr_eotf_frame, text="选择灰阶:").grid( @@ -469,7 +481,7 @@ class PQDebugPanel: self.hdr_accuracy_frame, text="测试完成后可用,选择色块进行单步调试", font=("SimHei", 9), - foreground="gray", + foreground=_theme_colors()["muted"], ).grid(row=0, column=0, columnspan=3, pady=(0, 10)) ttk.Label(self.hdr_accuracy_frame, text="选择色块:").grid( @@ -531,7 +543,7 @@ class PQDebugPanel: self.hdr_rgb_frame, text="测试完成后可用,选择颜色进行单步调试", font=("SimHei", 9), - foreground="gray", + foreground=_theme_colors()["muted"], ).grid(row=0, column=0, columnspan=3, pady=(0, 10)) # RGB 颜色选择 @@ -1007,12 +1019,13 @@ class PQDebugPanel: ) # 设置标签样式 - tree.tag_configure("header", background="#E3F2FD", font=("SimHei", 9, "bold")) - tree.tag_configure("normal", foreground="black") - tree.tag_configure("warning", foreground="red") - tree.tag_configure("highlight", foreground="blue", font=("SimHei", 9, "bold")) + palette = _theme_colors() + tree.tag_configure("header", background=palette["info"], font=("SimHei", 9, "bold")) + tree.tag_configure("normal", foreground=palette["fg"]) + tree.tag_configure("warning", foreground=palette["warning"]) + tree.tag_configure("highlight", foreground=palette["info"], font=("SimHei", 9, "bold")) tree.tag_configure( - "highlight_warning", foreground="red", font=("SimHei", 9, "bold") + "highlight_warning", foreground=palette["warning"], font=("SimHei", 9, "bold") ) def _disable_test_button(self, test_type, test_item): diff --git a/app/views/pq_log_gui.py b/app/views/pq_log_gui.py index cc70d5e..2ff97b4 100644 --- a/app/views/pq_log_gui.py +++ b/app/views/pq_log_gui.py @@ -17,6 +17,22 @@ _GUI_LEVEL_TO_LOG = { } +def _theme_colors(): + style = ttk.Style() + colors = style.colors + return { + "bg": colors.bg, + "fg": colors.fg, + "muted": colors.secondary, + "accent": colors.info, + "warning": colors.warning, + "error": colors.danger, + "success": colors.success, + "text_bg": colors.inputbg, + "text_fg": colors.inputfg, + } + + class PQLogGUI(ttk.Frame): VALID_LEVELS = {"info", "success", "warning", "error", "debug", "separator", "blank"} @@ -53,21 +69,22 @@ class PQLogGUI(ttk.Frame): text_container = ttk.Frame(log_frame) text_container.pack(fill=tk.BOTH, expand=True, padx=6, pady=(0, 6)) + palette = _theme_colors() self.log_text = tk.Text( text_container, height=10, width=50, wrap=tk.WORD, font=("Consolas", 10), - bg="#fbfcfe", - fg="#1f2937", + bg=palette["text_bg"], + fg=palette["text_fg"], relief=tk.FLAT, bd=0, padx=10, pady=8, spacing1=2, spacing3=2, - insertbackground="#1f2937", + insertbackground=palette["text_fg"], ) self.log_text.pack(fill=tk.BOTH, expand=True, side=tk.LEFT) @@ -114,19 +131,20 @@ class PQLogGUI(ttk.Frame): self._update_summary() def _configure_tags(self): - self.log_text.tag_configure("timestamp", foreground="#6b7280") - self.log_text.tag_configure("level_info", foreground="#2563eb") - self.log_text.tag_configure("level_success", foreground="#0f766e") - self.log_text.tag_configure("level_warning", foreground="#b45309") - self.log_text.tag_configure("level_error", foreground="#b91c1c") + palette = _theme_colors() + self.log_text.tag_configure("timestamp", foreground=palette["muted"]) + self.log_text.tag_configure("level_info", foreground=palette["accent"]) + self.log_text.tag_configure("level_success", foreground=palette["success"]) + self.log_text.tag_configure("level_warning", foreground=palette["warning"]) + self.log_text.tag_configure("level_error", foreground=palette["error"]) self.log_text.tag_configure("level_debug", foreground="#7c3aed") - self.log_text.tag_configure("message", foreground="#1f2937") - self.log_text.tag_configure("message_success", foreground="#0f766e") - self.log_text.tag_configure("message_warning", foreground="#b45309") - self.log_text.tag_configure("message_error", foreground="#991b1b") + self.log_text.tag_configure("message", foreground=palette["fg"]) + self.log_text.tag_configure("message_success", foreground=palette["success"]) + self.log_text.tag_configure("message_warning", foreground=palette["warning"]) + self.log_text.tag_configure("message_error", foreground=palette["error"]) self.log_text.tag_configure("message_debug", foreground="#6d28d9") - self.log_text.tag_configure("separator", foreground="#94a3b8") - self.log_text.tag_configure("traceback", foreground="#7f1d1d") + self.log_text.tag_configure("separator", foreground=palette["muted"]) + self.log_text.tag_configure("traceback", foreground=palette["error"]) self.log_text.tag_configure("blank", spacing1=4, spacing3=4) def _append_message(self, message, level): diff --git a/settings/pq_config.json b/settings/pq_config.json index 3b0c675..39d311a 100644 --- a/settings/pq_config.json +++ b/settings/pq_config.json @@ -1,5 +1,5 @@ { - "current_test_type": "screen_module", + "current_test_type": "hdr_movie", "test_types": { "screen_module": { "name": "屏模组性能测试",