Pure image rendering from drawing instructions for e-paper displays
Project description
odl-renderer
Python renderer for the OpenDisplay Language (ODL).
odl-renderer is a standalone Python library for generating images from ODL drawing instructions. Originally extracted from the OpenEPaperLink Home Assistant integration, it provides a clean, async API for rendering text, shapes, icons, QR codes, and more to PIL images.
Features
- Pure Rendering: No dependencies on Home Assistant or other frameworks
- 17 Element Types: Text, shapes, icons, QR codes, images, progress bars, and more
- Async/Await: Modern async API for efficient image generation
- Flexible Input: Accepts fonts as PIL objects, file paths, or built-in names
- Full Color Output: Returns PIL Image objects in full RGB/RGBA (caller handles dithering)
- Percentage-Based Coordinates: Position elements using percentages or absolute pixels
- Template-Ready: All values are plain data (templates expanded by caller)
Installation
uv add odl-renderer
# or
pip install odl-renderer
Quickstart
import asyncio
from odl_renderer import generate_image
async def main():
image = await generate_image(
width=296,
height=128,
elements=[
{
"type": "text",
"value": "Hello World",
"x": "50%",
"y": 50,
"font": "ppb",
"size": 24,
"color": "black",
"anchor": "mm",
},
{
"type": "rectangle",
"x_start": 10,
"y_start": 10,
"x_end": 100,
"y_end": 50,
"fill": "red",
"outline": "black",
"width": 2,
},
],
background="white",
accent_color="red",
)
image.save("output.png")
asyncio.run(main())
Element Types
| Category | Types |
|---|---|
| Text | text, multiline |
| Shapes | rectangle, rectangle_pattern, circle, ellipse, polygon, arc, line |
| Icons | icon, icon_sequence |
| Media | dlimg, qrcode |
| Visualizations | progress_bar, plot, diagram |
| Debug | debug_grid |
Reference
Colors
| Value | Description |
|---|---|
"black" / "b" |
Black |
"white" / "w" |
White |
"red" / "r" |
Red |
"yellow" / "y" |
Yellow |
"blue" / "bl" |
Blue |
"green" / "gr" / "g" |
Green |
"gray" / "grey" |
Mid-gray |
"half_black" / "hb" |
Half-tone black |
"half_red" / "hr" |
Half-tone red |
"half_yellow" / "hy" |
Half-tone yellow |
"accent" / "a" |
Accent color (configured via accent_color param) |
"half_accent" / "ha" |
Half-tone accent |
"#RGB" / "#RRGGBB" |
Hex color |
Coordinates
Coordinates accept either absolute integers (10, 256) or percentage strings ("50%", "100%").
When y is omitted, elements stack automatically: each element is placed below the previous one using pos_y + y_padding (default padding: 10px).
Anchors
Used by text, icon, and icon_sequence to set which point of the element aligns to the given x/y coordinates.
text uses PIL anchor format — horizontal axis first, then vertical:
| Code | Meaning |
|---|---|
lt |
Left-top |
mt |
Middle-top |
rt |
Right-top |
lm |
Left-middle |
mm |
Middle (center) |
rm |
Right-middle |
lb |
Left-bottom |
mb |
Middle-bottom |
rb |
Right-bottom |
icon and icon_sequence use a different format for corner anchors — vertical axis first:
| Code | Meaning |
|---|---|
tl |
Top-left |
tr |
Top-right |
mt |
Middle-top |
lm |
Left-middle |
mm |
Middle (center) |
rm |
Right-middle |
bl |
Bottom-left |
br |
Bottom-right |
mb |
Middle-bottom |
The visible field
Every element type accepts an optional visible field. When false, the element is skipped entirely (no rendering, no position update). Defaults to true.
{
"type": "text",
"value": "Hidden",
"x": 10,
"y": 10,
"visible": False
}
Text
text
Single-line text with optional wrapping, truncation, stroke, and inline color markup.
| Field | Required | Default | Notes |
|---|---|---|---|
value |
yes | — | Text content |
x |
yes | — | X position |
y |
no | auto | Y position; auto-stacks if omitted |
font |
no | "ppb" |
Built-in name ("ppb", "rbm"), file path, or PIL Font object |
size |
no | 20 |
Font size in pixels |
color |
no | "black" |
Text color |
anchor |
no | "lt" / "la" |
Anchor point; defaults to "la" when text wraps to multiple lines |
max_width |
no | — | Wrap text at this pixel width |
truncate |
no | false |
Truncate with … instead of wrapping |
align |
no | "left" |
"left", "center", or "right" |
spacing |
no | 5 |
Line spacing when wrapped |
stroke_width |
no | 0 |
Outline width |
stroke_fill |
no | "white" |
Outline color |
parse_colors |
no | false |
Enable [red]text[/red] inline color markup |
y_padding |
no | 10 |
Extra space above when auto-stacking |
{
"type": "text",
"value": "Hello",
"x": "50%",
"y": 20,
"size": 32,
"anchor": "mt",
}
Inline color markup (requires parse_colors: true): wrap text in [color]...[/color] tags. Accepts all named colors, short names, and hex ([#ff0000]red text[/#ff0000]).
multiline
Fixed-line text split by a delimiter, each line placed at a fixed vertical offset.
| Field | Required | Default | Notes |
|---|---|---|---|
value |
yes | — | Full text content |
x |
yes | — | X position |
delimiter |
yes | — | Character used to split lines (e.g. "|") |
offset_y |
yes | — | Pixels between each line |
y |
no | auto | Y of first line |
font |
no | "ppb" |
Font |
size |
no | 20 |
Font size |
color |
no | "black" |
Text color |
anchor |
no | "lm" |
Anchor point |
align |
no | "left" |
Text alignment |
stroke_width |
no | 0 |
Outline width |
stroke_fill |
no | "white" |
Outline color |
parse_colors |
no | false |
Inline color markup |
{
"type": "multiline",
"value": "Line 1|Line 2|Line 3",
"x": 10,
"y": 10,
"delimiter": "|",
"offset_y": 24,
"size": 18,
}
Shapes
rectangle
Rectangle with optional fill, outline, and rounded corners.
| Field | Required | Default | Notes |
|---|---|---|---|
x_start |
yes | — | Left edge |
y_start |
yes | — | Top edge |
x_end |
yes | — | Right edge |
y_end |
yes | — | Bottom edge |
fill |
no | — | Fill color |
outline |
no | "black" |
Border color |
width |
no | 1 |
Border width |
corners |
no | — | Which corners to round: "all", "top_left", "top_right", "bottom_left", "bottom_right" (comma-separated) |
radius |
no | 10 |
Corner radius; only applies when corners is set |
{
"type": "rectangle",
"x_start": 10,
"y_start": 10,
"x_end": 100,
"y_end": 60,
"fill": "black",
}
rectangle_pattern
Repeating grid of rectangles — useful for dot matrices, grids, and decorative patterns.
| Field | Required | Default | Notes |
|---|---|---|---|
x_start |
yes | — | Starting X |
y_start |
yes | — | Starting Y |
x_size |
yes | — | Width of each rectangle |
y_size |
yes | — | Height of each rectangle |
x_repeat |
yes | — | Number of columns |
y_repeat |
yes | — | Number of rows |
x_offset |
yes | — | Horizontal gap between rectangles |
y_offset |
yes | — | Vertical gap between rectangles |
fill |
no | — | Fill color |
outline |
no | "black" |
Border color |
width |
no | 1 |
Border width |
corners / radius |
no | — | Same as rectangle |
{
"type": "rectangle_pattern",
"x_start": 10,
"y_start": 10,
"x_size": 8,
"y_size": 8,
"x_repeat": 5,
"y_repeat": 3,
"x_offset": 4,
"y_offset": 4,
"fill": "black",
}
circle
Circle defined by center point and radius.
| Field | Required | Default | Notes |
|---|---|---|---|
x |
yes | — | Center X |
y |
yes | — | Center Y |
radius |
yes | — | Radius in pixels |
fill |
no | — | Fill color |
outline |
no | "black" |
Border color |
width |
no | 1 |
Border width |
{
"type": "circle",
"x": "50%",
"y": "50%",
"radius": 20,
"fill": "green",
"outline": "black",
}
ellipse
Ellipse defined by a bounding box.
| Field | Required | Default | Notes |
|---|---|---|---|
x_start |
yes | — | Left edge |
y_start |
yes | — | Top edge |
x_end |
yes | — | Right edge |
y_end |
yes | — | Bottom edge |
fill |
no | — | Fill color |
outline |
no | "black" |
Border color |
width |
no | 1 |
Border width |
{
"type": "ellipse",
"x_start": 10,
"y_start": 10,
"x_end": 100,
"y_end": 60,
"fill": "yellow",
}
polygon
Arbitrary polygon defined by a list of vertices.
| Field | Required | Default | Notes |
|---|---|---|---|
points |
yes | — | List of [x, y] pairs |
fill |
no | — | Fill color |
outline |
no | "black" |
Border color |
{
"type": "polygon",
"points": [[10, 50], [50, 10], [90, 50], [50, 90]],
"fill": "blue",
}
arc
Arc or pie slice defined by center, radius, and angle range.
| Field | Required | Default | Notes |
|---|---|---|---|
x |
yes | — | Center X |
y |
yes | — | Center Y |
radius |
yes | — | Radius in pixels |
start_angle |
yes | — | Start angle in degrees (0 = right, counter-clockwise) |
end_angle |
yes | — | End angle in degrees |
fill |
no | — | Fill color (creates a pie slice) |
outline |
no | "black" |
Border color |
width |
no | 1 |
Border width |
{
"type": "arc",
"x": 50,
"y": 50,
"radius": 30,
"start_angle": 0,
"end_angle": 270,
"outline": "black",
"fill": "half_red",
}
line
Straight line between two points with optional dashing.
| Field | Required | Default | Notes |
|---|---|---|---|
x_start |
yes | — | Start X |
x_end |
yes | — | End X |
y_start |
no | auto | Start Y |
y_end |
no | y_start |
End Y (horizontal line if omitted) |
fill |
no | "black" |
Line color |
width |
no | 1 |
Line width |
dashed |
no | false |
Enable dashed style |
dash_length |
no | 5 |
Length of each dash |
space_length |
no | 3 |
Gap between dashes |
{
"type": "line",
"x_start": "10%",
"y_start": "50%",
"x_end": "90%",
"y_end": "50%",
"fill": "black",
}
Icons
Icons use the bundled Material Design Icons font (7,000+ icons). Pass the icon name with or without the mdi: prefix.
icon
Single MDI icon.
| Field | Required | Default | Notes |
|---|---|---|---|
value |
yes | — | Icon name, e.g. "home" or "mdi:home" |
x |
yes | — | X position |
y |
yes | — | Y position |
size |
yes | — | Icon size in pixels |
color |
no | "black" |
Icon color |
anchor |
no | "mm" |
Anchor point |
{
"type": "icon",
"value": "mdi:thermometer",
"x": "50%",
"y": "50%",
"size": 48,
"anchor": "mm",
}
icon_sequence
Row or column of MDI icons.
| Field | Required | Default | Notes |
|---|---|---|---|
icons |
yes | — | List of icon names |
x |
yes | — | Starting X |
y |
yes | — | Starting Y |
size |
yes | — | Icon size in pixels |
direction |
no | "right" |
"right", "left", "up", or "down" |
spacing |
no | size / 4 |
Gap between icons |
color |
no | "black" |
Icon color |
anchor |
no | "mm" |
Anchor point |
{
"type": "icon_sequence",
"icons": ["mdi:weather-sunny", "mdi:weather-cloudy", "mdi:weather-rainy"],
"x": 20,
"y": 20,
"size": 32,
}
Media
dlimg
Image from a URL, file path, data URI, bytes, or PIL Image object. Resized to fit the specified dimensions.
Note: Entity IDs are not resolved — the caller must pass a URL, file path, or image object directly.
| Field | Required | Default | Notes |
|---|---|---|---|
url |
yes | — | Source: HTTP URL, file path, data URI, bytes, or PIL.Image |
x |
yes | — | X position |
y |
yes | — | Y position |
xsize |
yes | — | Target width |
ysize |
yes | — | Target height |
rotate |
no | 0 |
Rotation in degrees |
resize_method |
no | "stretch" |
"stretch", "crop", "cover", or "contain" |
{
"type": "dlimg",
"url": "https://picsum.photos/seed/odl/200/150",
"x": 0,
"y": 0,
"xsize": 200,
"ysize": 150,
"resize_method": "cover",
}
qrcode
QR code generated from any text or URL.
| Field | Required | Default | Notes |
|---|---|---|---|
data |
yes | — | QR code content |
x |
yes | — | X position |
y |
yes | — | Y position |
color |
no | "black" |
Foreground color |
bgcolor |
no | "white" |
Background color |
border |
no | 1 |
Quiet zone size in modules |
boxsize |
no | 2 |
Pixel size of each module |
{
"type": "qrcode",
"data": "https://opendisplay.org",
"x": 10,
"y": 10,
}
Visualizations
progress_bar
Horizontal or vertical progress bar with optional percentage label.
| Field | Required | Default | Notes |
|---|---|---|---|
x_start |
yes | — | Left edge |
y_start |
yes | — | Top edge |
x_end |
yes | — | Right edge |
y_end |
yes | — | Bottom edge |
progress |
yes | — | Fill level 0–100 (clamped automatically) |
direction |
no | "right" |
"right", "left", "up", or "down" |
fill |
no | "red" |
Progress fill color |
background |
no | "white" |
Empty track color |
outline |
no | "black" |
Border color |
width |
no | 1 |
Border width |
show_percentage |
no | false |
Overlay percentage text |
font_name |
no | "ppb" |
Font for percentage text |
{
"type": "progress_bar",
"x_start": "10%",
"y_start": 50,
"x_end": "90%",
"y_end": 70,
"progress": 72,
"fill": "accent",
"show_percentage": True,
}
plot
Time-series line chart with configurable axes, grid, and legends. Requires a DataProvider in the drawing context to supply historical data.
| Field | Required | Default | Notes |
|---|---|---|---|
data |
yes | — | List of series configs (see below) |
x_start |
no | 0 |
Chart left edge |
y_start |
no | 0 |
Chart top edge |
x_end |
no | image width | Chart right edge |
y_end |
no | image height | Chart bottom edge |
duration |
no | 86400 |
Time window in seconds |
low / high |
no | auto | Y-axis range override |
font |
no | "ppb" |
Font for labels |
Series, axis, and legend options
Each series in data:
| Field | Default | Notes |
|---|---|---|
entity |
required | Data source identifier passed to DataProvider |
color |
required | Line color |
width |
2 |
Line width |
smooth |
false |
Smooth the line |
line_style |
"solid" |
"solid", "dashed", or "dotted" |
show_points |
false |
Draw data points |
span_gaps |
false |
Connect across missing values |
yaxis (optional dict):
| Field | Default |
|---|---|
tick_every |
required |
color |
"black" |
grid |
true |
grid_style |
"dotted" |
xaxis (optional dict):
| Field | Default |
|---|---|
color |
"black" |
grid |
true |
grid_style |
"dotted" |
ylegend (optional dict):
| Field | Default |
|---|---|
position |
"left" |
size |
10 |
color |
"black" |
xlegend (optional dict):
| Field | Default |
|---|---|
interval |
required |
format |
"%H:%M" |
position |
"bottom" |
size |
10 |
snap_to_hours |
true |
{
"type": "plot",
"data":
[
{
"entity": "sensor.temperature",
"color": "red"
}
],
"yaxis":
{
"tick_every": 20
},
"xlegend":
{
"interval": 3600
}
}
diagram
Simple bar chart with labeled axes.
| Field | Required | Default | Notes |
|---|---|---|---|
x |
yes | — | X position |
height |
yes | — | Diagram height |
width |
no | image width | Diagram width |
margin |
no | 20 |
Margin from edges |
bars |
no | — | Bar chart config (see below) |
bars config:
| Field | Notes |
|---|---|
values |
Semicolon-separated "label,value" pairs: "Mon,10;Tue,20;Wed,15" |
color |
Bar fill color |
font |
Font name |
legend_size |
Label font size |
legend_color |
Label color |
{
"type": "diagram",
"x": 0,
"height": 128,
"bars": {
"values": "Mon,10;Tue,20;Wed,15",
"color": "black",
},
}
Debug
debug_grid
Renders a coordinate grid over the image. Useful during layout development.
| Field | Required | Default | Notes |
|---|---|---|---|
spacing |
no | 20 |
Grid cell size in pixels |
line_color |
no | "black" |
Grid line color |
dashed |
no | true |
Use dashed lines |
dash_length |
no | 2 |
Dash segment length |
space_length |
no | 4 |
Gap between dashes |
show_labels |
no | true |
Show coordinate labels |
label_step |
no | spacing * 2 |
Interval between labels |
label_color |
no | "black" |
Label color |
label_font_size |
no | 12 |
Label font size |
font |
no | "ppb" |
Font for labels |
{
"type": "debug_grid",
"spacing": 20,
"line_color": "gray",
}
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file odl_renderer-0.5.3.tar.gz.
File metadata
- Download URL: odl_renderer-0.5.3.tar.gz
- Upload date:
- Size: 1.4 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b7950f69bb465ab19d0f5c1f913c87d2e65287bf18ccab8d8b6b44d581054650
|
|
| MD5 |
7df003bc88d55db7cfa160e42e6b1a64
|
|
| BLAKE2b-256 |
d6e3997498a3664cd8713c115794d44bd972d9ef5b9bfbcb3a20d04ca2357e48
|
Provenance
The following attestation bundles were made for odl_renderer-0.5.3.tar.gz:
Publisher:
release.yml on OpenDisplay/odl-renderer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
odl_renderer-0.5.3.tar.gz -
Subject digest:
b7950f69bb465ab19d0f5c1f913c87d2e65287bf18ccab8d8b6b44d581054650 - Sigstore transparency entry: 1280943221
- Sigstore integration time:
-
Permalink:
OpenDisplay/odl-renderer@427fcb4e437e09e8cd545c86c7da4e64042782c7 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/OpenDisplay
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@427fcb4e437e09e8cd545c86c7da4e64042782c7 -
Trigger Event:
push
-
Statement type:
File details
Details for the file odl_renderer-0.5.3-py3-none-any.whl.
File metadata
- Download URL: odl_renderer-0.5.3-py3-none-any.whl
- Upload date:
- Size: 1.2 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8337de0e08120a9b16b85dd3dc53ce3f55c237ef92e35520e346e135bbba7b48
|
|
| MD5 |
415ca79a89efc1cd33ad20525eceebc0
|
|
| BLAKE2b-256 |
674ae170d67f5eb5724da1c8a6f34d8ab2827856155077e307122e8ff4decc7b
|
Provenance
The following attestation bundles were made for odl_renderer-0.5.3-py3-none-any.whl:
Publisher:
release.yml on OpenDisplay/odl-renderer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
odl_renderer-0.5.3-py3-none-any.whl -
Subject digest:
8337de0e08120a9b16b85dd3dc53ce3f55c237ef92e35520e346e135bbba7b48 - Sigstore transparency entry: 1280943225
- Sigstore integration time:
-
Permalink:
OpenDisplay/odl-renderer@427fcb4e437e09e8cd545c86c7da4e64042782c7 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/OpenDisplay
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@427fcb4e437e09e8cd545c86c7da4e64042782c7 -
Trigger Event:
push
-
Statement type: