canvas

Pure-MoonBit headless 2D canvas rasterizer with PNG output.

moonbit
canvas
2d
rasterizer
png
moon add mizchi/canvas@0.9.1
Download zip
Author
Version
0.9.1
License
Apache-2.0
Last updated
14 hours ago
Downloads
34
README

#canvas-mbt

Pure-MoonBit headless 2D canvas rasterizer that mirrors the HTML CanvasRenderingContext2D API and outputs PNG via mizchi/image. Designed for embedding into mizchi/crater.

  • Targets: js, native, wasm-gc
  • Deps: mizchi/font, mizchi/image
  • No FFI in canvas-mbt itself. Pure MoonBit throughout; the transitive zlib dependency links against the system libz on the native target only.

See src/README.mbt.md for the full Getting Started walkthrough (also executed as a doc test).

#Quick start

test {
let canvas = @canvas.Canvas::new(200, 100)
let ctx = canvas.context()
ctx.set_fill_style(@canvas.Color::rgb(255, 0, 0))
ctx.fill_rect(10.0, 10.0, 50.0, 50.0)
let _png : Bytes = canvas.to_png()
inspect(canvas.width, content="200")
}

#Development

just # check + test (js target) just test # run tests just check # type check with --deny-warn just ci-all # check + test on js, native, and wasm-gc

#mizchi/canvas

Pure-MoonBit headless 2D canvas rasterizer. Mirrors a subset of the HTML CanvasRenderingContext2D API and emits PNG via mizchi/image.

#Package

  • mizchi/canvas

#Getting Started

///|
test {
let canvas = @canvas.Canvas::new(200, 100)
let ctx = canvas.context()
ctx.set_fill_style(@canvas.Color::rgb(255, 0, 0))
ctx.fill_rect(10.0, 10.0, 50.0, 50.0)
// Convert to PNG bytes.
let _png : Bytes = canvas.to_png()
ignore(_png)
inspect(canvas.width, content="200")
inspect(canvas.height, content="100")
}

#Features

  • Color with hex / rgb() / rgba() / named parsing
  • Canvas framebuffer with optional 4x4 supersampling antialiasing
  • Context with rect, path, transform, text, and image drawing APIs
  • Path2D with lines, quadratic / cubic Bezier curves, arcs, and rectangles
  • Source-over blending, save/restore state stack, affine Matrix2D transforms
  • Text rendering via mizchi/font TTF glyph outlines
  • PNG encoding via mizchi/image

#
ColorParseError

pub suberror ColorParseError {
ColorParseError(String)
}

#
Canvas

pub struct Canvas {
width : Int
height : Int
pixels : FixedArray[Byte]
antialias : Bool
}

Headless 2D canvas backed by an RGBA8 (non-premultiplied) framebuffer.

Pixel layout: (y * width + x) * 4 offset, [r, g, b, a] bytes, each channel 0..=255. Alpha is straight (non-premultiplied).

Internally stored as FixedArray[Byte] so clear() and raster writes can mutate in place. to_image_data() converts to a Bytes for output.

#
Canvas::clear

fn Canvas::clear(self : Canvas) -> Unit

#
Canvas::context

fn Canvas::context(self : Canvas) -> Context

Create a drawing context bound to this canvas. The returned context starts with default state (black fill/stroke, 1px line width, identity transform, global alpha 1.0, alphabetic text baseline, start text alignment).

#
Canvas::new

fn Canvas::new(width : Int, height : Int, antialias? : Bool) -> Canvas

#
Canvas::to_image_data

fn Canvas::to_image_data(self : Canvas) ->
ImageData

#
Canvas::to_png

fn Canvas::to_png(self : Canvas) -> Bytes raise
EncodeError

#
ClipMask

pub struct ClipMask {
width : Int
height : Int
data : FixedArray[Byte]
} derive(Eq)

An 8-bit coverage mask the size of the canvas framebuffer. Each byte data[y * width + x] is the clip opacity at that pixel: 0 = fully clipped out, 255 = fully visible.

Stored on DrawState.clip lazily: canvases without clip keep clip = None and incur zero memory overhead.
impl Show for ClipMask

#
ClipMask::clone

fn ClipMask::clone(self : ClipMask) -> ClipMask

Return an independent deep copy of the mask data.

#
ClipMask::new

fn ClipMask::new(width : Int, height : Int) -> ClipMask

Create a fully-visible mask (every byte == 255) at the given size.

#
Color

pub struct Color {
r : Int
g : Int
b : Int
a : Double
} derive(Eq)

RGBA color in non-premultiplied form. r/g/b are integers in 0..=255, a is a Double in 0.0..=1.0.
impl Show for Color

#
Color::black

fn Color::black() -> Color

#
Color::parse

fn Color::parse(css : String) -> Color raise ColorParseError

#
Color::rgb

fn Color::rgb(r : Int, g : Int, b : Int) -> Color

#
Color::rgba

fn Color::rgba(r : Int, g : Int, b : Int, a : Double) -> Color

#
Color::transparent

fn Color::transparent() -> Color

#
Color::white

fn Color::white() -> Color

#
ColorStop

pub struct ColorStop {
offset : Double
color : Color
} derive(Eq)

A single color stop in a gradient. offset is in 0.0..=1.0.
impl Show for ColorStop

#
ColorStop::new

fn ColorStop::new(offset : Double, color : Color) -> ColorStop

#
Context

pub struct Context {
canvas : Canvas
state : DrawState
stack : Array[DrawState]
current_path : Path2D
glyph_cache : Map[Int,
GlyphBitmap
]
}

2D drawing context. Mirrors a subset of CanvasRenderingContext2D.

#
Context::arc

fn Context::arc(self : Context, x : Double, y : Double, radius : Double, start_angle : Double, end_angle : Double, counterclockwise? : Bool) -> Unit

#
Context::arc_to

fn Context::arc_to(self : Context, x1 : Double, y1 : Double, x2 : Double, y2 : Double, radius : Double) -> Unit

#
Context::begin_path

fn Context::begin_path(self : Context) -> Unit

#
Context::bezier_curve_to

fn Context::bezier_curve_to(self : Context, cp1x : Double, cp1y : Double, cp2x : Double, cp2y : Double, x : Double, y : Double) -> Unit

#
Context::clear_rect

fn Context::clear_rect(self : Context, x : Double, y : Double, w : Double, h : Double) -> Unit

#
Context::clip

fn Context::clip(self : Context) -> Unit

Intersects the current path with the active clip region. Subsequent fills and strokes are masked to the intersection. Matches HTML Canvas ctx.clip() semantics (non-zero winding, intersection-only).

On first use, allocates a zeroed ClipMask sized to the canvas framebuffer and rasterizes the current path into it. On subsequent calls, rasterizes the path into a temporary mask and multiplies it into the existing mask. Uses the canvas's antialias setting for the clip rasterization.

#
Context::close_path

fn Context::close_path(self : Context) -> Unit

#
Context::draw_image

fn Context::draw_image(self : Context, image :
ImageData
, dx : Double, dy : Double, scale? : (Double, Double)?, src_rect? : (Double, Double, Double, Double)?, smoothing? : Bool) -> Unit

Draw an image onto the canvas at (dx, dy).

  • scale optionally rescales the destination rect (defaults to (1, 1)).
  • src_rect optionally selects a sub-region of the source (defaults to the whole image).
  • smoothing toggles bilinear (true) vs nearest-neighbor (false) sampling. Defaults to true to mirror the HTML Canvas default.

#
Context::ellipse

fn Context::ellipse(self : Context, x : Double, y : Double, rx : Double, ry : Double, rotation : Double, start_angle : Double, end_angle : Double, counterclockwise? : Bool) -> Unit

#
Context::fill

fn Context::fill(self : Context) -> Unit

