跳转至

engines · 绘制引擎层入口

tkdeft 绘制引擎层入口。

本包把"画什么"(声明式规格)与"用什么画"(引擎)分开:

  • 规格与引擎基类在 :mod:tkdeft.engines.base;
  • 图片缓存在 :mod:tkdeft.engines.cache;
  • 颜色解析在 :mod:tkdeft.engines.colors;
  • 三个进程内栅格引擎分别在 :mod:tkdeft.engines.skia_engine / :mod:tkdeft.engines.pillow_engine / :mod:tkdeft.engines.cairo_engine。

这个模块只做一件事:把常用接口汇总到一个命名空间里。

内置引擎

====== ====== ========== ============================================== 编号 名字 类型 说明 ====== ====== ========== ============================================== 0 tksvg SVG(文件) 默认,保持既有行为完全不变 1 wand SVG(文件) 经 Wand(ImageMagick) 转 PNG,兼容旧 renderer=1 2 skia 栅格 skia-python,质量与速度最好 3 pillow 栅格 Pillow 超采样,零额外依赖,永远可用 4 cairo 栅格 pycairo ====== ====== ========== ==============================================

快速上手

::

from tkdeft.engines import RoundRectSpec, render_roundrect, set_engine

set_engine("skia")                              # 或 set_engine(2)
photo = render_roundrect(spec, master=canvas)   # -> PhotoImage | None

render_* 返回 None 表示"当前引擎不参与这条路径"(SVG 引擎就是如此), 调用方应回退到既有的 svgwrite + tksvg 实现。这样 SVG 引擎与栅格引擎可以共存于 同一套组件代码。

注册表与诊断

::

from tkdeft.engines import list_engines, describe_engines, available_engines

list_engines()        # {'tksvg': True, 'wand': True, 'skia': True, ...}
available_engines()   # ['tksvg', 'wand', 'skia', 'pillow', 'cairo']
describe_engines()    # 带编号/依赖/描述的完整表格,适合打印或喂给 CLI

缓存

::

from tkdeft.engines import cache_stats, set_cache_budget, clear_cache

cache_stats()                 # {'entries': .., 'hits': .., 'hit_rate': ..}
set_cache_budget(16_000_000)  # 调大像素预算(默认 8M 像素)

DrawEngine

绘制引擎基类。

子类需要声明 :attr:name / :attr:kind,并实现 :meth:render_roundrect 等渲染方法:kind == "raster" 的引擎会被画布用作快速路径。

:param str name: 引擎名(注册表里的主键,小写) :param str kind: "svg" 或 "raster" :param tuple requires: 依赖的 Python 模块名,供 :meth:available 惰性探测 :param str description: 人类可读的一句话说明

源代码位于: tkdeft\engines\base.py
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
class DrawEngine:
    """绘制引擎基类。

    子类需要声明 :attr:`name` / :attr:`kind`,并实现 :meth:`render_roundrect`
    等渲染方法:``kind == "raster"`` 的引擎会被画布用作快速路径。

    :param str name: 引擎名(注册表里的主键,小写)
    :param str kind: ``"svg"`` 或 ``"raster"``
    :param tuple requires: 依赖的 Python 模块名,供 :meth:`available` 惰性探测
    :param str description: 人类可读的一句话说明
    """

    name: str = "base"
    kind: str = "svg"  # "svg" | "raster"
    requires: Tuple[str, ...] = ()
    description: str = ""

    # -- 能力探测 ---------------------------------------------------------
    def available(self) -> bool:
        """依赖是否都能导入。

        :returns: 全部依赖可导入时为 ``True``;不装 ``skia-python`` 也不影响
            其它引擎(只有 :meth:`available` 为 ``False`` 的引擎不能选)。
        """
        import importlib.util

        for mod in self.requires:
            if importlib.util.find_spec(mod) is None:
                return False
        return True

    @property
    def is_raster(self) -> bool:
        """是否是进程内栅格引擎(只有它会参与画布快速路径)。"""
        return self.kind == "raster"

    def supports(self, kind: str) -> bool:
        """是否支持某类图元(``"roundrect"`` / ``"track"`` / ``"thumb"``)。"""
        return callable(getattr(self, f"render_{kind}", None))

    @property
    def index(self) -> int:
        """兼容历史 API 的编号(tksvg=0 … cairo=4);不在表里时为 ``-1``。"""
        return _NAME_TO_INDEX.get(self.name, -1)

    # -- 渲染接口(栅格引擎实现)------------------------------------------
    def render_roundrect(self, spec: RoundRectSpec):
        """渲染圆角矩形,返回 ``PIL.Image.Image``(RGBA)。

        :raises NotImplementedError: 该引擎不支持这条快速路径
            (SVG 引擎会走既有的 svgwrite + tksvg 流程)。
        """
        raise NotImplementedError

    def render_track(self, spec: TrackSpec):
        """渲染滑块进度条槽,返回 ``PIL.Image.Image``(RGBA)。"""
        raise NotImplementedError

    def render_thumb(self, spec: ThumbSpec):
        """渲染滑块把手,返回 ``PIL.Image.Image``(RGBA)。"""
        raise NotImplementedError

    def render(self, spec: Spec):
        """按规格自身的类型分发到对应的 ``render_*`` 方法。

        这是一个便捷入口,等价于 ``engine.render_roundrect(spec)`` 之类,
        但调用方不需要知道规格的具体类型。
        """
        renderer = getattr(self, f"render_{spec.kind}", None)
        if renderer is None:
            raise NotImplementedError(
                f"{self.name} 不支持图元 {spec.kind!r}"
            )
        return renderer(spec)

    def close(self) -> None:
        """释放引擎持有的资源(可选的钩子,默认什么都不做)。"""

    # -- 描述 -------------------------------------------------------------
    def info(self) -> Dict[str, Any]:
        """引擎的结构化信息,供 CLI / 文档 / 诊断使用。"""
        return {
            "name": self.name,
            "index": self.index,
            "kind": self.kind,
            "available": self.available(),
            "requires": tuple(self.requires),
            "description": self.description or type(self).__doc__ or "",
        }

    def __repr__(self) -> str:  # pragma: no cover - 调试友好
        return f"<{type(self).__name__} name={self.name!r} kind={self.kind!r}>"

index property

兼容历史 API 的编号(tksvg=0 … cairo=4);不在表里时为 -1。

is_raster property

是否是进程内栅格引擎(只有它会参与画布快速路径)。

available()

依赖是否都能导入。

:returns: 全部依赖可导入时为 True;不装 skia-python 也不影响 其它引擎(只有 :meth:available 为 False 的引擎不能选)。

源代码位于: tkdeft\engines\base.py
365
366
367
368
369
370
371
372
373
374
375
376
def available(self) -> bool:
    """依赖是否都能导入。

    :returns: 全部依赖可导入时为 ``True``;不装 ``skia-python`` 也不影响
        其它引擎(只有 :meth:`available` 为 ``False`` 的引擎不能选)。
    """
    import importlib.util

    for mod in self.requires:
        if importlib.util.find_spec(mod) is None:
            return False
    return True

