Skip to main content

blitz-py

CI PyPI

Render HTML/CSS to images from Python — no browser, no GPU, no JavaScript, no network.

Powered by Blitz, DioxusLabs' modular web engine: real CSS via Stylo (Servo/Firefox's style engine), flexbox/grid layout via Taffy, text shaping via Parley, and CPU rasterization via vello_cpu.

A 240×240 widget renders in ~1.5ms warm on an M-series Mac (~40ms for the first render). Output is deterministic and identical across platforms: the Inter font (SIL OFL 1.1) is bundled as the default face, so text renders the same on your laptop and in a fontless Alpine container.

What it looks like

Dashboard rendered from Tailwind v4 CSS: bento grid, SVG donut and sparkline, avatar stack, gradients

240x240 smart-display widget, hand-written CSS Bootstrap 5.3 card with buttons, badge, alert and progress bar

Unedited render_png output: a Tailwind v4 dashboard (grid, SVG donut + sparkline, gradients, avatar stack), a 240×240 widget, and Bootstrap 5.3 components.

Install

pip install blitz-py

Prebuilt wheels (abi3, Python ≥ 3.10): Linux glibc + musl (x86_64, aarch64), macOS (arm64, x86_64), Windows (x64).

Usage

import blitz_py

png = blitz_py.render_png(
    """
    <style>
      body { margin: 0; background: #000; color: #fff; font-family: sans-serif; }
      .screen { display: flex; flex-direction: column; align-items: center;
                justify-content: center; height: 240px; }
      .temp { font-size: 64px; font-weight: 600; }
      .label { color: #8e8e93; }
    </style>
    <body><div class="screen">
      <div class="temp">21.5&deg;</div>
      <div class="label">Living room</div>
    </div></body>
    """,
    width=240,
    height=240,
)
with open("out.png", "wb") as f:
    f.write(png)

Or get raw pixels for Pillow:

from PIL import Image

w, h, rgba = blitz_py.render_rgba(html, width=240, height=240)
Image.frombytes("RGBA", (w, h), rgba).convert("RGB").save("out.jpg", quality=90)

Animated GIFs

Animated dashboard: radar sweep, equalizer bars, deploy progress, typewriter terminal — pure CSS keyframes

Everything above is Tailwind classes + CSS @keyframes (source): satellites on different orbital periods, a live-scrolling traffic chart (a periodic series drawn two cycles wide, translated one cycle per loop — new data appears to stream in), an indeterminate progress sweep, and a steps()-driven typewriter — 48 frames rendered in ~220ms, seamless 4s loop.

CSS animations are evaluated on a deterministic clock: render_frames renders the document at any list of timestamps (seconds), and Pillow assembles the GIF. Frames after the first reuse the parsed document, so they're fast — ~1ms per 240×240 frame:

from PIL import Image

fps, seconds = 12, 3.2
w, h, frames = blitz_py.render_frames(
    html, width=240, height=240,
    times=[i / fps for i in range(int(fps * seconds))],
)
rgbs = [Image.frombytes("RGBA", (w, h), f).convert("RGB") for f in frames]
base = rgbs[0].quantize(colors=64, dither=Image.Dither.NONE)
imgs = [im.quantize(colors=64, palette=base, dither=Image.Dither.NONE) for im in rgbs]
imgs[0].save("widget.gif", save_all=True, append_images=imgs[1:],
             duration=int(1000 / fps), loop=0, optimize=True)

Anything @keyframes can express — transforms, opacity, colors — loops perfectly because you control the clock. See examples/animated_widget.py.

File-size tips (a 3.2s 240×240 widget loop, measured): one shared palette across frames and no dithering matter most — both per-frame palettes and dither noise defeat GIF's delta/LZW compression. Naive 20fps/256-color/dithered ≈ 163KB; 20fps/64-color shared/no-dither ≈ 26KB; 12fps ≈ 18KB; 8fps/32 colors ≈ 11KB. Flat-color UI animation quantizes to 64 colors with no visible loss; gradients are what eat palette entries.

Fast repeated renders: Template

For dashboards and device widgets that re-render the same document with fresh data, parse once and mutate by element id:

