Expand description
A multi-threaded, GPU-accelerated 2D graphics environment for Rust and Node: the HTML Canvas API on Skia, with GUI windows, animation, and export to raster and vector formats.
§Rust consumers: start at the crate root
Every public type is reachable as meo_skia_canvas::Thing, so nothing has
to be looked up by module first. The modules group them by subject for
reading; the prelude globs the same set for anyone who prefers one.
No signature anywhere in the crate exposes a skia_safe or neon type,
gui included, and the Node/Neon binding lives under the internal
node module. scripts/check-public-api.mjs reads rustdoc’s JSON on
every push and fails on a leak, with no module exempted – the claim is
checked rather than maintained by hand.
§The shape of it
Canvas + Context2D are the API. Method names and argument order
match CanvasRenderingContext2D, so knowledge carries over from
JavaScript: a graphics state you mutate – fill style, transform, clip –
and an encode straight to PNG, JPEG, WebP, GIF, APNG, TIFF, ICO, BMP,
AVIF, PDF or SVG – or to raw pixels in the surface’s own layout, which is
the twelfth of the formats counted above.
Everything else is the vocabulary those two speak – RgbaLinear,
Path2D, Shader, Image, the filters, the text types – and one
draw usually reaches across several, which is why they are at the root
rather than behind their modules.
Drawing something and saving it:
use meo_skia_canvas::prelude::*;
let mut canvas = Canvas::new(800.0, 400.0);
{
let ctx = canvas.context();
ctx.set_fill_style(RgbaLinear::opaque(0.05, 0.05, 0.08));
ctx.fill_rect(0.0, 0.0, 800.0, 400.0);
ctx.set_fill_style(RgbaLinear::opaque(1.0, 0.35, 0.2));
ctx.set_font(&Font::new("Helvetica", 48.0));
ctx.fill_text("Hello", 40.0, 120.0, None);
}
canvas.to_file("hello.png", &EncodeOptions::default())?;A canvas composites in the color space it was built with, and an export converts out of it:
use meo_skia_canvas::prelude::*;
let mut canvas = Canvas::with_options(
1920.0,
1080.0,
CanvasOptions {
color_space: PixelColorSpace::DisplayP3,
..CanvasOptions::default()
},
)?;
let ctx = canvas.context();
// Wider than sRGB can name, and the canvas is built to hold it.
ctx.set_fill_style(RgbaLinear::opaque(1.0, 0.0, 0.0));
ctx.fill_rect(100.0, 100.0, 200.0, 100.0);
let pixels = ctx.get_image_data(0.0, 0.0, 200.0, 100.0)?;§Colors are premultiplied and linear
RgbaLinear is not the 0-255 sRGB triple a CSS
color parses to. Components are linear-light and premultiplied by alpha,
so RgbaLinear::opaque(0.5, 0.5, 0.5) is not mid-gray on screen – it
encodes to sRGB byte 188.
When porting a CSS color, use the sRGB constructors and the conversion happens for you:
use meo_skia_canvas::prelude::*;
let grey = RgbaLinear::from_srgb8(0x80, 0x80, 0x80, 1.0); // "#808080"
let same = RgbaLinear::from_hex("#808080")?;
let translucent = RgbaLinear::from_srgb8(255, 0, 0, 0.5); // rgba(255,0,0,.5)Or skip the conversion: the fill and stroke styles take a CSS string directly, which is the shortest path when the color arrives as one. Unlike a browser, which ignores a color it cannot parse, these say so.
use meo_skia_canvas::prelude::*;
let mut canvas = Canvas::new(100.0, 100.0);
let ctx = canvas.context();
ctx.set_fill_style_css("#e33")?;
ctx.set_stroke_style_css("rgba(0, 0, 0, 0.4)")?;
assert!(ctx.set_fill_style_css("chartreuce").is_err()); // misspelled
See docs/rust.md in the repository for a longer
reference (color spaces, alpha semantics, surfaces, paint, paths, shaders,
filters, images, text, fonts).
§What runs on which thread
“Multi-threaded” in the line at the top means two pools, and they do different halves of an export.
Encoding runs on rayon. Writing a sequence hands every page to the pool
at once, and writing an animation does it a batch at a time so frames
reach the container in order – so RAYON_NUM_THREADS sizes the
compressors, and on a machine with cores to spare that is where the time
goes.
Rasterizing on the GPU does not. A Skia DirectContext belongs to the
thread that made it, so letting each rayon worker have one meant as many
contexts as workers, each cold and each holding its own resource cache;
resident memory grew with the pool and an export paid to warm every
context it touched. A bounded few threads own a context instead – four,
or fewer on a smaller machine – and a worker submits its page, waits, and
compresses the pixels it gets back where it already is. Nothing
texture-backed crosses between them.
Two consequences worth knowing. Peak memory follows the number of owners
rather than the size of the rayon pool, so raising RAYON_NUM_THREADS
buys encoding throughput without buying contexts. And none of this makes
a Canvas shareable: it is neither Send nor Sync, it stays on the
thread that made it, and the threads above are the crate’s own – reached
underneath a call that blocks until it has an answer.
That is a compile error rather than a convention, which is what lets the owners hold a Skia context safely. Sending one does not build:
use meo_skia_canvas::Canvas;
fn onto_a_thread<T: Send>(_: T) {}
onto_a_thread(Canvas::new(10.0, 10.0));and neither does sharing one:
use meo_skia_canvas::Canvas;
fn between_threads<T: Sync>(_: &T) {}
between_threads(&Canvas::new(10.0, 10.0));Rendering on the CPU has no owner and no context to belong to, so a page is rasterized wherever it is about to be encoded: both halves on the same worker, and nothing handed between them.
§What an export costs
PNG is the one format whose output depends on its own content. Both the row filtering and the deflate level are chosen by compressing a sample of the page two ways and keeping the cheaper, because what the deeper setting buys varies by more than the setting does – a page of flat interface colour gains nothing from it, and a dithered gradient gains most of its size. The answer is shared by the pages of one export and looked at again every sixteenth page, so a sequence that changes character partway is never far behind itself.
So two releases can write different bytes for the same drawing, and a PNG from this crate is not byte-comparable with one from another. The image is the same: PNG is lossless and both choices are reversible.
Nothing else adapts. JPEG, WebP and AVIF take the quality they are given, and PDF and SVG have no such choice to make.
§Rust callers are not batched
The Node binding records drawing calls into a buffer and hands them over
in one crossing, because a call from JavaScript costs more crossing the
boundary than the drawing behind it costs to do. There is no such boundary
here. Context2D mutates the recording directly, so there is nothing to
batch, nothing to flush, and no point at which a queued call has not
happened yet.
§Cargo features
vulkan– enable the Vulkan backend (Linux / Windows).metal– enable the Metal backend (macOS).window– enable thewinit-backed GUI window/event loop.freetype– bundle FreeType + WOFF2 support for font registration on Linux containers / minimal images.node-addon– register the#[neon::main]entry point so the resulting cdylib loads as a Node.js addon. Pure-Rust consumers should leave this off.
Pure-Rust consumers typically depend with default-features = false and
pick the backend they need:
[dependencies]
meo-skia-canvas = { git = "https://github.com/l7aromeo/meo-skia-canvas", default-features = false, features = ["vulkan", "freetype"] }Modules§
- canvas
- Entry point: the canvas, its pages, and the engine that draws them. The canvas document: pages in, encoded bytes out.
- color
- Colors and color spaces.
- context2d
- The stateful 2D drawing context, shaped like the Canvas API.
- error
- The crate’s error type.
- export
- Encoded output: image formats and the options that shape them.
- filter
- Image, color, and mask filters.
- font
- Font registration and variable-font axes.
- geometry
- Plain geometric value types shared across the public API.
- gui
- winit-backed windowing, behind the
windowfeature. - image
- Decoded raster images.
- js_
names - The seven types the two surfaces still spell differently.
- memory
- Returning freed memory to the operating system. Letting go of what a render left behind, once rendering stops.
- paint
- Fill and stroke styling.
- path
- Vector paths.
- pattern
- Tiled fill styles.
- pixels
- Pixel layouts for reading surfaces back and writing them.
- prelude
- Glob-importable re-export of the whole public API:
use meo_skia_canvas::prelude::*;. - shader
- Gradients and procedural shaders.
- text
- Text layout and styling.
- texture
- Hatch and stipple fill styles.
Structs§
- Affine
- 2D affine transform in
[a, b, c, d, tx, ty]form, matching the CSSDOMMatrix2DInitandCanvasRenderingContext2D.setTransformconvention. - Backend
Info - What this build renders through, and what it found to render with.
- Canvas
- A canvas document, holding one page per
Context2D. - Canvas
Options - What a canvas is built with, beyond its size.
- Color
Filter - Color-domain filter (luma, gamma transfers, color matrix, compose).
- Color
Matrix - A 4x5 color matrix, in the form
ColorFilter::matrixtakes. - Context2D
- The drawing surface of a
Canvaspage. - Dash
Pattern - An on/off dash pattern for stroked paths.
- Encode
Options - Settings applied while encoding.
- Font
- A font selection: families, size, weight, and slant.
- Font
Axis Tag - Four-byte OpenType axis tag (e.g.
"wght","wdth","opsz"). - Font
Family - What a font family offers, as
FontLibrary.family()reports it in JavaScript. - Font
Feature - One OpenType feature applied to a text run, mirroring CanvasKit’s
TextFontFeatures { name, value }. - Font
Library - Owned font registry for the Rust facade.
- Font
Variation - Variable-font axis position.
- Gradient
Interpolation - How a gradient interpolates between its stops: a colour space, and the direction hue travels within it.
- Gradient
Stop - One color stop in a gradient.
- Image
- An immutable decoded raster image.
- Image
Data - An owned pixel buffer read back from a canvas, together with the layout needed to interpret it.
- Image
Filter - Image-domain filter (blur, drop shadow, color matrix wrapped as image filter, compose).
- Invalid
Font Axis Tag - Returned by
FontAxisTag’sFromStrimpl when the input is not a 4-character ASCII string. - Line
Metrics - Per-line layout metrics.
start_indexandend_indexare byte offsets into the laid-out paragraph text. - Mask
Filter - Coverage-mask filter applied before rasterization.
- Paint
- A paint’s blend mode, cap and join, as the drawing state carries them.
- Paragraph
- Result of
TextEngine::layout_text. - Paragraph
Builder - A paragraph under construction, from
TextEngine::paragraph_builder. - Path2D
- Vector path.
- Path
Builder - Builds a
Path2Dsegment by segment. - Pattern
- A repeating fill built from an image or another canvas.
- Pixel
Export Options - Layout to read a surface back in, or write one from.
- Placeholder
- A box reserved in a paragraph for something the text engine does not draw.
- Point
- A point in surface space.
- Point3
- A point in the space a lighting filter lights, with
ztoward the viewer. - Projection
- A 3x3 transform, carrying the projective row an
Affinecannot. - Rect
- An axis-aligned rectangle, stored as its four edges.
- Rgba
Linear - A premultiplied color in linear light.
- Rich
Text Span - One span of rich text.
- Shader
- Public shader handle used by
Paint::set_shader. - Size
- A width/height pair, with no position.
- Strut
Style - A fixed line box independent of the per-run fonts, for deterministic leading (captions, subtitles, vertically-aligned blocks).
- TextBox
- One rectangle covering part of a laid-out run, and the direction the text inside it reads.
- Text
Decoration - Underline / overline / line-through flags.
- Text
Engine - Builds laid-out text from a
TextStyleand a maximum line width. - Text
Metrics - Measurements of a text run, as
measureTextreports them. - Text
Metrics Line - One line of a measured run, and the single-font stretches inside it.
- Text
Metrics Run - One stretch of a line drawn in a single font.
- Text
Position - A position within laid-out text, as
Paragraph::glyph_position_at_coordinatereports it. - Text
Shadow - Drop shadow applied behind glyphs. Multiple shadows on a single
TextStylestack additively. - Text
Style - Paragraph style.
- Texture
- A fill made of a repeated vector mark.
- Texture
Options - How a texture’s repeating mark is drawn.
Enums§
- Affinity
- Which side of a character boundary a text position sits on.
- Blend
Mode - Canvas-compatible blend modes, plus three CanvasKit ones.
- Blur
Style - Coverage-mask blur style. Mirrors CanvasKit’s
BlurStyle. - Chroma
Sampling - How many chroma samples an encoder writes per pixel.
- Color
Axis - Which color axis
ColorMatrix::rotatedturns around. - Color
Channel - One channel of a colour, for the filters that read a single one.
- DashFit
- How a dash marker is placed along the path it follows.
- Engine
Kind - The rasterizer a canvas ended up using.
- Error
- Everything this crate can fail with.
- Fill
Rule - Path2D winding rule.
- Filter
Op - One step of a CSS-style filter chain.
- Font
Stretch - How wide a face to select within a family.
- Font
Variant Caps - A capitals variant, as CSS
font-variant-caps. - Gradient
Color Space - Color space a gradient’s stops are interpolated in.
- HueMethod
- Which way around the colour wheel a gradient’s hue travels.
- Image
Format - A container format for encoded output.
- Paint
Source - What a fill or a stroke draws with.
- Paint
Style - Whether a draw fills its geometry or strokes its outline.
- PathOp
- A boolean operation between two paths.
- Path
Segment - One drawing command from a path, as
Path2D::edgesreports it. - Pattern
Repeat - Which axes a pattern repeats along.
- Pixel
Color Space - Strict export color space for surface read/write.
- Pixel
Depth - Pixel layout a surface is read back in, or written from.
- Pixel
Format - Channel layout and alpha mode of a raw frame.
- Placeholder
Alignment - Where an inline placeholder sits relative to the line it is on.
- Placeholder
Baseline - Which baseline
PlaceholderAlignment::Baselinealigns against. - Rect
Height Style - How tall the rectangles
Paragraph::rects_for_rangereturns are. - Rect
Width Style - How wide the rectangles
Paragraph::rects_for_rangereturns are. - Sampling
Mode - Image sampling strategy for
draw_image_srcand similar resampled draws. - Smoothing
Quality - How much work the filter does when an image is drawn at a size other than its own.
- Stroke
Cap - How the ends of an open stroked path are drawn.
- Stroke
Join - How two stroked segments are joined where they meet.
- Text
Align - Horizontal alignment of text within its layout width.
- Text
Baseline - Which horizontal line of the font a text draw sits on.
- Text
Decoration Style - How a decoration line is drawn.
- Text
Direction - The reading direction a run is laid out in.
- Text
Height Behavior - How the line-height multiplier is applied to the first ascent and last descent of a paragraph.
- Text
Slant - Whether glyphs are upright or slanted.
- Tile
Mode - What a filter does with the area outside its input.
Constants§
- DEFAULT_
HEIGHT - The height half of
DEFAULT_WIDTH. - DEFAULT_
WIDTH - The size a canvas has when nobody names one.