""" CIE 色度图底图渲染与缓存(工业版) 特点: - colour-science 谱迹渲染 - numpy RGBA 缓存 - 内存 + 磁盘缓存 - 支持 light / dark UI - 启动预热 - 线程安全 调用方式: bg, bbox = get_cie1931_background(mode="dark") ax.imshow(bg, extent=bbox) """ from __future__ import annotations import hashlib import os import threading from typing import Tuple import numpy as np # ---------------------------- # 配置 # ---------------------------- # 渲染分辨率 _DIAGRAM_RES = 1024 # 缓存版本(风格变化时递增) _CACHE_VERSION = "v2" # UI 颜色 _DARK_BG = "#0f1115" _LIGHT_BG = "#ffffff" _BBox = Tuple[float, float, float, float] _CIE1931_BBOX: _BBox = (0.0, 0.8, 0.0, 0.9) _CIE1976_BBOX: _BBox = (0.0, 0.65, 0.0, 0.6) _memory_cache: dict[str, np.ndarray] = {} _lock = threading.Lock() # ---------------------------- # cache path # ---------------------------- def _cache_dir() -> str: here = os.path.dirname(os.path.abspath(__file__)) root = os.path.abspath(os.path.join(here, "..", "..")) d = os.path.join(root, "settings", "cache") os.makedirs(d, exist_ok=True) return d def _cache_key(kind: str, bbox: _BBox, mode: str) -> str: sig = f"{kind}|{bbox}|{mode}|{_DIAGRAM_RES}|{_CACHE_VERSION}" h = hashlib.md5(sig.encode()).hexdigest()[:10] return f"chromaticity_{kind}_{mode}_{h}.npy" def _cache_path(kind: str, bbox: _BBox, mode: str) -> str: return os.path.join(_cache_dir(), _cache_key(kind, bbox, mode)) # ---------------------------- # 渲染 # ---------------------------- def _render_chromaticity(kind: str, bbox: _BBox, mode: str) -> np.ndarray: """ 通过 colour-science 渲染 chromaticity 图。 """ import matplotlib prev_backend = matplotlib.get_backend() try: matplotlib.use("Agg", force=True) except Exception: pass import matplotlib.pyplot as plt import colour from colour.plotting import ( plot_chromaticity_diagram_CIE1931, plot_chromaticity_diagram_CIE1976UCS, ) if mode == "dark": colour.plotting.colour_style("dark") bg_color = _DARK_BG else: colour.plotting.colour_style("light") bg_color = _LIGHT_BG xmin, xmax, ymin, ymax = bbox aspect = (xmax - xmin) / (ymax - ymin) height = _DIAGRAM_RES width = int(round(height * aspect)) fig = plt.figure( figsize=(width / 100.0, height / 100.0), dpi=100 ) fig.patch.set_facecolor(bg_color) ax = fig.add_axes([0, 0, 1, 1]) ax.set_facecolor(bg_color) if kind == "cie1931": plot_chromaticity_diagram_CIE1931( axes=ax, show=False, title=False, tight_layout=False, bounding_box=bbox, transparent_background=False, ) elif kind == "cie1976": plot_chromaticity_diagram_CIE1976UCS( axes=ax, show=False, title=False, tight_layout=False, bounding_box=bbox, transparent_background=False, ) else: plt.close(fig) raise ValueError(f"unknown diagram kind: {kind}") ax.set_xlim(xmin, xmax) ax.set_ylim(ymin, ymax) ax.set_axis_off() ax.set_position([0, 0, 1, 1]) fig.canvas.draw() buf = np.asarray(fig.canvas.buffer_rgba()).copy() buf = np.flipud(buf) plt.close(fig) try: matplotlib.use(prev_backend, force=True) except Exception: pass return buf # ---------------------------- # load / render # ---------------------------- def _load_or_render(kind: str, bbox: _BBox, mode: str) -> np.ndarray: key = _cache_key(kind, bbox, mode) with _lock: if key in _memory_cache: return _memory_cache[key] disk = _cache_path(kind, bbox, mode) if os.path.isfile(disk): try: arr = np.load(disk) _memory_cache[key] = arr return arr except Exception: try: os.remove(disk) except OSError: pass arr = _render_chromaticity(kind, bbox, mode) _memory_cache[key] = arr try: np.save(disk, arr) except Exception: pass return arr # ---------------------------- # public API # ---------------------------- def get_cie1931_background(mode: str = "dark") -> Tuple[np.ndarray, _BBox]: """ 获取 CIE1931 背景图 mode: "dark" "light" """ return _load_or_render("cie1931", _CIE1931_BBOX, mode), _CIE1931_BBOX def get_cie1976_background(mode: str = "dark") -> Tuple[np.ndarray, _BBox]: return _load_or_render("cie1976", _CIE1976_BBOX, mode), _CIE1976_BBOX # ---------------------------- # cache control # ---------------------------- def clear_cache(*, disk: bool = False) -> None: """ 清空缓存 """ with _lock: _memory_cache.clear() if disk: d = _cache_dir() for name in os.listdir(d): if name.startswith("chromaticity_"): try: os.remove(os.path.join(d, name)) except OSError: pass # ---------------------------- # warmup # ---------------------------- def warmup_cache(mode: str = "dark") -> None: """ 启动预热缓存 可在软件启动时调用,避免首次绘图卡顿。 """ get_cie1931_background(mode) get_cie1976_background(mode)