Rust GUI frameworks
2020 Rendering 2026: Widely depended on
13

tiny-skia: a software fallback

Skia-style drawing with no GPU and no C++.

tiny-skia is a pure-Rust CPU rasterizer that covers a subset of Skia's feature set: paths, strokes, gradients, patterns and clipping. There is no GPU, no window and no C++ dependency. You create a Pixmap, fill shapes into it and save or display the pixels.

tiny-skia-draw/src/bin/one.rs ✔ compiled · tiny-skia 0.12 · rustc 1.95.0
use book_error::{Context, Error, Result};
use tiny_skia::{Color, FillRule, Paint, PathBuilder, Pixmap, Transform};

fn main() -> Result<()> {
    // Constructors return `Option` (None for zero or absurd sizes); `context`
    // turns that into an error that says what we were trying to do.
    let mut pixmap = Pixmap::new(200, 200).context("could not allocate a 200x200 pixmap")?;
    pixmap.fill(Color::WHITE);

    let mut paint = Paint::default();
    paint.set_color_rgba8(50, 127, 150, 255);
    paint.anti_alias = true;

    let mut pb = PathBuilder::new();
    pb.move_to(100.0, 20.0);
    pb.line_to(180.0, 180.0);
    pb.line_to(20.0, 180.0);
    pb.close();
    let triangle = pb.finish().context("triangle path has no points")?;

    pixmap.fill_path(&triangle, &paint, FillRule::Winding, Transform::identity(), None);
    pixmap.save_png("triangle.png").map_err(Error::other)?;
    Ok(())
}

Its constructors return Option rather than Result, so the snippets use .context(...) to turn a None into an error that says what failed. Gradients, strokes and blends work the same way. Because it is deterministic and dependency-free it is also used as a reference renderer in tests.

tiny-skia-draw/src/bin/two.rs ✔ compiled · tiny-skia 0.12 · rustc 1.95.0
use book_error::{Context, Error, Result};
use tiny_skia::{Color, GradientStop, LinearGradient, Paint, Pixmap, Point, Rect, SpreadMode, Transform};

fn main() -> Result<()> {
    let mut pixmap = Pixmap::new(200, 100).context("could not allocate a 200x100 pixmap")?;
    pixmap.fill(Color::from_rgba8(240, 240, 240, 255));

    let mut paint = Paint::default();
    paint.shader = LinearGradient::new(
        Point::from_xy(0.0, 0.0),
        Point::from_xy(200.0, 0.0),
        vec![
            GradientStop::new(0.0, Color::from_rgba8(255, 80, 80, 255)),
            GradientStop::new(1.0, Color::from_rgba8(80, 80, 255, 255)),
        ],
        SpreadMode::Pad,
        Transform::identity(),
    )
    .context("degenerate gradient (start and end points coincide)")?;

    // A software rasterizer: no GPU, no window. This is what resvg and
    // CPU fallbacks use to turn vector shapes into pixels.
    let rect = Rect::from_xywh(10.0, 10.0, 180.0, 80.0).context("rectangle has non-finite size")?;
    pixmap.fill_rect(rect, &paint, Transform::identity(), None);
    pixmap.save_png("gradient.png").map_err(Error::other)?;
    Ok(())
}

It appears in fourteen of the lockfiles we generated, often where you would not expect. On Linux, winit itself pulls it in: winit → sctk-adwaita → tiny-skia draws client-side window decorations on Wayland. Above that, resvg renders SVG with it, iced keeps iced_tiny_skia as a software fallback, and Floem's README describes a tiny-skia CPU renderer for machines without a GPU. It is the quiet "no GPU required" layer of the ecosystem.

crates.io first publish.