close()

释放引擎持有的资源(可选的钩子,默认什么都不做)。

源代码位于: tkdeft\engines\base.py
422
423
def close(self) -> None:
    """释放引擎持有的资源(可选的钩子,默认什么都不做)。"""

info()

引擎的结构化信息,供 CLI / 文档 / 诊断使用。

源代码位于: tkdeft\engines\base.py
426
427
428
429
430
431
432
433
434
435
def info(self) -> Dict[str, Any]:
    """引擎的结构化信息,供 CLI / 文档 / 诊断使用。"""
    return {
        "name": self.name,
        "index": self.index,
        "kind": self.kind,
        "available": self.available(),
        "requires": tuple(self.requires),
        "description": self.description or type(self).__doc__ or "",
    }

render(spec)

按规格自身的类型分发到对应的 render_* 方法。

这是一个便捷入口,等价于 engine.render_roundrect(spec) 之类, 但调用方不需要知道规格的具体类型。

源代码位于: tkdeft\engines\base.py
409
410
411
412
413
414
415
416
417
418
419
420
def render(self, spec: Spec):
    """按规格自身的类型分发到对应的 ``render_*`` 方法。

    这是一个便捷入口,等价于 ``engine.render_roundrect(spec)`` 之类,
    但调用方不需要知道规格的具体类型。
    """
    renderer = getattr(self, f"render_{spec.kind}", None)
    if renderer is None:
        raise NotImplementedError(
            f"{self.name} 不支持图元 {spec.kind!r}"
        )
    return renderer(spec)

render_roundrect(spec)

渲染圆角矩形,返回 PIL.Image.Image(RGBA)。

:raises NotImplementedError: 该引擎不支持这条快速路径 (SVG 引擎会走既有的 svgwrite + tksvg 流程)。

源代码位于: tkdeft\engines\base.py
393
394
395
396
397
398
399
def render_roundrect(self, spec: RoundRectSpec):
    """渲染圆角矩形,返回 ``PIL.Image.Image``(RGBA)。

    :raises NotImplementedError: 该引擎不支持这条快速路径
        (SVG 引擎会走既有的 svgwrite + tksvg 流程)。
    """
    raise NotImplementedError

render_thumb(spec)

渲染滑块把手,返回 PIL.Image.Image(RGBA)。

源代码位于: tkdeft\engines\base.py
405
406
407
def render_thumb(self, spec: ThumbSpec):
    """渲染滑块把手,返回 ``PIL.Image.Image``(RGBA)。"""
    raise NotImplementedError

render_track(spec)

渲染滑块进度条槽,返回 PIL.Image.Image(RGBA)。

源代码位于: tkdeft\engines\base.py
401
402
403
def render_track(self, spec: TrackSpec):
    """渲染滑块进度条槽,返回 ``PIL.Image.Image``(RGBA)。"""
    raise NotImplementedError

supports(kind)

是否支持某类图元("roundrect" / "track" / "thumb")。

源代码位于: tkdeft\engines\base.py
383
384
385
def supports(self, kind: str) -> bool:
    """是否支持某类图元(``"roundrect"`` / ``"track"`` / ``"thumb"``)。"""
    return callable(getattr(self, f"render_{kind}", None))

ImageCache

单个 Tk 解释器对应的绘制结果缓存。

键是 (引擎名, 图元类型, spec);值是 (PhotoImage, 像素数)。 所有方法都是线程安全的(由模块级锁保护),可以直接在多线程里查询统计。

:param budget: 像素预算;超出后新规格不再写入缓存(见模块文档里的说明)

源代码位于: tkdeft\engines\cache.py
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
class ImageCache:
    """单个 Tk 解释器对应的绘制结果缓存。

    键是 ``(引擎名, 图元类型, spec)``;值是 ``(PhotoImage, 像素数)``。
    所有方法都是线程安全的(由模块级锁保护),可以直接在多线程里查询统计。

    :param budget: 像素预算;超出后新规格不再写入缓存(见模块文档里的说明)
    """

    __slots__ = ("_entries", "_pixels", "_budget", "_hits", "_misses", "_overflow")

    def __init__(self, budget: int = DEFAULT_PIXEL_BUDGET) -> None:
        self._entries: "OrderedDict[Any, Tuple[Any, int]]" = OrderedDict()
        self._pixels = 0
        self._budget = budget
        self._hits = 0
        self._misses = 0
        self._overflow = 0

    # -- 查询 -------------------------------------------------------------
    def get(self, key: Any):
        """取缓存图片;未命中返回 ``None``(同时累计命中率统计)。"""
        item = self._entries.get(key)
        if item is None:
            self._misses += 1
            return None
        self._hits += 1
        self._entries.move_to_end(key)
        return item[0]

    def __contains__(self, key: Any) -> bool:
        return key in self._entries

    def __len__(self) -> int:
        return len(self._entries)

    # -- 写入 -------------------------------------------------------------
    def put(self, key: Any, photo, pixels: int) -> bool:
        """写入缓存;超出预算时 **静默放弃**(已发出的图片保持有效)。

        :param key: ``(引擎名, 图元类型, spec)``
        :param photo: ``tkinter.PhotoImage``
        :param pixels: 该图片占用的像素数(用于预算核算)
        :returns: 是否真的写入了缓存;``False`` 表示这次只能"每次重新渲染"
        """
        if key in self._entries:
            return True
        if self._pixels + pixels > self._budget:
            self._overflow += 1
            return False
        self._entries[key] = (photo, pixels)
        self._pixels += pixels
        return True

    # -- 维护 -------------------------------------------------------------
    def clear(self) -> int:
        """清空缓存并返回被清掉的条目数。

        .. warning::
           已经发出去的 :class:`~tkinter.PhotoImage` 仍然由调用方(画布)持有,
           因此清空缓存不会让画面立刻变空白;真正的风险在于之后画布重新引用
           同一规格时会重新渲染。
        """
        count = len(self._entries)
        self._entries.clear()
        self._pixels = 0
        return count

    @property
    def budget(self) -> int:
        """像素预算。"""
        return self._budget

    @budget.setter
    def budget(self, value: int) -> None:
        self._budget = max(0, int(value))

    @property
    def pixels(self) -> int:
        """当前缓存占用的像素数。"""
        return self._pixels

    def stats(self) -> Dict[str, Any]:
        """这条缓存的统计信息(条目数、像素占用、命中率、溢出次数)。"""
        total = self._hits + self._misses
        return {
            "entries": len(self._entries),
            "pixels": self._pixels,
            "budget": self._budget,
            "hits": self._hits,
            "misses": self._misses,
            "hit_rate": (self._hits / total) if total else 0.0,
            "overflow": self._overflow,
        }

    def reset_stats(self) -> None:
        """把命中/未命中/溢出计数清零(不动缓存内容)。"""
        self._hits = self._misses = self._overflow = 0

budget property writable

像素预算。

pixels property

当前缓存占用的像素数。