Fills the current_path (built by begin_path / move_to / line_to / ...).

#
Context::fill_path

fn Context::fill_path(self : Context, path : Path2D) -> Unit

#
Context::fill_rect

fn Context::fill_rect(self : Context, x : Double, y : Double, w : Double, h : Double) -> Unit

#
Context::fill_text

fn Context::fill_text(self : Context, text : String, x : Double, y : Double) -> Unit

Rasterize text onto the canvas, using the current fill_style as the glyph tint and the current transform to position the pen.

Text is drawn with an alphabetic baseline at (x, y); each glyph bitmap is produced by @font.rasterize_glyph and then blitted using source-over blending with the glyph's alpha channel as coverage. If no font is set, this is a no-op.

#
Context::line_to

fn Context::line_to(self : Context, x : Double, y : Double) -> Unit

#
Context::measure_text

fn Context::measure_text(self : Context, text : String) -> TextMetrics

Measure a string using the currently-installed font. If no font has been set on the context, returns all-zero metrics (matching the degenerate default of HTML canvas before a font is chosen).

#
Context::move_to

fn Context::move_to(self : Context, x : Double, y : Double) -> Unit

#
Context::new

fn Context::new(canvas : Canvas) -> Context

#
Context::quadratic_curve_to

fn Context::quadratic_curve_to(self : Context, cpx : Double, cpy : Double, x : Double, y : Double) -> Unit

#
Context::rect

fn Context::rect(self : Context, x : Double, y : Double, w : Double, h : Double) -> Unit

#
Context::reset_transform

fn Context::reset_transform(self : Context) -> Unit

#
Context::restore

fn Context::restore(self : Context) -> Unit

Pop the last saved state off the stack. A restore with no prior save is a no-op (matches HTML Canvas semantics).

#
Context::rotate

fn Context::rotate(self : Context, angle : Double) -> Unit

#
Context::round_rect

fn Context::round_rect(self : Context, x : Double, y : Double, w : Double, h : Double, radii : Array[Double]) -> Unit

#
Context::round_rect_uniform

fn Context::round_rect_uniform(self : Context, x : Double, y : Double, w : Double, h : Double, r : Double) -> Unit

Convenience for a rounded rect with all four corners at the same radius.

#
Context::save

fn Context::save(self : Context) -> Unit

Push the current draw state onto the save stack.

#
Context::scale

fn Context::scale(self : Context, sx : Double, sy : Double) -> Unit

#
Context::set_fill_style

fn Context::set_fill_style(self : Context, c : Color) -> Unit

#
Context::set_fill_style_of

fn Context::set_fill_style_of(self : Context, style : FillStyle) -> Unit

#
Context::set_font

fn Context::set_font(self : Context, font :
TTFont
, size_px : Double) -> Unit

#
Context::set_global_alpha

fn Context::set_global_alpha(self : Context, a : Double) -> Unit

#
Context::set_line_cap

fn Context::set_line_cap(self : Context, cap : LineCap) -> Unit

Set the line cap shape for subsequent strokes. Matches HTML Canvas ctx.lineCap. Applies to both path endpoints and dashed-stroke on-segment endpoints.

#
Context::set_line_dash

fn Context::set_line_dash(self : Context, segments : Array[Double]) -> Unit

Set the dash pattern for subsequent strokes. An empty array produces a solid line. If the array length is odd, it is internally doubled (e.g. [5, 10, 15] becomes [5, 10, 15, 5, 10, 15]). Negative, NaN, or infinite values are stored as-is and cause the pattern to be silently treated as solid at stroke time. Matches HTML Canvas setLineDash.

#
Context::set_line_dash_offset

fn Context::set_line_dash_offset(self : Context, offset : Double) -> Unit

Set the dash phase offset. Subsequent strokes begin their dash pattern offset pixels into the cycle. Defaults to 0.0. Matches HTML Canvas lineDashOffset.

#
Context::set_line_join

fn Context::set_line_join(self : Context, join : LineJoin) -> Unit

