Skip to main content

A fast library for rendering HTML/CSS to images

Project description

dynimg

A fast library and CLI for rendering HTML/CSS to images. Use from Python, Rust, or the command line. Built on Blitz, a modular Rust rendering engine.

Perfect for generating dynamic images like Open Graph (OG) images, social media cards, email headers, and more.

Example Output

OG Image Example Social Card Example

Features

  • Python + Rust + CLI: Use from Python, as a Rust library, or command-line tool
  • Multiple output formats: PNG, WebP (lossless), and JPEG
  • Transparent backgrounds: PNG and WebP support transparency (JPEG uses white)
  • High-quality rendering: Configurable scale factor for retina displays
  • Fast: Native Rust performance with no browser overhead
  • Secure by default: Network and filesystem access disabled unless explicitly enabled

Installation

Python

pip install dynimg

Rust CLI

cargo install dynimg

Rust Library

[dependencies]
dynimg = "0.1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }

Library Usage

use dynimg::{render, RenderOptions};

#[tokio::main]
async fn main() -> Result<(), dynimg::Error> {
    let html = r#"
        <html>
        <body style="background: linear-gradient(135deg, #667eea, #764ba2);
                     display: flex; justify-content: center; align-items: center;
                     height: 630px; margin: 0;">
            <h1 style="color: white; font-family: system-ui; font-size: 64px;">
                Hello World
            </h1>
        </body>
        </html>
    "#;

    // Render with default options (1200×auto viewport, 2x scale)
    let image = render(html, RenderOptions::default()).await?;

    // Save as PNG
    image.save_png("output.png")?;

    // Or get raw bytes
    let png_bytes = image.to_png()?;
    let webp_bytes = image.to_webp();
    let jpeg_bytes = image.to_jpeg(90)?;

    Ok(())
}

Configuration

use dynimg::RenderOptions;

// Using builder pattern
let options = RenderOptions::default()
    .width(1200)
    .height(630)
    .scale(2.0)
    .allow_net()
    .assets_dir("./assets")
    .background("#ffffff")  // CSS hex color (default: transparent)
    .verbose();             // Show dependency output on stderr

// Or struct initialization
let options = RenderOptions {
    width: 1200,
    height: Some(630),
    scale: 2.0,
    allow_net: true,
    assets_dir: Some("./assets".into()),
    base_url: None,
    background: Some("#ffffff".into()),  // None = transparent
    verbose: false,                      // Suppress dependency output (default)
};

Convenience function

use dynimg::{render_to_file, RenderOptions};

// Render directly to a file (format detected from extension)
render_to_file(html, "output.png", RenderOptions::default(), 90).await?;

CLI Usage

Basic Usage

Render an HTML file to PNG:

dynimg input.html -o output.png

Output Formats

# PNG (lossless, supports transparency)
dynimg input.html -o image.png

# WebP (lossless, supports transparency)
dynimg input.html -o image.webp

# JPEG (lossy, no transparency - uses white background)
dynimg input.html -o image.jpg --quality 90

Transparent Backgrounds

PNG and WebP output supports transparent backgrounds. Simply don't set a background on your HTML body:

<body style="padding: 20px;">
  <div style="background: white; border-radius: 8px; padding: 20px;">
    Content with transparent surrounding area
  </div>
</body>
Transparent background example

JPEG doesn't support transparency, so it automatically uses a white background.

Image Dimensions

The --width and --height options set the viewport size (CSS layout dimensions). The actual output image is scaled by the --scale factor (default: 2x for high-DPI/retina displays).

Output image size = viewport × scale

# Default: 1200px viewport → 2400px output (at 2x scale)
dynimg input.html -o output.png

# OG image: 1200×630 viewport → 2400×1260 output
dynimg input.html -o output.png --width 1200 --height 630

# 1x scale for exact pixel dimensions (1200×630 output)
dynimg input.html -o output.png --width 1200 --height 630 --scale 1

# 3x scale for extra-high-DPI (3600×1890 output)
dynimg input.html -o output.png --width 1200 --height 630 --scale 3