clear()

清空缓存并返回被清掉的条目数。

.. warning:: 已经发出去的 :class:~tkinter.PhotoImage 仍然由调用方(画布)持有, 因此清空缓存不会让画面立刻变空白;真正的风险在于之后画布重新引用 同一规格时会重新渲染。

源代码位于: tkdeft\engines\cache.py
105
106
107
108
109
110
111
112
113
114
115
116
def clear(self) -> int:
    """清空缓存并返回被清掉的条目数。

    .. warning::
       已经发出去的 :class:`~tkinter.PhotoImage` 仍然由调用方(画布)持有,
       因此清空缓存不会让画面立刻变空白;真正的风险在于之后画布重新引用
       同一规格时会重新渲染。
    """
    count = len(self._entries)
    self._entries.clear()
    self._pixels = 0
    return count

get(key)

取缓存图片;未命中返回 None(同时累计命中率统计)。

源代码位于: tkdeft\engines\cache.py
70
71
72
73
74
75
76
77
78
def get(self, key: Any):
    """取缓存图片;未命中返回 ``None``(同时累计命中率统计)。"""
    item = self._entries.get(key)
    if item is None:
        self._misses += 1
        return None
    self._hits += 1
    self._entries.move_to_end(key)
    return item[0]

put(key, photo, pixels)

写入缓存;超出预算时 静默放弃(已发出的图片保持有效)。

:param key: (引擎名, 图元类型, spec) :param photo: tkinter.PhotoImage :param pixels: 该图片占用的像素数(用于预算核算) :returns: 是否真的写入了缓存;False 表示这次只能"每次重新渲染"

源代码位于: tkdeft\engines\cache.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
def put(self, key: Any, photo, pixels: int) -> bool:
    """写入缓存;超出预算时 **静默放弃**(已发出的图片保持有效)。

    :param key: ``(引擎名, 图元类型, spec)``
    :param photo: ``tkinter.PhotoImage``
    :param pixels: 该图片占用的像素数(用于预算核算)
    :returns: 是否真的写入了缓存;``False`` 表示这次只能"每次重新渲染"
    """
    if key in self._entries:
        return True
    if self._pixels + pixels > self._budget:
        self._overflow += 1
        return False
    self._entries[key] = (photo, pixels)
    self._pixels += pixels
    return True

reset_stats()

把命中/未命中/溢出计数清零(不动缓存内容)。

源代码位于: tkdeft\engines\cache.py
145
146
147
def reset_stats(self) -> None:
    """把命中/未命中/溢出计数清零(不动缓存内容)。"""
    self._hits = self._misses = self._overflow = 0

stats()

这条缓存的统计信息(条目数、像素占用、命中率、溢出次数)。

源代码位于: tkdeft\engines\cache.py
132
133
134
135
136
137
138
139
140
141
142
143
def stats(self) -> Dict[str, Any]:
    """这条缓存的统计信息(条目数、像素占用、命中率、溢出次数)。"""
    total = self._hits + self._misses
    return {
        "entries": len(self._entries),
        "pixels": self._pixels,
        "budget": self._budget,
        "hits": self._hits,
        "misses": self._misses,
        "hit_rate": (self._hits / total) if total else 0.0,
        "overflow": self._overflow,
    }

RoundRectSpec dataclass

Bases: _Spec

一个圆角矩形(可带纯色/渐变描边)的完整描述。

坐标系以自身左上角为 (0, 0);width / height 是位图尺寸, 不是坐标对——需要坐标时用 :meth:from_box。

:param width: 位图宽度(像素) :param height: 位图高度(像素) :param rx: 圆角半径(x 方向) :param ry: 圆角半径(y 方向);None 表示与 rx 相同 :param fill: 填充色;None / "transparent" 表示不填充 :param fill_opacity: 填充透明度,0–1 :param outline: 描边色;None 表示不描边 :param outline_opacity: 描边透明度 :param outline2: 渐变描边的第二个颜色;为空则用纯色描边 :param outline2_opacity: 渐变描边第二个颜色的透明度 :param outline_width: 描边宽度(描边居中,几何会自动内缩半个线宽) :param gradient_stop1: 渐变第一个 stop 的位置(0–1) :param gradient_stop2: 渐变第二个 stop 的位置(0–1)

源代码位于: tkdeft\engines\base.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
@dataclass(frozen=True)
class RoundRectSpec(_Spec):
    """一个圆角矩形(可带纯色/渐变描边)的完整描述。

    坐标系以自身左上角为 ``(0, 0)``;``width`` / ``height`` 是**位图尺寸**,
    不是坐标对——需要坐标时用 :meth:`from_box`。

    :param width: 位图宽度(像素)
    :param height: 位图高度(像素)
    :param rx: 圆角半径(x 方向)
    :param ry: 圆角半径(y 方向);``None`` 表示与 ``rx`` 相同
    :param fill: 填充色;``None`` / ``"transparent"`` 表示不填充
    :param fill_opacity: 填充透明度,``0``–``1``
    :param outline: 描边色;``None`` 表示不描边
    :param outline_opacity: 描边透明度
    :param outline2: 渐变描边的第二个颜色;为空则用纯色描边
    :param outline2_opacity: 渐变描边第二个颜色的透明度
    :param outline_width: 描边宽度(描边居中,几何会自动内缩半个线宽)
    :param gradient_stop1: 渐变第一个 stop 的位置(0–1)
    :param gradient_stop2: 渐变第二个 stop 的位置(0–1)
    """

    kind: ClassVar[str] = "roundrect"

    width: int
    height: int
    rx: float = 0.0
    ry: Optional[float] = None
    fill: Optional[str] = None
    fill_opacity: float = 1.0
    outline: Optional[str] = None
    outline_opacity: float = 1.0
    outline2: Optional[str] = None
    outline2_opacity: float = 1.0
    outline_width: float = 1.0
    # 渐变描边的两个 stop 位置(与既有 svgwrite 实现一致:0.9 / 1.0)
    gradient_stop1: float = 0.9
    gradient_stop2: float = 1.0

    @property
    def effective_ry(self) -> float:
        """实际生效的 y 方向圆角半径(``ry`` 为空时取 ``rx``)。"""
        return self.rx if self.ry is None else self.ry

    @classmethod
    def from_box(
        cls,
        x1: Number,
        y1: Number,
        x2: Number,
        y2: Number,
        radius: Number = 0,
        radiusy: Optional[Number] = None,
        **kwargs: Any,
    ) -> "RoundRectSpec":
        """按 ``(x1, y1)-(x2, y2)`` 的坐标对构造规格。

        这是给画布类 API 用的便捷入口:画布上的控件习惯用"两个角"描述矩形,
        而规格本身只关心位图尺寸。

        :returns: 新的 :class:`RoundRectSpec`;``radiusy`` 为空时保持 ``None``
            (由 :attr:`effective_ry` 回退到 ``rx``)。
        """
        return cls(
            width=max(1, int(round(float(x2) - float(x1)))),
            height=max(1, int(round(float(y2) - float(y1)))),
            rx=float(radius or 0),
            ry=None if radiusy is None else float(radiusy),
            **kwargs,
        )