Set the line join shape for subsequent strokes. Matches HTML Canvas ctx.lineJoin. Miter preserves the existing automatic bevel fallback at the fixed 10.0 miter limit.

#
Context::set_line_width

fn Context::set_line_width(self : Context, w : Double) -> Unit

#
Context::set_stroke_style

fn Context::set_stroke_style(self : Context, c : Color) -> Unit

#
Context::set_stroke_style_of

fn Context::set_stroke_style_of(self : Context, style : FillStyle) -> Unit

#
Context::set_text_align

fn Context::set_text_align(self : Context, align : TextAlign) -> Unit

#
Context::set_text_baseline

fn Context::set_text_baseline(self : Context, baseline : TextBaseline) -> Unit

#
Context::set_transform

fn Context::set_transform(self : Context, a : Double, b : Double, c : Double, d : Double, e : Double, f : Double) -> Unit

#
Context::stroke

fn Context::stroke(self : Context) -> Unit

Strokes the current_path (built by begin_path / move_to / line_to / ...).

#
Context::stroke_path

fn Context::stroke_path(self : Context, path : Path2D) -> Unit

#
Context::stroke_rect

fn Context::stroke_rect(self : Context, x : Double, y : Double, w : Double, h : Double) -> Unit

#
Context::transform

fn Context::transform(self : Context, a : Double, b : Double, c : Double, d : Double, e : Double, f : Double) -> Unit

#
Context::translate

fn Context::translate(self : Context, x : Double, y : Double) -> Unit

#
DrawState

pub struct DrawState {
fill_style : FillStyle
stroke_style : FillStyle
line_width : Double
global_alpha : Double
transform : Matrix2D
font :
TTFont
?
font_size : Double
text_align : TextAlign
text_baseline : TextBaseline
clip : ClipMask?
line_dash : Array[Double]
line_dash_offset : Double
line_cap : LineCap
line_join : LineJoin
}

Mutable drawing state maintained by Context. Snapshots of this struct are pushed onto a stack by save/restore.

#
DrawState::default

fn DrawState::default() -> DrawState

#
DrawState::snapshot

fn DrawState::snapshot(self : DrawState) -> DrawState

Returns a value-copy of the state (for the save-stack). font is copied as a reference because @font.TTFont is a struct.

#
FillStyle

pub enum FillStyle {
Solid(Color)
Linear(LinearGradient)
Radial(RadialGradient)
} derive(Eq)

A draw-time fill source — solid color or gradient.
impl Show for FillStyle

#
FillStyle::linear

#
FillStyle::radial

#
FillStyle::solid

fn FillStyle::solid(c : Color) -> FillStyle

#
LineCap

pub(all) enum LineCap {
Butt
Round
Square
} derive(Eq)

Shape at the endpoints of a stroked path (and each dashed on-segment). Matches HTML Canvas CanvasRenderingContext2D.lineCap.
impl Show for LineCap

#
LineCap::all

fn LineCap::all() -> Array[LineCap]

Materializes every LineCap variant once. Used to silence unused_constructor warnings under --deny-warn.

#
LineJoin

pub(all) enum LineJoin {
Miter
Round
Bevel
} derive(Eq)

Shape at interior vertices (corners) of a stroked path. Matches HTML Canvas CanvasRenderingContext2D.lineJoin.
impl Show for LineJoin

#
LineJoin::all

fn LineJoin::all() -> Array[LineJoin]

#
LinearGradient

pub struct LinearGradient {
x0 : Double
y0 : Double
x1 : Double
y1 : Double
stops : Array[ColorStop]
} derive(Eq)

Linear gradient from (x0, y0) to (x1, y1) in user space. Stops are sorted by offset at construction time; out-of-range t values clamp to the first/last color.

#
LinearGradient::new

fn LinearGradient::new(x0 : Double, y0 : Double, x1 : Double, y1 : Double, stops~ : Array[ColorStop]) -> LinearGradient

