Render¶
render ¶
Rendering helpers for deux keys, cards, and touchscreen.
BackgroundLayer ¶
Own the SVG source, parsed XML root, and rasterised outputs for a background.
A single BackgroundLayer encapsulates one background SVG and its
derived artefacts: the parsed element tree, rasterised full image,
and (for touchstrip backgrounds) per-panel tiles. Two kinds are
supported:
"touchstrip"
A full-width SVG that is rasterised and sliced into per-panel
tiles. Requires panel_count, panel_width, and
panel_height.
"key"
A single-key SVG that is rasterised and encoded to device image
bytes. Requires key_size and key_image_format.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
``"touchstrip"`` or ``"key"``
|
Which surface this background belongs to. |
required |
panel_count
|
int
|
Number of panels (touchstrip only). |
0
|
panel_width
|
int
|
Width of each panel in pixels (touchstrip only). |
0
|
panel_height
|
int
|
Height of each panel in pixels (touchstrip only). |
0
|
key_size
|
tuple[int, int]
|
|
(0, 0)
|
key_image_format
|
str
|
Device image format for key encoding (key only). |
'JPEG'
|
Source code in src/deux/render/background_layer.py
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 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 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 195 196 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 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 266 267 268 269 270 271 272 273 | |
tiles
property
¶
Pre-sliced touchstrip tiles as PNG bytes (shallow copy), or None.
Only meaningful for kind="touchstrip".
key_image
property
¶
Pre-rendered key background image bytes, or None.
Only meaningful for kind="key".
tile ¶
Return the cached background tile for panel index, or None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
Panel index (0-based). |
required |
Returns:
| Type | Description |
|---|---|
bytes or None
|
PNG-encoded tile bytes, or |
Source code in src/deux/render/background_layer.py
set_svg ¶
Set the background SVG and rasterise immediately.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
svg_data
|
bytes
|
Raw SVG content as UTF-8 bytes. |
required |
trusted
|
bool
|
When |
False
|
Source code in src/deux/render/background_layer.py
set_svg_deferred ¶
Parse the SVG without rasterising (for async workflows).
Call :meth:rasterize separately (e.g. via asyncio.to_thread)
to perform the CPU-bound rasterisation step.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
svg_data
|
bytes
|
Raw SVG content as UTF-8 bytes. |
required |
trusted
|
bool
|
When |
False
|
Source code in src/deux/render/background_layer.py
rasterize ¶
Public entry point for rasterisation.
Useful for offloading to a thread via asyncio.to_thread.
No-op if no SVG is set.
clear ¶
invalidate ¶
Re-rasterize the current SVG (e.g. after stylesheet changes).
No-op if no SVG is set.
RenderingContext
dataclass
¶
Immutable-ish bag of rendering state carried through the pipeline.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
theme
|
Theme or None
|
The resolved theme for this render pass. |
None
|
stylesheet
|
str or None
|
CSS stylesheet text derived from theme. When set, this
overrides the module-level |
None
|
Examples:
::
from deux.render.context import RenderingContext
from deux.render.theme import Theme
theme = Theme.from_color(255, 0, 128)
ctx = RenderingContext(theme=theme, stylesheet=theme.css)
Source code in src/deux/render/context.py
from_theme
classmethod
¶
Create a context from a :class:Theme.
The stylesheet is derived automatically from theme.css.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
theme
|
Theme
|
The theme to derive the context from. |
required |
Returns:
| Type | Description |
|---|---|
RenderingContext
|
A new context with theme and its CSS. |
Source code in src/deux/render/context.py
resolve_stylesheet ¶
Return the effective stylesheet.
If an explicit stylesheet was provided it is returned.
Otherwise, if a theme is set, its CSS is used. Returns
None when neither is available.
Returns:
| Type | Description |
|---|---|
str or None
|
The CSS stylesheet text, or |
Source code in src/deux/render/context.py
SurfaceBackgrounds ¶
Bases: TypedDict
Mapping of surface type to raw SVG bytes.
A TypedDict with three optional keys; only those matching the
device's hardware capabilities are populated.
Keys
key : bytes Raw SVG bytes used as the default background for individual key images. touchscreen : bytes Raw SVG bytes used as the default background for the full touchscreen strip (Stream Deck+ family). screen : bytes Raw SVG bytes used as the default background for the secondary information screen (Stream Deck Neo and similar).
Source code in src/deux/render/defaults/__init__.py
ImageFetchError ¶
RenderMetrics ¶
Computed rendering metrics for a specific device.
All fields are derived from the device's
:class:~deux.runtime.capabilities.DeviceCapabilities — there are
no model-specific defaults. Panel dimensions are computed without
margins or gaps; consumers are responsible for any spacing they want
to apply in their own SVG/layout.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
caps
|
DeviceCapabilities
|
Device capabilities to derive metrics from. |
required |
Attributes:
| Name | Type | Description |
|---|---|---|
key_size |
tuple[int, int]
|
|
key_image_format |
str
|
Native key image format expected by the device (e.g. |
key_count |
int
|
Total number of physical keys on the device. |
touchscreen_width |
int
|
Width of the touchscreen surface in pixels, or |
touchscreen_height |
int
|
Height of the touchscreen surface in pixels, or |
panel_count |
int
|
Number of logical touchscreen panels (typically one per dial on
Stream Deck+), or |
panel_width |
int
|
Width of a single touchscreen panel in pixels. |
panel_height |
int
|
Height of a single touchscreen panel in pixels. |
screen_width |
int
|
Width of the secondary information screen in pixels, or |
screen_height |
int
|
Height of the secondary information screen in pixels, or |
dial_count |
int
|
Number of rotary encoders (dials) on the device. |
Source code in src/deux/render/metrics.py
__init__ ¶
Derive rendering metrics from device capabilities.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
caps
|
DeviceCapabilities
|
Capabilities of the device the metrics apply to. All attributes are computed eagerly from this object; the capabilities reference is retained for later inspection. |
required |
Source code in src/deux/render/metrics.py
RenderProfiler ¶
Collect wall-clock timings for named rendering steps.
Each profiler instance tracks a single logical operation (e.g.
"render_screen_complete") and records sub-step timings via the
:meth:step context manager. Steps may be nested: a child profiler
can be attached to record finer-grained breakdowns.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Human-readable name for the operation being profiled. |
required |
parent
|
RenderProfiler or None
|
Parent profiler for nested timing trees. When set, this profiler's summary is included in the parent's log output. |
None
|
Source code in src/deux/render/profiler.py
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 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 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | |
steps
property
¶
Recorded steps as (name, elapsed_ms, child_profiler) tuples.
step ¶
Time a named sub-step.
Yields a child :class:RenderProfiler that can be used for
further nesting. The child's timings are automatically attached
to this profiler's step record.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Human-readable name for the sub-step. |
required |
Yields:
| Type | Description |
|---|---|
RenderProfiler
|
A child profiler for recording nested sub-steps. |
Source code in src/deux/render/profiler.py
finish ¶
Mark the profiler as finished and set the total elapsed time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
elapsed_ms
|
float or None
|
Total elapsed time. If |
None
|
Source code in src/deux/render/profiler.py
log ¶
Log the collected timings at DEBUG level.
Produces a tree-formatted summary, for example::
render_screen_complete 142.3ms
prefetch_icons 12.1ms
render_all_keys 89.4ms
render_phase 71.2ms
push_phase 18.2ms
render_touchscreen 38.7ms
render_info_screen 2.1ms
Source code in src/deux/render/profiler.py
RasterizeError ¶
Theme ¶
An immutable colour theme derived from a single primary colour.
A theme contains a primary RGB colour, a font family, the derived
18-class CSS palette, and the complete CSS stylesheet string ready
for :func:~deux.set_svg_stylesheet.
Instances are created via the factory class methods
:meth:default, :meth:from_color, or :meth:from_random.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
primary
|
tuple[int, int, int]
|
Primary colour as |
required |
font_family
|
str
|
CSS font-family name (e.g. |
_DEFAULT_FONT_FAMILY
|
Attributes:
| Name | Type | Description |
|---|---|---|
primary |
tuple[int, int, int]
|
The primary RGB colour. |
font_family |
str
|
The configured font family name. |
palette |
dict[str, str]
|
Mapping of CSS class name to hex colour (18 entries). |
css |
str
|
Complete CSS stylesheet string. |
Source code in src/deux/render/theme.py
42 43 44 45 46 47 48 49 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 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 | |
palette
property
¶
Mapping of CSS class name to hex colour string (18 entries).
default
classmethod
¶
Create the default DeUX theme.
Uses rgb(39, 87, 179) as the primary colour and Inter
as the font family.
Returns:
| Type | Description |
|---|---|
Theme
|
The default theme instance. |
Source code in src/deux/render/theme.py
from_color
classmethod
¶
Create a theme from a specific primary RGB colour.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
r
|
int
|
Primary colour channels (0–255). |
required |
g
|
int
|
Primary colour channels (0–255). |
required |
b
|
int
|
Primary colour channels (0–255). |
required |
font_family
|
str
|
CSS font-family name. |
"Inter"
|
Returns:
| Type | Description |
|---|---|
Theme
|
A new theme instance. |
Source code in src/deux/render/theme.py
from_random
classmethod
¶
Create a theme from a random primary colour.
Picks a random hue with moderate saturation and value to ensure readable, visually appealing palettes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
font_family
|
str
|
CSS font-family name. |
"Inter"
|
Returns:
| Type | Description |
|---|---|
Theme
|
A new theme instance with a random primary colour. |
Source code in src/deux/render/theme.py
get_default_backgrounds ¶
Return default background SVGs for a device identified by VID:PID.
Looks up the bundled manifest to find the correct SVG files for each surface type (key, touchscreen, screen) supported by the device.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vid
|
int
|
USB vendor ID (e.g. |
required |
pid
|
int
|
USB product ID. |
required |
Returns:
| Type | Description |
|---|---|
SurfaceBackgrounds
|
Mapping of surface type to raw SVG bytes. Returns an empty dict if no defaults are defined for the given VID:PID. |
Examples:
::
backgrounds = get_default_backgrounds(0x0FD9, 0x0084)
if "key" in backgrounds:
key_svg = backgrounds["key"]
Source code in src/deux/render/defaults/__init__.py
list_supported_devices ¶
Return all VID:PID pairs that have default backgrounds.
Returns:
| Type | Description |
|---|---|
list[tuple[int, int]]
|
Sorted list of |
Source code in src/deux/render/defaults/__init__.py
fetch_image ¶
Fetch a remote image by URL and return it as a PIL Image.
The result is cached in-process; subsequent calls with the same URL
return the cached :class:~PIL.Image.Image immediately. Negative
lookups (network failure, invalid image data) are also cached so we
do not retry broken URLs on every render.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
Fully-qualified HTTP(S) URL pointing to an image resource. |
required |
Returns:
| Type | Description |
|---|---|
Image
|
The decoded image. |
Raises:
| Type | Description |
|---|---|
ImageFetchError
|
If the URL is malformed, the network request fails, or the response cannot be decoded as a valid image. |
SSRFError
|
If the URL resolves to a private/loopback/link-local address
and private URLs have not been explicitly allowed via
:func: |
Source code in src/deux/render/image_fetch.py
clear_image_cache ¶
render_blank_key ¶
Render a blank key image at the given size.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key_size
|
tuple[int, int]
|
Key dimensions |
required |
image_format
|
str
|
Image encoding format ( |
'JPEG'
|
Returns:
| Type | Description |
|---|---|
bytes
|
Encoded blank-key image bytes. |
Source code in src/deux/render/key_renderer.py
render_key_image ¶
render_key_image(key_size: tuple[int, int], icon: bytes | Image | None = None, background: str = 'black', image_format: str = 'JPEG') -> bytes
Render an image for a Stream Deck key.
The icon (if any) is resized to fill the entire key, edge-to-edge — the library does not impose margins or padding. Callers that want spacing should bake it into the source icon.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key_size
|
tuple[int, int]
|
Key dimensions |
required |
icon
|
bytes | Image | None
|
Optional icon: encoded image bytes (PNG/JPEG) or a
|
None
|
background
|
str
|
Background colour name (used when icon is |
'black'
|
image_format
|
str
|
Image encoding format ( |
'JPEG'
|
Returns:
| Type | Description |
|---|---|
bytes
|
Encoded image bytes ready to send to the device. |
Source code in src/deux/render/key_renderer.py
render_profiler ¶
Create a new :class:RenderProfiler instance.
Convenience factory that mirrors the common usage pattern.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Operation name for the profiler. |
required |
Returns:
| Type | Description |
|---|---|
RenderProfiler
|
A new profiler instance. |
Source code in src/deux/render/profiler.py
render_info_screen ¶
render_info_screen(image: Image | None, width: int, height: int, background: str = 'black', image_format: str = 'JPEG') -> bytes
Render an info screen image.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
image
|
Image | None
|
Optional PIL Image to display. If |
required |
width
|
int
|
Screen width in pixels. |
required |
height
|
int
|
Screen height in pixels. |
required |
background
|
str
|
Background colour. |
'black'
|
image_format
|
str
|
Encoding format ( |
'JPEG'
|
Returns:
| Type | Description |
|---|---|
bytes
|
Encoded image bytes. |
Source code in src/deux/render/screen_renderer.py
clear_svg_cache ¶
Clear the SVG rasterisation cache.
Removes all cached rasterised images and resets the hit/miss counters. Call this after changing the global stylesheet or theme to ensure stale images are not served.
Source code in src/deux/render/svg_rasterize.py
get_svg_stylesheet ¶
Return the currently active application-wide CSS stylesheet.
Returns:
| Type | Description |
|---|---|
str or None
|
The CSS text set via :func: |
Source code in src/deux/render/svg_rasterize.py
load_svg_stylesheet ¶
Load a CSS stylesheet from a file and set it as the active stylesheet.
This is a convenience wrapper around :func:set_svg_stylesheet that
reads the CSS text from path and applies it application-wide.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str or Path
|
Path to a CSS file (UTF-8 encoded). |
required |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If path does not exist. |
IsADirectoryError
|
If path is a directory. |
Examples:
::
from deux import load_svg_stylesheet
load_svg_stylesheet("assets/theme.css")
Source code in src/deux/render/svg_rasterize.py
set_svg_stylesheet ¶
Set an application-wide CSS stylesheet for SVG rasterisation.
The stylesheet is applied to every SVG before rasterisation by
injecting a <style> element before any existing <style>
elements in the SVG, so that per-package styles can override the
application defaults.
Pass None to clear the stylesheet.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
css
|
str or None
|
Raw CSS text, or |
required |
Examples:
::
from deux import set_svg_stylesheet
set_svg_stylesheet(""".text-primary { color: #ff0000; }""")
Source code in src/deux/render/svg_rasterize.py
svg_cache_stats ¶
Return SVG rasterisation cache statistics.
Returns:
| Type | Description |
|---|---|
dict[str, int]
|
Dictionary with |
get_active_theme ¶
Return the currently active system-wide theme.
If no theme has been explicitly set, returns :meth:Theme.default.
Returns:
| Type | Description |
|---|---|
Theme
|
The active theme. |
Source code in src/deux/render/theme.py
get_default_font_family ¶
Return the font family from the active system-wide theme.
Used by the SVG renderer for text measurement so that CSS font declarations and pixel-based text wrapping agree.
Returns:
| Type | Description |
|---|---|
str
|
The active theme's font family name. |
Source code in src/deux/render/theme.py
set_active_theme ¶
Set the system-wide active theme.
Also updates the global SVG stylesheet via
:func:~deux.render.svg_rasterize.set_svg_stylesheet so that
all subsequent SVG rasterisation uses the new theme's CSS.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
theme
|
Theme or None
|
The theme to activate. Pass |
required |
Source code in src/deux/render/theme.py
compose_touchstrip ¶
compose_touchstrip(card_tiles: list[bytes | None], *, touchscreen_width: int, touchscreen_height: int, panel_count: int, panel_width: int, background: str = 'black', bg_tiles: list[bytes] | None = None, image_format: str = 'JPEG') -> bytes
Compose card images into a single touchscreen image.
Cards are tiled edge-to-edge across the touchscreen. Card i
starts at (i * panel_width, 0).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
card_tiles
|
list[bytes | None]
|
Encoded card images (or |
required |
touchscreen_width
|
int
|
Total touchscreen width in pixels. |
required |
touchscreen_height
|
int
|
Total touchscreen height in pixels. |
required |
panel_count
|
int
|
Number of card zones. |
required |
panel_width
|
int
|
Width of each card panel in pixels. |
required |
background
|
str
|
Fill colour for the canvas where no card is drawn. |
'black'
|
bg_tiles
|
list[bytes] | None
|
Optional list of PNG-encoded background tiles (one per panel). |
None
|
image_format
|
str
|
Image encoding format ( |
'JPEG'
|
Returns:
| Type | Description |
|---|---|
bytes
|
Encoded touchscreen image bytes. |
Source code in src/deux/render/touch_renderer.py
render_blank_touchscreen ¶
render_blank_touchscreen(*, touchscreen_width: int, touchscreen_height: int, panel_count: int, panel_width: int, background: str = 'black', image_format: str = 'JPEG') -> bytes
Render a blank touch-strip image.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
touchscreen_width
|
int
|
Total touchscreen width in pixels. |
required |
touchscreen_height
|
int
|
Total touchscreen height in pixels. |
required |
panel_count
|
int
|
Number of card zones. |
required |
panel_width
|
int
|
Width of each card panel in pixels. |
required |
background
|
str
|
Fill colour for the canvas. |
'black'
|
image_format
|
str
|
Image encoding format ( |
'JPEG'
|
Returns:
| Type | Description |
|---|---|
bytes
|
Encoded blank touchscreen image bytes. |