effective_ry property

实际生效的 y 方向圆角半径(ry 为空时取 rx)。

from_box(x1, y1, x2, y2, radius=0, radiusy=None, **kwargs) classmethod

按 (x1, y1)-(x2, y2) 的坐标对构造规格。

这是给画布类 API 用的便捷入口:画布上的控件习惯用"两个角"描述矩形, 而规格本身只关心位图尺寸。

:returns: 新的 :class:RoundRectSpec;radiusy 为空时保持 None (由 :attr:effective_ry 回退到 rx)。

源代码位于: tkdeft\engines\base.py
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
@classmethod
def from_box(
    cls,
    x1: Number,
    y1: Number,
    x2: Number,
    y2: Number,
    radius: Number = 0,
    radiusy: Optional[Number] = None,
    **kwargs: Any,
) -> "RoundRectSpec":
    """按 ``(x1, y1)-(x2, y2)`` 的坐标对构造规格。

    这是给画布类 API 用的便捷入口:画布上的控件习惯用"两个角"描述矩形,
    而规格本身只关心位图尺寸。

    :returns: 新的 :class:`RoundRectSpec`;``radiusy`` 为空时保持 ``None``
        (由 :attr:`effective_ry` 回退到 ``rx``)。
    """
    return cls(
        width=max(1, int(round(float(x2) - float(x1)))),
        height=max(1, int(round(float(y2) - float(y1)))),
        rx=float(radius or 0),
        ry=None if radiusy is None else float(radiusy),
        **kwargs,
    )

SvgEngine

Bases: DrawEngine

SVG 系引擎的占位实现。

它们不参与栅格快速路径——真正的绘制仍由各组件里既有的 Flu*Draw 代码 完成,这里只用于在注册表里表达"当前选中的是哪个引擎"。

源代码位于: tkdeft\engines\base.py
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
class SvgEngine(DrawEngine):
    """SVG 系引擎的占位实现。

    它们不参与栅格快速路径——真正的绘制仍由各组件里既有的 ``Flu*Draw`` 代码
    完成,这里只用于在注册表里表达"当前选中的是哪个引擎"。
    """

    kind = "svg"

    def render_roundrect(self, spec):  # pragma: no cover - 有意不支持
        raise NotImplementedError(
            f"{self.name} 是 SVG 引擎,请走既有的 svgwrite + tksvg 路径"
        )

    render_track = render_roundrect
    render_thumb = render_roundrect

    def supports(self, kind: str) -> bool:
        """SVG 引擎不参与栅格快速路径,因此对任何图元都返回 ``False``。"""
        return False

supports(kind)

SVG 引擎不参与栅格快速路径,因此对任何图元都返回 False。

源代码位于: tkdeft\engines\base.py
458
459
460
def supports(self, kind: str) -> bool:
    """SVG 引擎不参与栅格快速路径,因此对任何图元都返回 ``False``。"""
    return False

ThumbSpec dataclass

Bases: _Spec

滑块的圆形把手:外圈渐变伪阴影 + 外填充 + 内填充。

:param width: 位图宽度(通常等于把手直径 + 阴影余量) :param height: 位图高度 :param r1: 外圆半径(伪阴影所在的那一圈) :param r2: 内圆半径 :param fill: 外填充色(画在 r1 - 1 的圆里) :param fill_opacity: 外填充透明度 :param outline: 伪阴影渐变的第一个颜色 :param outline_opacity: 伪阴影第一个颜色的透明度 :param outline2: 伪阴影渐变的第二个颜色 :param outline2_opacity: 伪阴影第二个颜色的透明度 :param inner_fill: 内圆填充色 :param inner_fill_opacity: 内圆填充透明度

源代码位于: tkdeft\engines\base.py
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
@dataclass(frozen=True)
class ThumbSpec(_Spec):
    """滑块的圆形把手:外圈渐变伪阴影 + 外填充 + 内填充。

    :param width: 位图宽度(通常等于把手直径 + 阴影余量)
    :param height: 位图高度
    :param r1: 外圆半径(伪阴影所在的那一圈)
    :param r2: 内圆半径
    :param fill: 外填充色(画在 ``r1 - 1`` 的圆里)
    :param fill_opacity: 外填充透明度
    :param outline: 伪阴影渐变的第一个颜色
    :param outline_opacity: 伪阴影第一个颜色的透明度
    :param outline2: 伪阴影渐变的第二个颜色
    :param outline2_opacity: 伪阴影第二个颜色的透明度
    :param inner_fill: 内圆填充色
    :param inner_fill_opacity: 内圆填充透明度
    """

    kind: ClassVar[str] = "thumb"

    width: int
    height: int
    r1: float
    r2: float
    fill: Optional[str] = None
    fill_opacity: float = 1.0
    outline: Optional[str] = None
    outline_opacity: float = 1.0
    outline2: Optional[str] = None
    outline2_opacity: float = 1.0
    inner_fill: Optional[str] = None
    inner_fill_opacity: float = 1.0

    @classmethod
    def from_box(
        cls,
        x1: Number,
        y1: Number,
        width: Number,
        height: Number,
        r1: Number,
        r2: Number,
        **kwargs: Any,
    ) -> "ThumbSpec":
        """按画布坐标构造规格(``x1`` / ``y1`` 只影响摆放,不影响位图内容)。"""
        return cls(
            width=max(1, int(round(float(width)))),
            height=max(1, int(round(float(height)))),
            r1=float(r1),
            r2=float(r2),
            **kwargs,
        )

from_box(x1, y1, width, height, r1, r2, **kwargs) classmethod

按画布坐标构造规格(x1 / y1 只影响摆放,不影响位图内容)。

源代码位于: tkdeft\engines\base.py
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
@classmethod
def from_box(
    cls,
    x1: Number,
    y1: Number,
    width: Number,
    height: Number,
    r1: Number,
    r2: Number,
    **kwargs: Any,
) -> "ThumbSpec":
    """按画布坐标构造规格(``x1`` / ``y1`` 只影响摆放,不影响位图内容)。"""
    return cls(
        width=max(1, int(round(float(width)))),
        height=max(1, int(round(float(height)))),
        r1=float(r1),
        r2=float(r2),
        **kwargs,
    )

TksvgEngine

Bases: SvgEngine

svgwrite 生成 SVG → tksvg 栅格化(默认引擎,保持历史行为)。

源代码位于: tkdeft\engines\__init__.py
150
151
152
153
154
155
class TksvgEngine(SvgEngine):
    """``svgwrite`` 生成 SVG → tksvg 栅格化(默认引擎,保持历史行为)。"""

    name = "tksvg"
    requires = ("tksvg",)
    description = "svgwrite + tksvg(默认,保持既有行为)"

TrackSpec dataclass

Bases: _Spec

滑块的进度条槽:左半段(已选中)+ 右半段(未选中)两个圆角矩形。