#
Matrix2D

pub struct Matrix2D {
a : Double
b : Double
c : Double
d : Double
e : Double
f : Double
} derive(Eq,
Debug
)

2×3 affine transform matrix. Maps (x, y) -> (a*x + c*y + e, b*x + d*y + f). Storage follows the Canvas2D convention setTransform(a, b, c, d, e, f).
impl Show for Matrix2D

#
Matrix2D::identity

fn Matrix2D::identity() -> Matrix2D

#
Matrix2D::invert

fn Matrix2D::invert(self : Matrix2D) -> Matrix2D?

Returns the affine inverse of this matrix, or None if the matrix is singular (determinant is zero).

#
Matrix2D::multiply

fn Matrix2D::multiply(self : Matrix2D, o : Matrix2D) -> Matrix2D

#
Matrix2D::of

fn Matrix2D::of(a : Double, b : Double, c : Double, d : Double, e : Double, f : Double) -> Matrix2D

#
Matrix2D::rotate

fn Matrix2D::rotate(self : Matrix2D, angle : Double) -> Matrix2D

#
Matrix2D::scale

fn Matrix2D::scale(self : Matrix2D, sx : Double, sy : Double) -> Matrix2D

#
Matrix2D::transform_point

fn Matrix2D::transform_point(self : Matrix2D, x : Double, y : Double) -> (Double, Double)

#
Matrix2D::translate

fn Matrix2D::translate(self : Matrix2D, x : Double, y : Double) -> Matrix2D

#
Path2D

pub struct Path2D {
commands : Array[PathCmd]
}

A path — an ordered sequence of drawing commands. Mirrors Path2D from the HTML Canvas2D API.

#
Path2D::arc

fn Path2D::arc(self : Path2D, cx : Double, cy : Double, radius : Double, start_angle : Double, end_angle : Double, counterclockwise? : Bool) -> Unit

Approximate a circular arc with cubic Beziers of at most 90° per segment. Angles are in radians, measured from the +X axis. The arc is traversed counter-clockwise when counterclockwise=true, clockwise otherwise (matching HTML Canvas semantics).

#
Path2D::arc_to

fn Path2D::arc_to(self : Path2D, x1 : Double, y1 : Double, x2 : Double, y2 : Double, radius : Double) -> Unit

Append a tangent arc between two lines. Mirrors HTML Canvas arcTo(). Silent normalization: negative radius → absolute value. Degenerate cases (no current point, collinear, r=0) → LineTo(x1, y1).

#
Path2D::bezier_curve_to

fn Path2D::bezier_curve_to(self : Path2D, cp1x : Double, cp1y : Double, cp2x : Double, cp2y : Double, x : Double, y : Double) -> Unit

#
Path2D::close

fn Path2D::close(self : Path2D) -> Unit

#
Path2D::ellipse

fn Path2D::ellipse(self : Path2D, cx : Double, cy : Double, rx : Double, ry : Double, rotation : Double, start_angle : Double, end_angle : Double, counterclockwise? : Bool) -> Unit

Append an elliptical arc. Mirrors HTML Canvas ellipse(). Emits MoveTo to the arc start, then ≤90° cubic Bezier segments. Silent normalization: negative rx/ry → absolute value.

#
Path2D::line_to

fn Path2D::line_to(self : Path2D, x : Double, y : Double) -> Unit

#
Path2D::move_to

fn Path2D::move_to(self : Path2D, x : Double, y : Double) -> Unit

#
Path2D::new

fn Path2D::new() -> Path2D

#
Path2D::quadratic_curve_to

fn Path2D::quadratic_curve_to(self : Path2D, cpx : Double, cpy : Double, x : Double, y : Double) -> Unit

#
Path2D::rect

fn Path2D::rect(self : Path2D, x : Double, y : Double, w : Double, h : Double) -> Unit

#
Path2D::round_rect

fn Path2D::round_rect(self : Path2D, x : Double, y : Double, w : Double, h : Double, radii : Array[Double]) -> Unit

Append a rounded rectangle sub-path. Mirrors HTML Canvas roundRect().

radii length: 1 = uniform, 2 = (tl/br, tr/bl), 3 = (tl, tr/bl, br), 4 = (tl, tr, br, bl). Invalid lengths → nothing emitted. Negative radii → abs(). Negative w/h → rect flips. Radii exceeding min(|w|,|h|)/2 are proportionally scaled.

#
PathCmd

pub enum PathCmd {
MoveTo(Double, Double)
LineTo(Double, Double)
QuadTo(Double, Double, Double, Double)
CubicTo(Double, Double, Double, Double, Double, Double)
Close
} derive(Eq)

A single command in a Path2D. Matches the subset of Canvas2D commands needed for fill and stroke.
impl Show for PathCmd

#
RadialGradient

pub struct RadialGradient {
x0 : Double
y0 : Double
r0 : Double
x1 : Double
y1 : Double
r1 : Double
stops : Array[ColorStop]
} derive(Eq)

Radial gradient from circle (x0, y0, r0) to circle (x1, y1, r1) in user space. Matches HTML Canvas createRadialGradient.

#
RadialGradient::new

fn RadialGradient::new(x0 : Double, y0 : Double, r0 : Double, x1 : Double, y1 : Double, r1 : Double, stops~ : Array[ColorStop]) -> RadialGradient

#
TextAlign

pub enum TextAlign {
Start
End
Left
Right
Center
} derive(Eq)

Text alignment relative to the draw point.
impl Show for TextAlign

#
TextAlign::all

fn TextAlign::all() -> Array[TextAlign]

All TextAlign variants, in declaration order. Exposed so that later tasks (and external callers) have a stable way to enumerate them; also keeps every constructor reachable from the public API.

#
TextBaseline

pub enum TextBaseline {
Alphabetic
Top
Middle
Bottom
} derive(Eq)

Vertical baseline to which text is anchored.

#
TextBaseline::all

All TextBaseline variants, in declaration order.

#
TextMetrics

pub struct TextMetrics {
width : Double
ascent : Double
descent : Double
}

Metrics describing the rendered geometry of a string, mirroring the subset of TextMetrics we currently expose.
impl Show for TextMetrics

#
blend_over

fn blend_over(pixels : FixedArray[Byte], offset : Int, src : Color, coverage : Int, global_alpha : Double) -> Unit

Source-over blend into a pixel at offset in a RGBA8 non-premultiplied buffer.

coverage is an integer 0..=256 (256 == fully covered). global_alpha multiplies the source alpha further (0.0..=1.0). Fast paths:
  • coverage == 0 → early return (source is invisible).
  • src.a * coverage/256 * global_alpha <= 0 → early return.

#
clear_polylines

fn clear_polylines(pixels : FixedArray[Byte], width : Int, height : Int, polylines : Array[Array[(Double, Double)]]) -> Unit

Zero every pixel inside a set of polylines. Used by Context::clear_rect and other operations that need to erase a region rather than blend. Uses the same non-zero winding scanline approach as fill_polylines but writes zeros directly instead of calling blend_over.

#
dash_polylines

fn dash_polylines(polylines : Array[Array[(Double, Double)]], pattern : Array[Double], offset : Double) -> Array[Array[(Double, Double)]]

Split each sub-polyline into dashed sub-polylines. Returns only the "on" segments; "off" gaps are dropped from the output. Each input sub-polyline restarts the dash phase from offset (matching HTML Canvas moveTo semantics — dash is not continuous across sub-paths). Caller must have validated pattern via is_valid_dash_pattern.

#
draw_image_raw

fn draw_image_raw(pixels : FixedArray[Byte], dst_w : Int, dst_h : Int, src :
ImageData
, dst_x : Double, dst_y : Double, scale_x : Double, scale_y : Double, src_x : Double, src_y : Double, src_w : Double, src_h : Double, matrix : Matrix2D, global_alpha : Double, smoothing : Bool) -> Unit

