Constant MASK

Source
pub const MASK: &str = r#"
uniform int m_shape;
uniform vec2 m_center;
uniform vec2 m_radius;
uniform float m_rot;
uniform float m_feather;
uniform float m_expand;
uniform float m_opacity;
uniform int m_invert;
uniform int m_count;
// ponytail: 64 points is plenty for hand-drawn masks; a point texture would lift the cap.
uniform vec2 m_points[64];

float sd_box(vec2 p, vec2 b) {
    vec2 d = abs(p) - b;
    return length(max(d, vec2(0.0))) + min(max(d.x, d.y), 0.0);
}

// ponytail: cheap ellipse SDF (exact only on circles); good enough for a feathered edge.
float sd_ellipse(vec2 p, vec2 r) {
    vec2 rr = max(r, vec2(1e-4));
    return (length(p / rr) - 1.0) * min(rr.x, rr.y);
}

float sd_poly(vec2 p) {
    int n = min(m_count, 64);
    if (n < 3) { return 1e6; }
    vec2 w0 = p - m_points[0];
    float d = dot(w0, w0);
    float s = 1.0;
    for (int i = 0, j = n - 1; i < n; j = i, i++) {
        vec2 e = m_points[j] - m_points[i];
        vec2 w = p - m_points[i];
        vec2 b = w - e * clamp(dot(w, e) / max(dot(e, e), 1e-9), 0.0, 1.0);
        d = min(d, dot(b, b));
        bvec3 c = bvec3(p.y >= m_points[i].y, p.y < m_points[j].y, e.x * w.y > e.y * w.x);
        if (all(c) || all(not(c))) { s = -s; }
    }
    return s * sqrt(d);
}

void main() {
    vec2 q = v_uv * u_res - m_center;
    float a = radians(m_rot);
    float ca = cos(a);
    float sa = sin(a);
    q = vec2(q.x * ca + q.y * sa, -q.x * sa + q.y * ca);
    float d;
    if (m_shape == 0) {
        d = sd_box(q, m_radius);
    } else if (m_shape == 1) {
        d = sd_ellipse(q, m_radius);
    } else {
        d = sd_poly(q);
    }
    d -= m_expand;
    float cov = clamp(0.5 - d / max(m_feather, 1.0), 0.0, 1.0);
    if (m_invert != 0) { cov = 1.0 - cov; }
    out_color = vec4(cov * m_opacity);
}
"#;
Expand description

Mask rasteriser (rect / ellipse / polygon / path with feather, expand, invert, rotation). Appended to PRELUDE; everything is in canvas pixels, relative to the layer centre. Writes coverage to every channel so it reads as .r (a matte) or as alpha.