draw

tape
@profile(t1);
import gfx from "gfx";
import fill from "fill";
import draw from "draw";

/* What draw works in: your memory, nothing allocated inside */
struct DrawMemory {
    cmds: [64]draw.Cmd;
    edges: [2048]fill.Edge;
    order: [2048]i32;
    active: [2048]i32;
    cov: [256]u32;
    coverage: [65536]u8;     // one 256 x 256 mask
    filler: fill.Filler;
}

var mem: DrawMemory;

fn main() -> i32 {
    fill.init(&mem.filler, mem.edges[0..2048], mem.order[0..2048], mem.active[0..2048], mem.cov[0..256]);
    var p: draw.Path = draw.path(mem.cmds[0..64]);
    if (!draw.parse_svg(&p, "M12 2 L22 20 H2 Z")) { return 1; }       // a triangle, 24 units wide

    var s: gfx.Surface = gfx.new_surface(256, 256, gfx.Format.Rgbx) or { return 2; };
    s.clear(#FFFFFF);
    let t: draw.Target = draw.target(s.pixels() as *u32, s.width, s.height, s.stride, true);
    var m: fill.Mask = fill.Mask { pixels: mem.coverage[0..65536]; width: 256; height: 256; };

    // Filled with a gradient, scaled 10x and moved
    let place: draw.Transform = draw.Transform { scale: 10.0; tx: 8.0; ty: 8.0; };
    draw.fill_path(&mem.filler, &m, &p, place, fill.Rule.NonZero);
    var g: draw.Paint = draw.linear(0.0, 0.0, 0.0, 240.0);
    draw.stop(&g, 0.0, 0xFFB13B, 1.0);
    draw.stop(&g, 1.0, 0xE0452B, 1.0);
    draw.paint(&t, &m, 0, 0, &g, null);

    // Its outline stroked 4 px wide, round caps and joins
    draw.stroke_path(&mem.filler, &m, &p, place, 4.0);
    let ink: draw.Paint = draw.solid(0x202020, 1.0);
    draw.paint(&t, &m, 0, 0, &ink, null);
    s.free();
    return 0;
}

draw fills and strokes vector paths onto 32-bit pixels. Like fill, which it draws with, it allocates nothing: you give it the path’s storage, the filler’s arrays and the masks, so the same code runs in a program, a kernel and on Uusi. It is a pure module.

FunctionDoes
path(storage)A path over your []Cmd
move_to, line_to, quad_to, cubic_to, closeAdd to it; false when the storage is full
parse_svg(&p, d)SVG path data (M L H V Q C Z, absolute and relative); false on arcs (A, S, T) or when it doesn’t fit
fill_path(&f, &mask, &p, t, rule)Cover a mask with the path placed by t (Transform { scale, tx, ty })
stroke_path(&f, &mask, &p, t, width)Cover it with the path’s outline, round caps and joins
solid(rgb, alpha), linear(x0, y0, x1, y1), radial(cx, cy, r)Paints; add a gradient’s colours with stop(&g, t, rgb, alpha) (up to 8)
target(pixels, width, height, stride, bgr)Pixels to paint on, opaque (a gfx.Surface of format Rgbx); set_clip(&t, x, y, w, h) keeps drawing inside a rectangle
target_alpha(pixels, width, height, stride, bgr)The same over pixels with alpha (format Argb)
paint(&target, &mask, x, y, &paint, clip)Blend the paint through the mask placed at (x, y), times a clip mask or null
blur(&mask, &scratch, r)Soften a mask (a shadow)

Colours are 0xRRGGBB, alphas 0 to 1. A gradient’s coordinates are device pixels (placed(&g, t) moves one from path units). bgr: true means the pixels are 0xRRGGBB words, as a gfx.Surface is.

A target from target_alpha (its field alpha is true) has the pixel’s alpha in the word’s top byte, whichever bgr says, and colours unmultiplied. Painting blends as gfx does on an Argb surface, with the paint’s alpha times the coverage as the source’s alpha: over an opaque pixel it stays opaque, and over a transparent one the pixel gets the paint’s colour with that alpha as its own. An uncovered pixel is left as it is. A target from target is for opaque pixels: it writes 0 to the top byte, so on an Argb surface it makes what it touches transparent.

For SVG files, with arcs, dashes, any transform and CSS, use svg.

Last modified: