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.
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.
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.