Your HTML/CSS should use the viewport dimensions (e.g., width: 1200px) - the scale factor handles the high-resolution rendering.

Reading from stdin

echo '<html><body><h1>Hello</h1></body></html>' | dynimg - -o output.png

Loading External Resources

By default, network and filesystem access are disabled for security. Enable them to load images, fonts, and other resources:

# Load images/fonts from URLs
dynimg input.html -o output.png --allow-net

# Load images/fonts from a local assets directory
dynimg input.html -o output.png --assets ./assets

# Allow both
dynimg input.html -o output.png --allow-net --assets ./assets

When using --assets, all local paths are resolved relative to the asset directory. Attempts to load files outside this directory will error:

<!-- With --assets ./assets -->
<img src="logo.png">         <!-- loads ./assets/logo.png -->
<img src="img/hero.png">     <!-- loads ./assets/img/hero.png -->
<img src="../secret.png">    <!-- ERROR: outside assets directory -->

For self-contained templates, consider using inline base64 data URIs instead:

<img src="data:image/png;base64,iVBORw0KGgo...">

CLI Reference

dynimg [OPTIONS] <INPUT> -o <OUTPUT>

Arguments:
  <INPUT>   HTML file path or '-' for stdin

Options:
  -o, --output <FILE>       Output image path (format detected from extension)
  -w, --width <PIXELS>      Viewport width in CSS pixels [default: 1200]
  -H, --height <PIXELS>     Viewport height in CSS pixels [default: document height]
  -s, --scale <FACTOR>      Scale multiplier for output (2 = 2x resolution) [default: 2]
  -q, --quality <1-100>     JPEG quality [default: 90]
      --allow-net           Allow network access for loading remote resources
      --assets <DIR>        Asset directory for local resources
  -v, --verbose             Enable verbose logging and show dependency output on stderr
      --help                Print help
      --version             Print version

Options can also be set via HTML meta tags (see below). CLI flags override meta tags.

Note: Output image dimensions = viewport × scale. A 1200×630 viewport at 2x scale produces a 2400×1260 image.

Python Usage

import dynimg

html = """
<html>
<style>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
</style>
<body style="background: linear-gradient(135deg, #667eea, #764ba2);
             display: flex; justify-content: center; align-items: center;
             height: 630px; margin: 0;">
    <h1 style="color: white; font-family: system-ui; font-size: 64px;">
        Hello World
    </h1>
</body>
</html>
"""

# Render with default options
image = dynimg.render(html)

# Save to file
image.save("output.png")

# Or get bytes
png_bytes = image.to_png()
webp_bytes = image.to_webp()
jpeg_bytes = image.to_jpeg(quality=90)

Configuration

import dynimg

options = dynimg.RenderOptions(
    width=1200,          # Viewport width (default: 1200)
    height=630,          # Viewport height (default: auto)
    scale=2.0,           # Output scale factor (default: 2.0)
    allow_net=True,      # Allow network requests (default: False)
    assets_dir="./assets",  # Local assets directory (default: None)
    base_url="https://example.com",  # Base URL for relative URLs (default: None)
    background="#ffffff",  # CSS hex color (default: transparent)
    verbose=False,       # Show dependency output on stderr (default: False)
)

image = dynimg.render(html, options)

Direct File Output

# Render directly to a file (format detected from extension)
dynimg.render_to_file(html, "output.png")

# With options
dynimg.render_to_file(html, "output.png", options=options)

# JPEG with quality setting
dynimg.render_to_file(html, "output.jpg", quality=90)

Image Properties

image = dynimg.render(html)
print(f"Size: {image.width}x{image.height}")
print(f"Bytes: {len(image.data)}")

HTML Meta Tags

You can configure rendering options directly in your HTML using meta tags. CLI flags take precedence over meta tags.

<meta name="dynimg:width" content="1200">   <!-- viewport width -->
<meta name="dynimg:height" content="630">   <!-- viewport height -->
<meta name="dynimg:scale" content="2">      <!-- output multiplier -->
<meta name="dynimg:quality" content="90">   <!-- JPEG quality -->

