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.

API

Three functions, same keyword arguments:

render_png(html, *, width, height, ...) -> bytes            # PNG file bytes
render_rgba(html, *, width, height, ...) -> (w, h, bytes)   # raw RGBA pixels
render_frames(html, *, width, height, times, ...) -> (w, h, [bytes, ...])  # animation frames
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
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 104ms 0.4ms 43MB
240×240 widget @2× (flex + gradients) 480×480 42ms 1.3ms 47MB
Bootstrap 5.3 card (233KB CSS) 880×720 37ms 7.0ms 58MB
Tailwind v4 dashboard (the gallery image) 1520×1328 63ms 18ms 62MB
Long article 800×4000 48ms 13ms 69MB
Animated GIF: widget, 38 frames 240×240×38 53ms total 1.4ms/frame 77MB

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

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.1.0.tar.gz (1.0 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.1.0-cp310-abi3-win_amd64.whl (6.5 MB view details)

Uploaded CPython 3.10+Windows x86-64

blitz_py-0.1.0-cp310-abi3-musllinux_1_2_x86_64.whl (7.5 MB view details)

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

blitz_py-0.1.0-cp310-abi3-musllinux_1_2_aarch64.whl (7.1 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ ARM64

blitz_py-0.1.0-cp310-abi3-manylinux_2_28_x86_64.whl (7.1 MB view details)

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

blitz_py-0.1.0-cp310-abi3-manylinux_2_28_aarch64.whl (7.0 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.28+ ARM64

blitz_py-0.1.0-cp310-abi3-macosx_11_0_arm64.whl (6.4 MB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

blitz_py-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl (6.8 MB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

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

File metadata

  • Download URL: blitz_py-0.1.0.tar.gz
  • Upload date:
  • Size: 1.0 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.1.0.tar.gz
Algorithm Hash digest
SHA256 f996b5b5c5b77bb73bdc488e57c929c20645393642af1e33663a3d9c2d4e5a06
MD5 602d4ad73a1308f3abb952cb5d08de06
BLAKE2b-256 b7d9a17c88718dfb5988bbc700f5376fb43cdec98d8bcf1061a15194d07211ad

See more details on using hashes here.

Provenance

The following attestation bundles were made for blitz_py-0.1.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.1.0-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: blitz_py-0.1.0-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 6.5 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.1.0-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 6bef99ab2111953b6928effdc7398f938c503db5175e1d04e894b1097bd9b051
MD5 59304e0de699269c2c85500445e5823f
BLAKE2b-256 c6a52cdbf7d70733f571930e3becfbb73ca8e36e777abf36b1b61d64576226f7

See more details on using hashes here.

Provenance

The following attestation bundles were made for blitz_py-0.1.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.1.0-cp310-abi3-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for blitz_py-0.1.0-cp310-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 940325c4ecbf96c621f13e5a2a7e13412dc18117585af3087be976caecb345b8
MD5 d590c3745c8c4d345ea6d39c993241b8
BLAKE2b-256 83daa16a6310ba5ba0498f88d0ba595a343c1c5a779fff5457fdbfe9960a3f6d

See more details on using hashes here.

Provenance

The following attestation bundles were made for blitz_py-0.1.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.1.0-cp310-abi3-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for blitz_py-0.1.0-cp310-abi3-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 b00412e1a4052ffef5bd17884970aa82dacecc4fe50c045435811fe2c2c41133
MD5 2e6acbe73331e38ad6f67ffe89881f8f
BLAKE2b-256 db6a7dbde1ca4dd9e2e6648d9bcd1731e2930efb5be4f98dcb9e20d332667518

See more details on using hashes here.

Provenance

The following attestation bundles were made for blitz_py-0.1.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.1.0-cp310-abi3-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for blitz_py-0.1.0-cp310-abi3-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 3055961f6888b9479339be1afb9a0525c4759d22154c0d18703134c7d47ac042
MD5 2337418479f2b88e457b1c60813f9a77
BLAKE2b-256 8bbc9a26a3e10ef2fc6821dbb4133aeb70403b13a7708cf3db06ed30efed82bf

See more details on using hashes here.

Provenance

The following attestation bundles were made for blitz_py-0.1.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.1.0-cp310-abi3-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for blitz_py-0.1.0-cp310-abi3-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 fe4cbb902366474164e45a7a3544f4c81b29681ded145e486d87d6a04b15bb9a
MD5 d4ce9dec84764ee7ea71f601f4701095
BLAKE2b-256 67ea17d762e0182da3b75f348cab8e4e0773141a3141b61f8c357f4e7ed53b22

See more details on using hashes here.

Provenance

The following attestation bundles were made for blitz_py-0.1.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.1.0-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for blitz_py-0.1.0-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 72c71b42f433658ef17b704fc47cb0c97958bcc00dd6c752065220c632816326
MD5 5b66a50ec049c785be27d63a93f8a714
BLAKE2b-256 9e9e6eda628053da1624226cb2f1061950b21ae7bcb7b4713d4bd684b7d26dba

See more details on using hashes here.

Provenance

The following attestation bundles were made for blitz_py-0.1.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.1.0-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for blitz_py-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 93f779e4aa0bcacabe6360f40b5ee38090a8527512b862295ecedcd2f1d88765
MD5 17338b28f5e3cb2a1ade71fa2facfe68
BLAKE2b-256 06da29b594be51adcf7c3378ced75dc21be5f501f63018c8ee8046549437001c

See more details on using hashes here.

Provenance

The following attestation bundles were made for blitz_py-0.1.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