:param width: 位图宽度 :param height: 位图高度 :param width2: 左半段(选中部分)的宽度,会被夹到 0..width :param radius: 圆角半径 :param track_fill: 选中部分的颜色 :param track_opacity: 选中部分的透明度 :param rail_fill: 未选中底轨的颜色 :param rail_opacity: 未选中底轨的透明度

源代码位于: tkdeft\engines\base.py
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
@dataclass(frozen=True)
class TrackSpec(_Spec):
    """滑块的进度条槽:左半段(已选中)+ 右半段(未选中)两个圆角矩形。

    :param width: 位图宽度
    :param height: 位图高度
    :param width2: 左半段(选中部分)的宽度,会被夹到 ``0..width``
    :param radius: 圆角半径
    :param track_fill: 选中部分的颜色
    :param track_opacity: 选中部分的透明度
    :param rail_fill: 未选中底轨的颜色
    :param rail_opacity: 未选中底轨的透明度
    """

    kind: ClassVar[str] = "track"

    width: int
    height: int
    width2: float
    radius: float = 3.0
    track_fill: Optional[str] = None
    track_opacity: float = 1.0
    rail_fill: Optional[str] = None
    rail_opacity: float = 1.0

    @classmethod
    def from_box(
        cls,
        x1: Number,
        y1: Number,
        width: Number,
        height: Number,
        width2: Number,
        **kwargs: Any,
    ) -> "TrackSpec":
        """按画布坐标构造规格(``x1`` / ``y1`` 只影响摆放,不影响位图内容)。"""
        return cls(
            width=max(1, int(round(float(width)))),
            height=max(1, int(round(float(height)))),
            width2=float(width2),
            **kwargs,
        )

from_box(x1, y1, width, height, width2, **kwargs) classmethod

按画布坐标构造规格(x1 / y1 只影响摆放,不影响位图内容)。

源代码位于: tkdeft\engines\base.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
@classmethod
def from_box(
    cls,
    x1: Number,
    y1: Number,
    width: Number,
    height: Number,
    width2: Number,
    **kwargs: Any,
) -> "TrackSpec":
    """按画布坐标构造规格(``x1`` / ``y1`` 只影响摆放,不影响位图内容)。"""
    return cls(
        width=max(1, int(round(float(width)))),
        height=max(1, int(round(float(height)))),
        width2=float(width2),
        **kwargs,
    )

UnknownEngineError

Bases: ValueError

请求了注册表里不存在的绘制引擎。

继承自 :class:ValueError,因此历史代码里的 except ValueError 依旧有效。

源代码位于: tkdeft\engines\base.py
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
class UnknownEngineError(ValueError):
    """请求了注册表里不存在的绘制引擎。

    继承自 :class:`ValueError`,因此历史代码里的 ``except ValueError`` 依旧有效。
    """

    def __init__(self, name: Any, available: Optional[List[str]] = None) -> None:
        self.name = name
        self.available = list(available or [])
        message = f"未知的绘制引擎 {name!r}"
        if self.available:
            message += f";可用:{sorted(self.available)}"
        hint = _suggest_name(str(name), self.available)
        if hint:
            message += f"(你是不是想用 {hint!r}?)"
        super().__init__(message)

WandEngine

Bases: SvgEngine

svgwrite 生成 SVG → Wand(ImageMagick) 转 PNG(历史 renderer=1)。

源代码位于: tkdeft\engines\__init__.py
158
159
160
161
162
163
class WandEngine(SvgEngine):
    """``svgwrite`` 生成 SVG → Wand(ImageMagick) 转 PNG(历史 ``renderer=1``)。"""

    name = "wand"
    requires = ("wand",)
    description = "svgwrite + Wand(ImageMagick)"

available_engines()

当前环境里真正能用的引擎名列表。

源代码位于: tkdeft\engines\base.py
531
532
533
def available_engines() -> List[str]:
    """当前环境里真正能用的引擎名列表。"""
    return [name for name, ok in list_engines().items() if ok]

cache_stats(master=None)

缓存统计,便于性能诊断。

:param master: 只看某个解释器;None 表示汇总所有解释器 (额外带一个 interpreters 字段)

::

>>> cache_stats()
{'interpreters': 1, 'entries': 62, 'pixels': 169244, 'budget': 8000000,
 'hits': 178, 'misses': 99, 'hit_rate': 0.642, 'overflow': 0}
源代码位于: tkdeft\engines\cache.py
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
def cache_stats(master=None) -> Dict[str, Any]:
    """缓存统计,便于性能诊断。

    :param master: 只看某个解释器;``None`` 表示汇总所有解释器
        (额外带一个 ``interpreters`` 字段)

    ::

        >>> cache_stats()
        {'interpreters': 1, 'entries': 62, 'pixels': 169244, 'budget': 8000000,
         'hits': 178, 'misses': 99, 'hit_rate': 0.642, 'overflow': 0}
    """
    with _LOCK:
        if master is not None:
            return _cache_for(master).stats()
        merged: Dict[str, Any] = {
            "interpreters": len(_CACHES),
            "entries": 0,
            "pixels": 0,
            "budget": _PIXEL_BUDGET,
            "hits": 0,
            "misses": 0,
            "overflow": 0,
        }
        for _, cache in _CACHES.values():
            stats = cache.stats()
            for field in ("entries", "pixels", "hits", "misses", "overflow"):
                merged[field] += stats[field]
        total = merged["hits"] + merged["misses"]
        merged["hit_rate"] = (merged["hits"] / total) if total else 0.0
        return merged

clear_cache(master=None)

清空缓存,返回被清掉的条目数。

:param master: 只清某个解释器的缓存;None 表示清空全部解释器

.. warning:: 清空后,仍然存在于画布上的 item 会因为图片被回收而变空白。 仅在确实要重建整个界面时调用。

源代码位于: tkdeft\engines\cache.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
def clear_cache(master=None) -> int:
    """清空缓存,返回被清掉的条目数。

    :param master: 只清某个解释器的缓存;``None`` 表示清空全部解释器

    .. warning::
       清空后,仍然存在于画布上的 item 会因为图片被回收而变空白。
       仅在确实要重建整个界面时调用。
    """
    with _LOCK:
        if master is None:
            total = sum(cache.clear() for _, cache in _CACHES.values())
            _CACHES.clear()
            return total
        return _cache_for(master).clear()

clear_engine_error()

清掉失败记录,并允许下次失败时重新发出一次警告。

源代码位于: tkdeft\engines\__init__.py
194
195
196
197
198
def clear_engine_error() -> None:
    """清掉失败记录,并允许下次失败时重新发出一次警告。"""
    global engine_error
    engine_error = None
    _WARNED.clear()

describe_engines()

所有引擎的结构化信息列表(按编号排序,含别名与"是否当前")。

与 :func:list_engines 相比,这里的信息更适合直接打印或喂给 CLI/文档。