This is useful for templates that should always render at specific dimensions. Remember: the output image size is viewport × scale.

Example Templates

OG Image

OG Image Example
View HTML
<!DOCTYPE html>
<html>
<head>
  <meta name="dynimg:width" content="1200">
  <meta name="dynimg:height" content="630">
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: system-ui, -apple-system, sans-serif; }
    .container {
      width: 1200px;
      height: 630px;
      display: flex;
      flex-direction: column;
      justify-content: center;
      padding: 80px;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    }
    h1 { color: white; font-size: 72px; font-weight: 700; line-height: 1.1; margin-bottom: 24px; }
    p { color: rgba(255, 255, 255, 0.85); font-size: 32px; }
    .footer { position: absolute; bottom: 40px; left: 80px; color: rgba(255, 255, 255, 0.6); font-size: 24px; }
  </style>
</head>
<body>
  <div class="container">
    <h1>Your Blog Post Title Here</h1>
    <p>A compelling subtitle that captures attention</p>
    <div class="footer">yoursite.com</div>
  </div>
</body>
</html>

Social Card

Social Card Example
View HTML
<!DOCTYPE html>
<html>
<head>
  <meta name="dynimg:width" content="1200">
  <meta name="dynimg:height" content="675">
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: system-ui, -apple-system, sans-serif; }
    .card {
      width: 1200px;
      height: 675px;
      background: #0f172a;
      display: flex;
      padding: 60px;
    }
    .content { flex: 1; display: flex; flex-direction: column; justify-content: center; }
    .tag {
      display: inline-block;
      background: #3b82f6;
      color: white;
      padding: 8px 16px;
      border-radius: 20px;
      font-size: 18px;
      font-weight: 600;
      margin-bottom: 24px;
      width: fit-content;
    }
    h1 { color: white; font-size: 56px; font-weight: 700; line-height: 1.2; margin-bottom: 20px; }
    p { color: #94a3b8; font-size: 24px; line-height: 1.5; }
    .avatar {
      width: 200px;
      height: 200px;
      background: linear-gradient(135deg, #06b6d4, #3b82f6);
      border-radius: 100px;
      display: flex;
      align-items: center;
      justify-content: center;
      align-self: center;
      margin-left: 60px;
    }
    .avatar-text { color: white; font-size: 72px; font-weight: 700; }
  </style>
</head>
<body>
  <div class="card">
    <div class="content">
      <span class="tag">Tutorial</span>
      <h1>Getting Started with Rust</h1>
      <p>Learn the fundamentals of Rust programming language with practical examples.</p>
    </div>
    <div class="avatar">
      <span class="avatar-text">RS</span>
    </div>
  </div>
</body>
</html>

Supported CSS Features

dynimg uses Blitz for rendering, which supports:

  • Flexbox and Grid layouts
  • CSS variables
  • Media queries
  • Complex selectors
  • Gradients and shadows
  • Web fonts (via @font-face, requires --allow-net or --assets)
  • Images (requires --allow-net or --assets, or use data URIs)

Performance

dynimg is designed for speed:

  • No browser startup overhead
  • Native Rust rendering pipeline
  • Efficient image encoding

Typical rendering time: 50-200ms depending on complexity.

Development

Building

# Build CLI
cargo build --release

# Build Python wheel
pip install maturin
maturin build --release --features python

# Install locally for development
maturin develop --features python

Running Tests

cargo test
cargo clippy -- -D warnings
cargo fmt -- --check

Releasing

Releases are automated via a release script and GitHub Actions. To create a new release:

./scripts/release.sh

The script will:

  1. Verify you're on the main branch with a clean working directory
  2. Check that you're up to date with the remote
  3. Bump the patch version in Cargo.toml and pyproject.toml
  4. Commit the version changes and create an annotated tag
  5. Push the commit and tag to origin

This triggers the GitHub Actions release workflow which:

  • Builds wheels for Linux (x86_64, aarch64) and macOS (x86_64, aarch64)
  • Creates a GitHub Release with all artifacts
  • Publishes to PyPI

License

MIT

AI Warning

This is AI slop, if you want to use it, fork and make it your own!

Project details


Download files

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

Source Distribution

dynimg-0.1.22.tar.gz (3.1 MB view details)

Uploaded Source

Built Distributions

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

dynimg-0.1.22-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (9.7 MB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ x86-64

dynimg-0.1.22-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (9.2 MB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ ARM64

dynimg-0.1.22-cp38-abi3-macosx_11_0_arm64.whl (8.1 MB view details)

Uploaded CPython 3.8+macOS 11.0+ ARM64

dynimg-0.1.22-cp38-abi3-macosx_10_12_x86_64.whl (8.6 MB view details)

Uploaded CPython 3.8+macOS 10.12+ x86-64

File details

Details for the file dynimg-0.1.22.tar.gz.

File metadata

  • Download URL: dynimg-0.1.22.tar.gz
  • Upload date:
  • Size: 3.1 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for dynimg-0.1.22.tar.gz
Algorithm Hash digest
SHA256 737dd5298b6b211ed0b5b8731e3fdb48fa8e483d42a87982d3cd30e19f9e9a85
MD5 cecc08b15e48eb344421d7314a8aa73f
BLAKE2b-256 3c42ba850bf6e72ed6494e3185316113b2124ebd4fc3aa4edeb66f6fbaacca32

See more details on using hashes here.

Provenance

The following attestation bundles were made for dynimg-0.1.22.tar.gz:

Publisher: release.yml on blopker/dynimg

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

File details

Details for the file dynimg-0.1.22-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for dynimg-0.1.22-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 1dc1267e6ce01f8947aa98bbf8d69844b8ef4b93b65665150f62ff559f9b2283
MD5 9802556f8a1499eb07a6b01a95400efc
BLAKE2b-256 9988d2ebcf95e5b9b3c7a06696b5195ad2bf6f25cb2390a41dab3fc2d4005e02

See more details on using hashes here.

Provenance

The following attestation bundles were made for dynimg-0.1.22-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on blopker/dynimg

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

File details

Details for the file dynimg-0.1.22-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for dynimg-0.1.22-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 04c3eacd6335e54fc8a17f70f1b45618b10803a92964f277e0ed3b1e1218bc6e
MD5 345cdc3d24c21d20c0b6fb221b9ea707
BLAKE2b-256 f6138cc0e9fed6dd5a30a7e83947af1d24f41390a614b9489a7a3bf44a407dcc

See more details on using hashes here.

Provenance

The following attestation bundles were made for dynimg-0.1.22-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on blopker/dynimg

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

File details

Details for the file dynimg-0.1.22-cp38-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for dynimg-0.1.22-cp38-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 fa3a8b07199aa0c0ce053b9373b12dd8a79f901aa4446bd51bafaadb62bf27ed
MD5 7b01d3351d8b94ab0c1ea3378141fe61
BLAKE2b-256 2010c8c568b031f7b0691149ea1ead63d5276aa41c339ea9e041df7dc6118d6a

See more details on using hashes here.

Provenance

The following attestation bundles were made for dynimg-0.1.22-cp38-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on blopker/dynimg

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

File details

Details for the file dynimg-0.1.22-cp38-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for dynimg-0.1.22-cp38-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 a5f48b14eb6d89af8b125200c5bc4d6a326854793a7d007d13f87f10546d4a8d
MD5 cdd93ccb0554d936a208d8948f752bb7
BLAKE2b-256 f9fc77a336b04fd8dcc93b5e1e8bff1fd8fb112770bcbbd1571aaf24a77475c7

See more details on using hashes here.

Provenance

The following attestation bundles were made for dynimg-0.1.22-cp38-abi3-macosx_10_12_x86_64.whl:

Publisher: release.yml on blopker/dynimg

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 Pingdom Monitoring Sentry Error logging StatusPage Status page