Sign in

    canvas

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

    moonbit
    canvas
    2d
    rasterizer
    png
    Download zip
    Author
    Version
    0.9.1
    License
    Apache-2.0
    Last updated
    2 months ago
    Downloads
    70

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