源代码位于: tkdeft\engines\base.py
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
def describe_engines() -> List[Dict[str, Any]]:
    """所有引擎的结构化信息列表(按编号排序,含别名与"是否当前")。

    与 :func:`list_engines` 相比,这里的信息更适合直接打印或喂给 CLI/文档。
    """
    aliases: Dict[str, List[str]] = {}
    for key, engine in _ENGINES.items():
        if key != engine.name:
            aliases.setdefault(engine.name, []).append(key)
    rows = []
    for name, engine in [(n, _ENGINES[n]) for n in list_engines()]:
        info = engine.info()
        info["aliases"] = tuple(sorted(aliases.get(name, ())))
        info["current"] = name == _CURRENT_NAME
        rows.append(info)
    return rows

engine_from_index(index)

把历史编号翻译成引擎名。

:raises UnknownEngineError: 编号不在 :data:RENDERER_INDEX 里

源代码位于: tkdeft\engines\base.py
550
551
552
553
554
555
556
557
558
def engine_from_index(index: Any) -> str:
    """把历史编号翻译成引擎名。

    :raises UnknownEngineError: 编号不在 :data:`RENDERER_INDEX` 里
    """
    try:
        return RENDERER_INDEX[int(index)]
    except (KeyError, TypeError, ValueError):
        raise UnknownEngineError(index, list(RENDERER_INDEX.values())) from None

engine_index(name=None)

取引擎对应的历史编号;None 表示当前引擎。

:raises UnknownEngineError: 引擎不存在 :returns: 0–4;自定义引擎不在 :data:RENDERER_INDEX 里时返回 -1

源代码位于: tkdeft\engines\base.py
541
542
543
544
545
546
547
def engine_index(name: Optional[str] = None) -> int:
    """取引擎对应的历史编号;``None`` 表示当前引擎。

    :raises UnknownEngineError: 引擎不存在
    :returns: ``0``–``4``;自定义引擎不在 :data:`RENDERER_INDEX` 里时返回 ``-1``
    """
    return get_engine(name).index

engine_names()

注册表里的全部引擎名(不过滤可用性)。

源代码位于: tkdeft\engines\base.py
536
537
538
def engine_names() -> List[str]:
    """注册表里的全部引擎名(不过滤可用性)。"""
    return list(list_engines())

get_cache(master)

取(必要时创建)某个 Tk 解释器对应的缓存。

:param master: 任意 Tk 控件,或 tkapp 本身 :returns: 绑定到该解释器的 :class:ImageCache

源代码位于: tkdeft\engines\cache.py
196
197
198
199
200
201
202
def get_cache(master) -> ImageCache:
    """取(必要时创建)某个 Tk 解释器对应的缓存。

    :param master: 任意 Tk 控件,或 ``tkapp`` 本身
    :returns: 绑定到该解释器的 :class:`ImageCache`
    """
    return _cache_for(master)

get_engine(name=None, *, strict=True)

取引擎实例。

:param name: 引擎名(大小写不敏感,可以是别名);None 表示当前引擎 :param strict: 名字未知时抛 :class:UnknownEngineError(默认); 置 False 则退回当前引擎,兼容历史行为 :raises UnknownEngineError: strict=True 且名字未知

源代码位于: tkdeft\engines\base.py
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
def get_engine(name: Optional[str] = None, *, strict: bool = True) -> DrawEngine:
    """取引擎实例。

    :param name: 引擎名(大小写不敏感,可以是别名);``None`` 表示**当前引擎**
    :param strict: 名字未知时抛 :class:`UnknownEngineError`(默认);
        置 ``False`` 则退回当前引擎,兼容历史行为
    :raises UnknownEngineError: ``strict=True`` 且名字未知
    """
    if name is None:
        engine = _ENGINES.get(_CURRENT_NAME)
        if engine is None:  # 理论上不会发生:注册表至少有一个默认引擎
            raise RuntimeError("tkdeft 引擎注册表为空,无法解析绘制引擎")
        return engine

    engine = _ENGINES.get(_key(name))
    if engine is not None:
        return engine
    if strict:
        raise UnknownEngineError(name, list(list_engines()))
    current = _ENGINES.get(_CURRENT_NAME)
    if current is None:
        raise RuntimeError("tkdeft 引擎注册表为空,无法解析绘制引擎")
    return current

get_engine_name()

当前引擎的名字(比编号直观)。

源代码位于: tkdeft\engines\base.py
604
605
606
def get_engine_name() -> str:
    """当前引擎的名字(比编号直观)。"""
    return _CURRENT_NAME

last_engine_error()

最近一次渲染失败的原因;一切正常时返回 None。

比直接读 :data:engine_error 更明确,也不会让静态检查器以为你在读、 写一个普通全局变量。

源代码位于: tkdeft\engines\__init__.py
185
186
187
188
189
190
191
def last_engine_error() -> Optional[str]:
    """最近一次渲染失败的原因;一切正常时返回 ``None``。

    比直接读 :data:`engine_error` 更明确,也不会让静态检查器以为你在读、
    写一个普通全局变量。
    """
    return engine_error

lerp_rgba(c1, c2, t)

在两个 RGBA 之间线性插值(t 会被裁剪到 [0, 1])。

:param c1: 起点颜色(t <= 0 时原样返回) :param c2: 终点颜色(t >= 1 时原样返回) :param t: 插值位置

源代码位于: tkdeft\engines\colors.py
179
180
181
182
183
184
185
186
187
188
189
190
191
192
def lerp_rgba(c1: RGBA, c2: RGBA, t: float) -> RGBA:
    """在两个 RGBA 之间线性插值(``t`` 会被裁剪到 ``[0, 1]``)。

    :param c1: 起点颜色(``t <= 0`` 时原样返回)
    :param c2: 终点颜色(``t >= 1`` 时原样返回)
    :param t: 插值位置
    """
    if t <= 0.0:
        return c1
    if t >= 1.0:
        return c2
    return tuple(  # type: ignore[return-value]
        int(round(c1[i] + (c2[i] - c1[i]) * t)) for i in range(4)
    )

list_engines()

返回 {引擎名: 是否可用}(只列主名,不含别名,按编号排序)。

::

>>> list_engines()
{'tksvg': True, 'wand': True, 'skia': True, 'pillow': True, 'cairo': True}
源代码位于: tkdeft\engines\base.py
519
520
521
522
523
524
525
526
527
528
def list_engines() -> Dict[str, bool]:
    """返回 ``{引擎名: 是否可用}``(只列主名,不含别名,按编号排序)。

    ::

        >>> list_engines()
        {'tksvg': True, 'wand': True, 'skia': True, 'pillow': True, 'cairo': True}
    """
    listed = {name: engine.available() for name, engine in _ENGINES.items() if engine.name == name}
    return dict(sorted(listed.items(), key=lambda item: (_NAME_TO_INDEX.get(item[0], 99), item[0])))

normalize_hex(value)

把 #FFF / #FFFFFF 统一成小写 6 位形式(不含 alpha)。

:param value: 形如 "#fff" / "FFF" / "#F0F0F0" 的字符串 :returns: "#rrggbb";超过 6 位的部分(alpha)会被截掉