tpl = blitz_py.Template(html, width=240, height=240)
tpl.set_text("temp", "21.5°")            # element must hold one text node
tpl.set_style("bar", "width", "62%")
tpl.set_attribute("icon", "src", data_uri)
png = tpl.render_png()                    # ~0.4ms — 3x faster than one-shot
frame = tpl.render_png(time=0.5)          # animation clock also available

Templates are safe to share across threads (the document lives on its own worker thread), and renders release the GIL — 4 rendering threads get ~3.5× throughput.

API

Three functions and a class, same keyword arguments:

render_png(html, *, width, height=None, ...) -> bytes       # PNG; height=None → content height
render_rgba(html, *, width, height=None, ...) -> (w, h, bytes)   # raw RGBA pixels
render_frames(html, *, width, height, times, ...) -> (w, h, [bytes, ...])  # animation frames
Template(html, *, width, height, ...)                       # parse once, re-render fast
Argument Default Meaning
width, height required CSS-pixel viewport size
scale 1.0 Device-pixel ratio; output is width*scale × height*scale physical pixels. Use 2.0 for supersampled/hi-dpi output.
color_scheme "light" "light" or "dark" — drives @media (prefers-color-scheme: ...)
background "#ffffff" Base canvas color (#rgb, #rrggbb, #rrggbbaa), or None for transparent
base_url None Base for resolving relative URLs
css None Extra CSS appended after the document's styles (wins the cascade)
css_vars None Dict of CSS custom properties set on :root, e.g. {"accent": "#f00"}var(--accent)
fonts None List of font file bytes (TTF/OTF, variable fonts OK) to register
default_font_family None Family name to use for all CSS generic families (sans-serif, serif, ...) and as text fallback
allow_file_urls False Permit file:// URLs for images/resources

Images and resources

Rendering is fully offline. Embed images as data: URIs, or enable allow_file_urls=True and use file:// paths. http(s) URLs are intentionally ignored.

import base64
b64 = base64.b64encode(open("icon.png", "rb").read()).decode()
html = f'<img src="data:image/png;base64,{b64}" style="width:32px">'

CSS frameworks (Bootstrap, Tailwind, ...)

Any framework that ships as plain CSS works — inline it in a <style> tag:

css = open("bootstrap.min.css").read()  # fetch/cache it however you like
html = f"<style>{css}</style><body class='p-4'><div class='card'>...</div></body>"

Bootstrap 5 components (cards, buttons, badges, alerts, progress bars) render correctly. For Tailwind, run its build step and inline the generated CSS — the JS "Play CDN" won't work because there is no JavaScript engine. JS-driven behavior (modals opening, dropdowns) doesn't apply to static rendering anyway.

Fonts

Bundled Inter is the default for every CSS generic family and the Latin-script fallback, everywhere. Explicit family names (font-family: "Comic Sans MS") resolve against system fonts where available (macOS/Windows natively; Linux via fontconfig loaded at runtime if present — never a link dependency). To use your own font:

font = open("MyFont.ttf", "rb").read()
blitz_py.render_png(html, width=240, height=240,
                    fonts=[font], default_font_family="My Font")

@font-face also works with data: (or file://) sources — state the format explicitly, either as the unquoted CSS keyword or a bare extension string:

@font-face {
  font-family: MyWebFont;
  src: url(data:font/ttf;base64,...) format(truetype);  /* or format("ttf") */
}

WOFF/WOFF2 sources are supported too. local(...) sources and format-less data URIs are currently skipped by the engine.

Note on coverage: bundled Inter covers Latin scripts (plus Greek/Cyrillic). For CJK, Arabic, and other scripts on systems without suitable fonts, pass an appropriate font (e.g. a Noto variant) via fonts=.

What's supported

Modern CSS as implemented by Stylo/Taffy: flexbox, grid, gradients, border-radius, shadows, transforms, calc(), custom properties, media queries, SVG images, WOFF... No JavaScript, no @font-face fetching, no external resources. Blitz itself is pre-1.0: capable but not pixel-perfect against browsers.

Performance

Measured on an M-series Mac (arm64), each scenario in a fresh process, release build — reproduce with examples/bench.py:

Scenario Output px First render Warm render Peak RSS after 200 renders
<h1>Hello</h1> 200×100 35ms 0.5ms 42MB
240×240 widget @2× (flex + gradients) 480×480 32ms 1.8ms 45MB
Bootstrap 5.3 card (233KB CSS) 880×720 39ms 8.2ms 51MB
Tailwind v4 dashboard (the gallery image) 1520×1328 60ms 22ms 62MB
Long article 800×4000 56ms 21ms 73MB
Animated GIF: widget, 38 frames 240×240×38 56ms total 1.5ms/frame 76MB

GIF encoding on top of rendering (Pillow quantize + LZW, 38 frames): ~60ms, 18KB output.

More performance properties, all verified in CI or by examples/bench.py:

  • Template re-renders in ~0.4ms (parse and first style pass amortized away).
  • Thread scaling: the GIL is released during rendering; 4 threads → ~3.5× throughput.
  • Deterministic across platforms: CI renders a golden set on Linux, macOS, and Windows and asserts the outputs are byte-identical. Snapshot tests in your project can compare exact hashes.
  • Package ships type stubs (py.typed), so the API autocompletes and type-checks.

The first render pays a one-time system-font scan; after that the font collection is cached and cloned per render. Importing the module adds ~1MB RSS; memory stays flat under sustained rendering (no per-render growth — verified over 1000+ renders). On an Alpine/arm64 container the warm widget render measures ~0.8ms.

The GIL is released during rendering, so concurrent renders from Python threads scale and async event loops aren't blocked.

Why not a headless browser?

Playwright/Chromium render HTML too — at ~150MB+ of install, a browser process to babysit, and cold starts in the hundreds of milliseconds. blitz-py is a ~7MB self-contained wheel with millisecond renders, suitable for embedded targets like Home Assistant integrations generating widget images for small displays (its original use case).

License

MIT OR Apache-2.0. Bundled Inter font: SIL OFL 1.1 (assets/LICENSE-Inter.txt).

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

blitz_py-0.2.0.tar.gz (1.1 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

blitz_py-0.2.0-cp310-abi3-win_amd64.whl (5.0 MB view details)

Uploaded CPython 3.10+Windows x86-64

blitz_py-0.2.0-cp310-abi3-musllinux_1_2_x86_64.whl (5.8 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ x86-64

blitz_py-0.2.0-cp310-abi3-musllinux_1_2_aarch64.whl (5.4 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ ARM64

blitz_py-0.2.0-cp310-abi3-manylinux_2_28_x86_64.whl (5.5 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.28+ x86-64

blitz_py-0.2.0-cp310-abi3-manylinux_2_28_aarch64.whl (5.2 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.28+ ARM64

blitz_py-0.2.0-cp310-abi3-macosx_11_0_arm64.whl (4.9 MB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

blitz_py-0.2.0-cp310-abi3-macosx_10_12_x86_64.whl (5.3 MB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

Details for the file blitz_py-0.2.0.tar.gz.

File metadata

  • Download URL: blitz_py-0.2.0.tar.gz
  • Upload date:
  • Size: 1.1 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for blitz_py-0.2.0.tar.gz
Algorithm Hash digest
SHA256 b3abbca72f78b4f0adc9398e43055abb338db2286980d5dad6d38c571463f2e1
MD5 4df4e6e5ea5feb83affebf47958ac7bd
BLAKE2b-256 366f1fdb8dc081122cd4699b2406030bd7f333680505754817f6fa5a698cf8bf

See more details on using hashes here.

Provenance

The following attestation bundles were made for blitz_py-0.2.0.tar.gz:

Publisher: ci.yml on adrienbrault/blitz-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file blitz_py-0.2.0-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: blitz_py-0.2.0-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 5.0 MB
  • Tags: CPython 3.10+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for blitz_py-0.2.0-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 8f6279891867011f8c6aad56fd88e79a1f59f72772615071522e5754b5a68316
MD5 979e1b641adf514b60cd18cf317057be
BLAKE2b-256 5bf1125a1847937b4f2b59c8768aa69a2a6527cbe733a2a48ee418e2081188de

See more details on using hashes here.

Provenance

The following attestation bundles were made for blitz_py-0.2.0-cp310-abi3-win_amd64.whl:

Publisher: ci.yml on adrienbrault/blitz-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file blitz_py-0.2.0-cp310-abi3-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for blitz_py-0.2.0-cp310-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 d22d84f0b502c3a0a4a2b55547c68d2cd113d675b1bba23afd521e2c6ad995fd
MD5 a322bf81427eed5cbbc79bb2294d2ffa
BLAKE2b-256 bb687f6856878de984c3ed06dfde70b44ba2ddd88c80a5c3f961726ffdee8656

See more details on using hashes here.

Provenance

The following attestation bundles were made for blitz_py-0.2.0-cp310-abi3-musllinux_1_2_x86_64.whl:

Publisher: ci.yml on adrienbrault/blitz-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file blitz_py-0.2.0-cp310-abi3-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for blitz_py-0.2.0-cp310-abi3-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 e03b21fd9493edcede4dc94f973e230e518548b3b7c013615ac972c8e676124f
MD5 353522041dbf67ee6e127b8e82b04768
BLAKE2b-256 0a5d8d91da2d61fada5f61774ab73e0e809296eac71010df57f054536d955317

See more details on using hashes here.

Provenance

The following attestation bundles were made for blitz_py-0.2.0-cp310-abi3-musllinux_1_2_aarch64.whl:

Publisher: ci.yml on adrienbrault/blitz-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file blitz_py-0.2.0-cp310-abi3-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for blitz_py-0.2.0-cp310-abi3-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 5e1de18361255fa55b8555ccce264d4d92a2244d7eee8d765cb80e7d6d0fbe8a
MD5 b3ce05e04bcf4b0b67288e70db205898
BLAKE2b-256 0746e7e93c69e7f9b7094bbf4eaabf0e871a7eb1f47b1b25a2507274abf1fa67

See more details on using hashes here.

Provenance

The following attestation bundles were made for blitz_py-0.2.0-cp310-abi3-manylinux_2_28_x86_64.whl:

Publisher: ci.yml on adrienbrault/blitz-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file blitz_py-0.2.0-cp310-abi3-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for blitz_py-0.2.0-cp310-abi3-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 312c847a64a64ebc13e140d2e6d1c5600b852a83d6cfaec33f7ecc16817cee56
MD5 bc439aa987ca526906b08facaba19fb6
BLAKE2b-256 ce4a22002921065986358d50e921fabc8329a1607e4fd1ef9c20fcb6bbdf2eb2

See more details on using hashes here.

Provenance

The following attestation bundles were made for blitz_py-0.2.0-cp310-abi3-manylinux_2_28_aarch64.whl:

Publisher: ci.yml on adrienbrault/blitz-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file blitz_py-0.2.0-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for blitz_py-0.2.0-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 423d5f4843ce26e75890c6060b82a43fe31f599ed8120d8d106166d1a1b5ce2d
MD5 3b431dfd2abb933c40524954a0399a1f
BLAKE2b-256 69a401fcb68b2d6bbced96bdf93f7f9b475fa01370ad12dd828c4c6c565ece4d

See more details on using hashes here.

Provenance

The following attestation bundles were made for blitz_py-0.2.0-cp310-abi3-macosx_11_0_arm64.whl:

Publisher: ci.yml on adrienbrault/blitz-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file blitz_py-0.2.0-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for blitz_py-0.2.0-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 d0d49fe9d373d9bda438200bfc04a3a4544aae5ea03afc8b869bb491637bf0c7
MD5 a653ac6821b50aae862d5605de94e9f5
BLAKE2b-256 8fa9f8b7541c8a035e49456ad73a081982f1f478c30f8f1039ad348c8cc70409

See more details on using hashes here.

Provenance

The following attestation bundles were made for blitz_py-0.2.0-cp310-abi3-macosx_10_12_x86_64.whl:

Publisher: ci.yml on adrienbrault/blitz-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page