Raw axis-aligned image blit. Samples per destination pixel via nearest or bilinear interpolation, applies the matrix to map dest rect coordinates, and composites via source-over blend_over.

This is the low-level primitive used by Context::draw_image. It doesn't handle rotation or shear cleanly (the destination rect is computed from the matrix-transformed axis-aligned corners and sampled as an axis-aligned rectangle — acceptable for v0).

#
fill_polylines

fn fill_polylines(pixels : FixedArray[Byte], width : Int, height : Int, polylines : Array[Array[(Double, Double)]], style : FillStyle, matrix : Matrix2D, clip : ClipMask?, global_alpha : Double, aa : Bool) -> Unit

Fill polylines using non-zero winding. AA off: each pixel is fully covered (coverage == 256) or not covered at all. Gradient fills sample per pixel after inverse-transforming the pixel center into user space.

#
flatten

fn flatten(cmds : Array[PathCmd], matrix : Matrix2D, flatness : Double) -> Array[Array[(Double, Double)]]

Flatten a Path2D command stream into polylines in destination (post-matrix) space. Each Close or a new MoveTo starts a new sub-polyline. Control points are subdivided adaptively until each segment is within flatness destination pixels of the ideal curve. flatness must be positive; the caller is responsible for guarding against NaN and ≤0 values.

Empty polylines (produced by e.g. consecutive Close commands) are discarded from the output.

#
interp_stops

fn interp_stops(stops : Array[ColorStop], t : Double) -> Color

Interpolate a color from a sorted, non-empty stops array at position t. Values of t outside [stops[0].offset, stops[last].offset] clamp to the first/last color. Interpolation is sRGB component-wise.

#
intersect_clip

fn intersect_clip(dst : ClipMask, src : ClipMask) -> Unit

In-place intersection: dst.data[i] = (dst.data[i] * src.data[i] + 127) / 255. Uses integer math with half-to-up rounding to minimize drift across successive intersections. dst and src must have matching dimensions; mismatched sizes lead to out-of-bounds reads and are the caller's problem.

#
rasterize_clip

fn rasterize_clip(target : ClipMask, polylines : Array[Array[(Double, Double)]], aa : Bool) -> Unit

Rasterize polylines into target.data as an 8-bit coverage field. Writes (not multiplies) per-pixel coverage for the non-zero winding interior. Caller must pre-zero target if it was freshly ClipMask::new'd (which starts at 255). When aa is true, coverage is computed via 4x4 supersampling matching fill_polylines_aa.

#
sample_linear

fn sample_linear(g : LinearGradient, u : Double, v : Double) -> Color

Sample a linear gradient at user-space (u, v). The gradient parameter t is the projection of (u - x0, v - y0) onto the axis (x1 - x0, y1 - y0), normalized by the axis squared length. Values of t outside [0, 1] clamp to the endpoint colors.

#
sample_radial

fn sample_radial(g : RadialGradient, u : Double, v : Double) -> Color

Sample a 2-circle radial gradient at user-space (u, v). Solves the quadratic for the parameter t such that (u, v) lies on the circle P(t) = (1-t) * C0 + t * C1 with radius r0 + t * dr. Picks the larger valid root (outer circle contribution). Out-of-range t clamps to the endpoint colors. Returns Color::transparent() when no valid root exists (the pixel is outside the gradient's drawable region).

#
stroke_polylines

fn stroke_polylines(pixels : FixedArray[Byte], width : Int, height : Int, polylines : Array[Array[(Double, Double)]], style : FillStyle, line_width : Double, line_cap : LineCap, line_join : LineJoin, matrix : Matrix2D, clip : ClipMask?, global_alpha : Double, aa : Bool) -> Unit

Stroke each polyline by expanding it into an offset polygon and filling. Cap = butt (perpendicular at endpoints), join = miter with a hard miter limit of 10.0; past the limit, fall back to bevel.