源代码位于: tkdeft\engines\colors.py
45
46
47
48
49
50
51
52
53
54
def normalize_hex(value: str) -> str:
    """把 ``#FFF`` / ``#FFFFFF`` 统一成小写 6 位形式(不含 alpha)。

    :param value: 形如 ``"#fff"`` / ``"FFF"`` / ``"#F0F0F0"`` 的字符串
    :returns: ``"#rrggbb"``;超过 6 位的部分(alpha)会被截掉
    """
    h = value.strip().lstrip("#")
    if len(h) == 3:
        h = "".join(ch * 2 for ch in h)
    return "#" + h[:6].lower()

parse_color(value, opacity=1.0)

解析颜色 + 透明度。

:param value: 颜色写法(见模块文档);None / "transparent" 表示不绘制 :param opacity: 外层透明度,会与颜色自带的 alpha 相乘(元组色与 #RRGGBBAA 都适用) :returns: (r, g, b, a);当颜色表示"不绘制"、解析失败或最终 alpha 为 0 时 返回 None,调用方据此完全跳过这一层的绘制

::

parse_color("#ffffff", 0.2)   # (255, 255, 255, 51)
parse_color((0, 0, 0, 128))   # (0, 0, 0, 128)
parse_color("transparent")    # None
源代码位于: tkdeft\engines\colors.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
def parse_color(value, opacity=1.0) -> Optional[RGBA]:
    """解析颜色 + 透明度。

    :param value: 颜色写法(见模块文档);``None`` / ``"transparent"`` 表示不绘制
    :param opacity: 外层透明度,会与颜色自带的 alpha **相乘**(元组色与
        ``#RRGGBBAA`` 都适用)
    :returns: ``(r, g, b, a)``;当颜色表示"不绘制"、解析失败或最终 alpha 为 0 时
        返回 ``None``,调用方据此完全跳过这一层的绘制

    ::

        parse_color("#ffffff", 0.2)   # (255, 255, 255, 51)
        parse_color((0, 0, 0, 128))   # (0, 0, 0, 128)
        parse_color("transparent")    # None
    """
    alpha = parse_opacity(opacity)

    if value is None:
        return None

    if isinstance(value, (tuple, list)):
        parts = list(value)
        if len(parts) == 3:
            r, g, b = (int(max(0, min(255, p))) for p in parts)
            a = int(round(alpha * 255))
        elif len(parts) >= 4:
            r, g, b = (int(max(0, min(255, p))) for p in parts[:3])
            a = int(max(0, min(255, parts[3])))
            # 元组自带 alpha 时,外层 opacity 视为额外调制
            a = int(round(a * alpha))
        else:
            return None
        return (r, g, b, a) if a > 0 else None

    if not isinstance(value, str):
        return None

    text = value.strip()
    if text.lower() in _TRANSPARENT_WORDS:
        return None

    rgb: Optional[Tuple[int, int, int]] = None
    embedded_alpha: Optional[int] = None

    if text.startswith("#"):
        h = text[1:]
        if len(h) == 3:
            h = "".join(ch * 2 for ch in h)
        if len(h) == 4:  # #RGBA
            h = "".join(ch * 2 for ch in h)
        if len(h) == 8:  # #RRGGBBAA
            try:
                embedded_alpha = int(h[6:8], 16)
            except ValueError:
                return None
            h = h[:6]
        if len(h) != 6:
            return None
        try:
            rgb = (int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16))
        except ValueError:
            return None
    else:
        rgb = _named_to_rgb(text)

    if rgb is None:
        return None

    a = alpha if embedded_alpha is None else (embedded_alpha / 255.0) * alpha
    a_int = int(round(a * 255))
    if a_int <= 0:
        return None
    return (rgb[0], rgb[1], rgb[2], a_int)

parse_opacity(value, default=1.0)

把透明度统一成 [0, 1] 的 float。

:param value: float / int / 数字字符串;None 表示用 default :param default: 解析失败(或 None)时的回退值 :returns: 夹到 [0, 1] 的浮点数;NaN 与非法值都回退到 default

源代码位于: tkdeft\engines\colors.py
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
def parse_opacity(value, default: float = 1.0) -> float:
    """把透明度统一成 ``[0, 1]`` 的 float。

    :param value: ``float`` / ``int`` / 数字字符串;``None`` 表示用 ``default``
    :param default: 解析失败(或 ``None``)时的回退值
    :returns: 夹到 ``[0, 1]`` 的浮点数;``NaN`` 与非法值都回退到 ``default``
    """
    if value is None:
        return default
    try:
        result = float(value)
    except (TypeError, ValueError):
        return default
    if result != result:  # NaN
        return default
    if result < 0.0:
        return 0.0
    if result > 1.0:
        return 1.0
    return result

register_engine(engine, *, aliases=(), default=False)

把一个引擎实例登记进注册表。

:param engine: 引擎实例 :param aliases: 额外的别名(例如 ("2",) 让 set_engine(2) 也能命中), 大小写不敏感 :param default: 是否同时把它设为当前引擎;注册表为空时也会自动成为当前引擎 :returns: 传入的 engine,方便写成 register_engine(MyEngine(), aliases=...)

重复注册同名引擎会覆盖旧的实例(便于测试里替换引擎)。

源代码位于: tkdeft\engines\base.py
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
def register_engine(
    engine: DrawEngine,
    *,
    aliases: Tuple[str, ...] = (),
    default: bool = False,
) -> DrawEngine:
    """把一个引擎实例登记进注册表。

    :param engine: 引擎实例
    :param aliases: 额外的别名(例如 ``("2",)`` 让 ``set_engine(2)`` 也能命中),
        大小写不敏感
    :param default: 是否同时把它设为当前引擎;注册表为空时也会自动成为当前引擎
    :returns: 传入的 ``engine``,方便写成 ``register_engine(MyEngine(), aliases=...)``

    重复注册同名引擎会覆盖旧的实例(便于测试里替换引擎)。
    """
    _ENGINES[engine.name] = engine
    for alias in aliases:
        _ENGINES[_key(alias)] = engine
    if default or _CURRENT_NAME not in _ENGINES:
        _set_current(engine)
    return engine

render(spec, master)

渲染任意一种绘制规格,按规格类型自动分发。

:param spec: :class:~tkdeft.engines.base.RoundRectSpec / :class:~tkdeft.engines.base.TrackSpec / :class:~tkdeft.engines.base.ThumbSpec :param master: 目标 Tk 控件 :returns: PhotoImage;当前引擎不支持时返回 None

::

photo = render(RoundRectSpec(width=120, height=32, rx=6), master=canvas)
源代码位于: tkdeft\engines\__init__.py
266
267
268
269
270
271
272
273
274
275
276
277
278
279
def render(spec: Spec, master) -> Optional[Any]:
    """渲染任意一种绘制规格,按规格类型自动分发。

    :param spec: :class:`~tkdeft.engines.base.RoundRectSpec` /
        :class:`~tkdeft.engines.base.TrackSpec` /
        :class:`~tkdeft.engines.base.ThumbSpec`
    :param master: 目标 Tk 控件
    :returns: ``PhotoImage``;当前引擎不支持时返回 ``None``

    ::

        photo = render(RoundRectSpec(width=120, height=32, rx=6), master=canvas)
    """
    return _render(spec.kind, spec, master)

