Skip to content
Clipping to a Path

Clipping to a Path

Restrict drawing to an arbitrary path.

Clipping to a Path

Usage

from std.math import cos, pi, sin

from canvas.buffer import Canvas
from canvas.color import Color
from canvas.gradient import LinearGradient
from canvas.io.png import write_png
from canvas.path import Path
from canvas.shapes.lines import draw_line_aa
from canvas.shapes.rects import fill_rect_gradient
from canvas.text.font_cache import FontCache
from canvas.text.render import draw_text

comptime W = 660
comptime H = 280
comptime PANEL = 220


def _blob(cx: Float64, cy: Float64, r: Float64) raises -> Path:
    """A closed wobbly shape, not a circle, so the clip boundary is
    somewhere `push_clip`'s rectangle could never reach.
    """
    var p = Path()
    var lobes = 5
    for i in range(41):
        var t = Float64(i) / 40.0 * 2.0 * pi
        var radius = r * (1.0 + 0.18 * sin(Float64(lobes) * t))
        var x = cx + radius * cos(t)
        var y = cy + radius * sin(t)
        if i == 0:
            p.move_to(x, y)
        else:
            p.line_to(x, y)
    p.close()
    return p^


def main() raises:
    var c = Canvas(W, H, Color(252, 252, 250))
    var cache = FontCache()

    # --- panel 1: stripes clipped to the blob -------------------------
    var blob1 = _blob(110.0, 130.0, 78.0)
    c.push_clip_path(blob1)
    var stripe = 0
    while stripe < 300:
        draw_line_aa(
            c,
            Float64(stripe) - 60.0,
            30.0,
            Float64(stripe) + 40.0,
            240.0,
            Color(40, 110, 200) if stripe % 16 == 0 else Color(220, 90, 70),
            width=5.0,
        )
        stripe += 8
    c.pop_clip_path()

    # --- panel 2: a gradient masked into the same shape ---------------
    var blob2 = _blob(330.0, 130.0, 78.0)
    c.push_clip_path(blob2)
    var grad = LinearGradient(250.0, 50.0, 410.0, 210.0)
    grad.add_stop(0.0, Color(250, 200, 60))
    grad.add_stop(0.5, Color(220, 90, 70))
    grad.add_stop(1.0, Color(70, 50, 140))
    fill_rect_gradient(c, PANEL, 0, PANEL, H, grad)
    c.pop_clip_path()

    # --- panel 3: nested clips ----------------------------------------
    # The circle extends past the blob on every side; the intersection
    # is all that draws, which is the nesting rule.
    var blob3 = _blob(550.0, 130.0, 78.0)
    var ring = Path()
    ring.move_to(550.0 + 95.0, 130.0)
    ring.arc_to(550.0, 130.0, 95.0, 0.0, 2.0 * pi)
    ring.close()

    c.push_clip_path(blob3)
    c.push_clip_path(ring)
    var y = 30
    while y < 250:
        draw_line_aa(
            c, 440.0, Float64(y), 660.0, Float64(y), Color(60, 140, 110), 4.0
        )
        y += 9
    c.pop_clip_path()
    c.pop_clip_path()

    for i in range(3):
        var labels: List[String] = [
            "stripes clipped to a path",
            "gradient masked to a path",
            "nested: circle inside blob",
        ]
        draw_text(
            c,
            Float64(20 + i * PANEL),
            262.0,
            labels[i],
            Color(60, 65, 80),
            size=12.0,
            cache=cache,
        )

    write_png(c, "examples/out_clip_path.png")