render_roundrect(spec, master)

渲染圆角矩形;当前引擎不支持时返回 None。

源代码位于: tkdeft\engines\__init__.py
282
283
284
def render_roundrect(spec: RoundRectSpec, master) -> Optional[Any]:
    """渲染圆角矩形;当前引擎不支持时返回 ``None``。"""
    return _render("roundrect", spec, master)

render_thumb(spec, master)

渲染滑块把手;当前引擎不支持时返回 None。

源代码位于: tkdeft\engines\__init__.py
292
293
294
def render_thumb(spec: ThumbSpec, master) -> Optional[Any]:
    """渲染滑块把手;当前引擎不支持时返回 ``None``。"""
    return _render("thumb", spec, master)

render_track(spec, master)

渲染滑块进度条槽;当前引擎不支持时返回 None。

源代码位于: tkdeft\engines\__init__.py
287
288
289
def render_track(spec: TrackSpec, master) -> Optional[Any]:
    """渲染滑块进度条槽;当前引擎不支持时返回 ``None``。"""
    return _render("track", spec, master)

reset_engine()

把当前引擎恢复为出厂默认(tksvg),返回该引擎。

源代码位于: tkdeft\engines\base.py
651
652
653
def reset_engine() -> DrawEngine:
    """把当前引擎恢复为出厂默认(``tksvg``),返回该引擎。"""
    return set_engine(DEFAULT_ENGINE_NAME)

set_cache_budget(pixels)

设置像素预算,并立即应用到所有已存在的解释器缓存。

:param pixels: 预算(像素数);None 表示恢复默认 :data:DEFAULT_PIXEL_BUDGET(注意:并不是"不限制")

源代码位于: tkdeft\engines\cache.py
222
223
224
225
226
227
228
229
230
231
232
def set_cache_budget(pixels: Optional[int]) -> None:
    """设置像素预算,并立即应用到所有已存在的解释器缓存。

    :param pixels: 预算(像素数);``None`` 表示恢复默认
        :data:`DEFAULT_PIXEL_BUDGET`(注意:并不是"不限制")
    """
    global _PIXEL_BUDGET
    with _LOCK:
        _PIXEL_BUDGET = DEFAULT_PIXEL_BUDGET if pixels is None else max(0, int(pixels))
        for _, cache in _CACHES.values():
            cache.budget = _PIXEL_BUDGET

set_engine(name)

切换当前引擎。

name 可以是引擎名("skia" / "pillow" / "tksvg" / "cairo" / "wand"),也可以是兼容旧 API 的整数编号:

===== ========== ========================================================== 编号 引擎 说明 ===== ========== ========================================================== 0 tksvg SVG 文件(默认,保持既有行为) 1 wand SVG → PNG(ImageMagick) 2 skia 进程内栅格,速度与画质最好 3 pillow 进程内栅格,零额外依赖,永远可用 4 cairo 进程内栅格(pycairo) ===== ========== ==========================================================

:raises UnknownEngineError: 引擎名/编号不在注册表里 :raises ValueError: 引擎存在但依赖缺失(报错会说明缺哪个包)

非法或不可用的引擎都会抛异常,避免静默降级导致"以为开了 skia 其实还在走慢路径"。

源代码位于: tkdeft\engines\base.py
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
def set_engine(name: Any) -> DrawEngine:
    """切换当前引擎。

    ``name`` 可以是引擎名(``"skia"`` / ``"pillow"`` / ``"tksvg"`` / ``"cairo"``
    / ``"wand"``),也可以是兼容旧 API 的整数编号:

    ===== ========== ==========================================================
    编号   引擎       说明
    ===== ========== ==========================================================
    0     tksvg      SVG 文件(默认,保持既有行为)
    1     wand       SVG → PNG(ImageMagick)
    2     skia       进程内栅格,速度与画质最好
    3     pillow     进程内栅格,零额外依赖,永远可用
    4     cairo      进程内栅格(pycairo)
    ===== ========== ==========================================================

    :raises UnknownEngineError: 引擎名/编号不在注册表里
    :raises ValueError: 引擎存在但依赖缺失(报错会说明缺哪个包)

    非法或不可用的引擎都会抛异常,避免静默降级导致"以为开了 skia 其实还在走慢路径"。
    """
    try:
        engine = get_engine(name)
    except UnknownEngineError:
        # 兼容 ``set_engine(2)`` 这种历史写法
        if isinstance(name, int) and not isinstance(name, bool):
            engine = get_engine(engine_from_index(name))
        else:
            raise
    if not engine.available():
        raise ValueError(
            f"绘制引擎 {engine.name!r} 不可用,缺少依赖:{engine.requires}"
        )
    _set_current(engine)
    return engine

to_photoimage(image, master)

PIL.Image → tkinter 的 PhotoImage。

:param image: PIL.Image.Image(RGBA) :param master: 目标 Tk 控件;必须传,否则图片会挂到默认根窗口, 多 Tk 解释器场景下会出问题 :returns: :class:tkinter.PhotoImage

源代码位于: tkdeft\engines\__init__.py
221
222
223
224
225
226
227
228
229
230
231
232
233
def to_photoimage(image, master) -> Any:
    """``PIL.Image`` → ``tkinter`` 的 ``PhotoImage``。

    :param image: ``PIL.Image.Image``(RGBA)
    :param master: 目标 Tk 控件;**必须传**,否则图片会挂到默认根窗口,
        多 Tk 解释器场景下会出问题
    :returns: :class:`tkinter.PhotoImage`
    """
    from PIL import ImageTk

    if master is None:
        return ImageTk.PhotoImage(image)
    return ImageTk.PhotoImage(image, master=master)

unregister_engine(name)

从注册表里移除一个引擎(连同它的别名)。

:returns: 是否真的移除了。移除当前引擎时,当前选择会回退到默认引擎 (默认引擎也没了就退到剩下的第一个)。

源代码位于: tkdeft\engines\base.py
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
def unregister_engine(name: str) -> bool:
    """从注册表里移除一个引擎(连同它的别名)。

    :returns: 是否真的移除了。移除当前引擎时,当前选择会回退到默认引擎
        (默认引擎也没了就退到剩下的第一个)。
    """
    global _CURRENT_NAME
    target = _ENGINES.get(_key(name))
    if target is None:
        return False
    for key in [k for k, engine in _ENGINES.items() if engine is target]:
        _ENGINES.pop(key, None)
    if _ENGINES and _CURRENT_NAME not in _ENGINES:
        if DEFAULT_ENGINE_NAME in _ENGINES:
            _CURRENT_NAME = DEFAULT_ENGINE_NAME
        else:
            _CURRENT_NAME = _ENGINES[next(iter(_ENGINES))].name
    return True