Skip to main content

cranpose_ui_graphics/
path_fill.rs

1//! Path fills cut into vertical slices the shape stage draws on the GPU.
2//!
3//! A slice is the part of a fill between two of the path's edges over a run
4//! of columns that no vertex and no crossing of edges falls inside, so both
5//! of its edges are straight across it: a trapezoid with vertical sides.
6//! Slices that meet side by side tile the fill. Each owns the pixels whose
7//! centres lie in its columns, so only the fill's outline is anti-aliased
8//! and no seam shows between slices.
9
10use crate::{PathFillRule, Point, Rect};
11
12/// One vertical slice of a filled path: the area between a top and a bottom
13/// edge, each straight, over the columns from `left` to `right`.
14///
15/// A side that the next slice of the same fill continues past is shared:
16/// a pixel there belongs to the slice whose columns hold its centre, and
17/// the side is drawn hard. An open side is part of the fill's outline, and
18/// its pixels take the share of them it covers.
19#[derive(Clone, Copy, Debug, PartialEq)]
20pub struct Trapezoid {
21    pub left: f32,
22    pub right: f32,
23    /// The top edge's y at `left` and at `right`.
24    pub top: [f32; 2],
25    /// The bottom edge's y at `left` and at `right`.
26    pub bottom: [f32; 2],
27    pub open_left: bool,
28    pub open_right: bool,
29}
30
31impl Trapezoid {
32    /// The box of its corners.
33    pub fn bounds(&self) -> Rect {
34        let top = self.top[0].min(self.top[1]);
35        let bottom = self.bottom[0].max(self.bottom[1]);
36        Rect {
37            x: self.left,
38            y: top,
39            width: self.right - self.left,
40            height: bottom - top,
41        }
42    }
43
44    /// The slice moved by `dx`, `dy`.
45    pub fn translate(self, dx: f32, dy: f32) -> Self {
46        Self {
47            left: self.left + dx,
48            right: self.right + dx,
49            top: self.top.map(|y| y + dy),
50            bottom: self.bottom.map(|y| y + dy),
51            ..self
52        }
53    }
54
55    /// The share of the pixel centred at `point` the slice covers, as the
56    /// shape stage takes it: half a pixel each way across each edge and
57    /// each open side. A shared side owns the centres in its columns whole.
58    pub fn coverage(&self, point: Point) -> f32 {
59        let owned =
60            (self.open_left || point.x >= self.left) && (self.open_right || point.x < self.right);
61        if self.right <= self.left || !owned {
62            return 0.0;
63        }
64        let corner = |x: f32, y: f32| Point::new(x, y);
65        let below_top = edge_distance(
66            point,
67            corner(self.left, self.top[0]),
68            corner(self.right, self.top[1]),
69            1.0,
70        );
71        let above_bottom = edge_distance(
72            point,
73            corner(self.left, self.bottom[0]),
74            corner(self.right, self.bottom[1]),
75            -1.0,
76        );
77        let band = half_pixel(below_top) + half_pixel(above_bottom) - 1.0;
78        let left = if self.open_left {
79            half_pixel(point.x - self.left)
80        } else {
81            1.0
82        };
83        let right = if self.open_right {
84            half_pixel(self.right - point.x)
85        } else {
86            1.0
87        };
88        band.max(0.0) * (left + right - 1.0).clamp(0.0, 1.0)
89    }
90}
91
92/// How far `point` lies inside the edge from `a` to `b`, `side` 1 for a top
93/// edge and -1 for a bottom one: across the edge beside it, and from the
94/// nearer end past either end, so a steep edge does not shade the column
95/// past its end. The shape stage's `trapezoid_edge_distance` is the same.
96fn edge_distance(point: Point, a: Point, b: Point, side: f32) -> f32 {
97    let (dx, dy) = (b.x - a.x, b.y - a.y);
98    let extent = (dx * dx + dy * dy).sqrt();
99    let (along_x, along_y) = (dx / extent, dy / extent);
100    let (offset_x, offset_y) = (point.x - a.x, point.y - a.y);
101    let across = (offset_y * along_x - offset_x * along_y) * side;
102    let t = offset_x * along_x + offset_y * along_y;
103    if (0.0..=extent).contains(&t) {
104        return across;
105    }
106    let end = if t < 0.0 { a } else { b };
107    let distance = ((point.x - end.x).powi(2) + (point.y - end.y).powi(2)).sqrt();
108    if across < 0.0 {
109        -distance
110    } else if across > 0.0 {
111        distance
112    } else {
113        0.0
114    }
115}
116
117/// The share of a pixel inside an edge whose distance from its centre is
118/// `distance`, positive inside.
119fn half_pixel(distance: f32) -> f32 {
120    (distance + 0.5).clamp(0.0, 1.0)
121}
122
123/// The most edges a fill is sliced with; a larger path keeps its mask.
124const MAX_EDGES: usize = 1024;
125/// The most slices one fill is drawn as.
126const MAX_SLICES: usize = 4096;
127/// How near two values count as equal, in the path's units.
128const TOLERANCE: f32 = 1.0e-3;
129
130/// An edge of a path that is not upright, from its left end to its right,
131/// with the winding direction it ran in.
132#[derive(Clone, Copy, Debug)]
133struct Edge {
134    x0: f32,
135    y0: f32,
136    x1: f32,
137    y1: f32,
138    winding: i32,
139}
140
141impl Edge {
142    /// Its y at `x`, exact at its ends, so slices that meet at a vertex
143    /// agree where.
144    fn y_at(&self, x: f32) -> f32 {
145        if x <= self.x0 {
146            self.y0
147        } else if x >= self.x1 {
148            self.y1
149        } else {
150            self.y0 + (x - self.x0) * (self.y1 - self.y0) / (self.x1 - self.x0)
151        }
152    }
153}
154
155/// An edge across one slice: its y at the slice's left and right sides.
156#[derive(Clone, Copy, Debug)]
157struct Crossing {
158    edge: Edge,
159    start: f32,
160    end: f32,
161}
162
163/// Cuts path fills into [`Trapezoid`]s, reusing its buffers from one fill
164/// to the next.
165#[derive(Debug, Default)]
166pub(crate) struct PathSlicer {
167    edges: Vec<Edge>,
168    events: Vec<f32>,
169    active: Vec<Edge>,
170    crossings: Vec<Crossing>,
171    slices: Vec<Trapezoid>,
172}
173
174impl PathSlicer {
175    /// The fill of `contours`, each closed, under `fill_rule`, as slices
176    /// from left to right. `None` when a slice's side meets only part of
177    /// its neighbour's, as an upright edge inside the fill makes it, when a
178    /// coordinate is not finite, or when the path has too many edges.
179    pub(crate) fn slice<'a, I>(
180        &mut self,
181        contours: I,
182        fill_rule: PathFillRule,
183    ) -> Option<&[Trapezoid]>
184    where
185        I: IntoIterator<Item = &'a [Point]>,
186        I::IntoIter: Clone,
187    {
188        let filled = contours.into_iter().filter(|points| points.len() >= 3);
189        if let (Some(points), None) = (filled.clone().next(), filled.clone().nth(1)) {
190            match self.slice_monotone(points) {
191                ChainSlicing::Sliced => return Some(&self.slices),
192                ChainSlicing::Unsliceable => return None,
193                ChainSlicing::NotMonotone => {}
194            }
195        }
196        self.collect_edges(filled)?;
197        self.slices.clear();
198        self.active.clear();
199        self.events.clear();
200        self.events
201            .extend(self.edges.iter().flat_map(|edge| [edge.x0, edge.x1]));
202        // A contour's edges come in runs already ordered, as a chart's do
203        // from its left end to its right and back; a stable sort merges the
204        // runs instead of sorting the whole.
205        self.events.sort_by(f32::total_cmp);
206        self.events.dedup();
207        self.edges.sort_by(|a, b| a.x0.total_cmp(&b.x0));
208        let mut next_edge = 0;
209        let mut previous = 0..0;
210        let mut event = 0;
211        let mut left = self.events.first().copied().unwrap_or_default();
212        while let Some(&next_event) = self.events.get(event + 1) {
213            while let Some(&edge) = self.edges.get(next_edge).filter(|edge| edge.x0 <= left) {
214                self.active.push(edge);
215                next_edge += 1;
216            }
217            self.active.retain(|edge| edge.x1 > left);
218            let cut = self.order_crossings(left, next_event);
219            let right = cut.unwrap_or(next_event);
220            let first = self.slices.len();
221            self.push_spans(left, right, fill_rule);
222            let (before, after) = self.slices.split_at_mut(first);
223            share_sides(&mut before[previous], after)?;
224            if self.slices.len() > MAX_SLICES {
225                return None;
226            }
227            previous = first..self.slices.len();
228            left = right;
229            if cut.is_none() {
230                event += 1;
231            }
232        }
233        Some(&self.slices)
234    }
235
236    /// Slices a contour that each upright line crosses at most twice, as a
237    /// chart's area or a convex shape: its two chains from the leftmost
238    /// vertex to the rightmost, walked together, give the slices from left
239    /// to right with no sorting. They are the slices the sweep in
240    /// [`Self::slice`] makes of the contour.
241    fn slice_monotone(&mut self, points: &[Point]) -> ChainSlicing {
242        if points
243            .iter()
244            .any(|point| !(point.x.is_finite() && point.y.is_finite()))
245        {
246            return ChainSlicing::Unsliceable;
247        }
248        let Some((leftmost, rightmost)) = horizontal_extremes(points) else {
249            return ChainSlicing::NotMonotone;
250        };
251        if !chain_edges(points, leftmost, rightmost, 1, &mut self.edges)
252            || !chain_edges(points, leftmost, rightmost, -1, &mut self.active)
253        {
254            return ChainSlicing::NotMonotone;
255        }
256        if self.edges.len() + self.active.len() > MAX_EDGES {
257            return ChainSlicing::Unsliceable;
258        }
259        self.slices.clear();
260        let (mut first, mut second) = (0, 0);
261        let mut left = self.edges.first().map_or(0.0, |edge| edge.x0);
262        let mut previous_sliced = false;
263        while let (Some(&a), Some(&b)) = (self.edges.get(first), self.active.get(second)) {
264            let next = a.x1.min(b.x1);
265            let (top, bottom, cut) = ordered_pair(a, b, left, next);
266            let right = cut.unwrap_or(next);
267            let sliced = top.start != bottom.start || top.end != bottom.end;
268            if sliced {
269                if previous_sliced && let Some(previous) = self.slices.last_mut() {
270                    previous.open_right = false;
271                }
272                self.slices.push(Trapezoid {
273                    left,
274                    right,
275                    top: [top.start, top.end],
276                    bottom: [bottom.start, bottom.end],
277                    open_left: !previous_sliced,
278                    open_right: true,
279                });
280                if self.slices.len() > MAX_SLICES {
281                    return ChainSlicing::Unsliceable;
282                }
283            }
284            previous_sliced = sliced;
285            left = right;
286            if cut.is_none() {
287                first += usize::from(a.x1 <= left);
288                second += usize::from(b.x1 <= left);
289            }
290        }
291        ChainSlicing::Sliced
292    }
293
294    fn collect_edges<'a>(&mut self, contours: impl IntoIterator<Item = &'a [Point]>) -> Option<()> {
295        self.edges.clear();
296        for points in contours {
297            let closing = points.iter().skip(1).chain(points.first());
298            for (a, b) in points.iter().zip(closing) {
299                if !(a.x.is_finite() && a.y.is_finite() && b.x.is_finite() && b.y.is_finite()) {
300                    return None;
301                }
302                let edge = |from: &Point, to: &Point, winding| Edge {
303                    x0: from.x,
304                    y0: from.y,
305                    x1: to.x,
306                    y1: to.y,
307                    winding,
308                };
309                if a.x < b.x {
310                    self.edges.push(edge(a, b, 1));
311                } else if a.x > b.x {
312                    self.edges.push(edge(b, a, -1));
313                }
314            }
315            if self.edges.len() > MAX_EDGES {
316                return None;
317            }
318        }
319        Some(())
320    }
321
322    /// Orders the active edges from top to bottom across the slice from
323    /// `left` to `right`, and returns where the first two of them cross
324    /// inside it, which ends the slice early.
325    fn order_crossings(&mut self, left: f32, right: f32) -> Option<f32> {
326        self.crossings.clear();
327        self.crossings
328            .extend(self.active.iter().map(|&edge| Crossing {
329                edge,
330                start: edge.y_at(left),
331                end: edge.y_at(right),
332            }));
333        // The active edges keep the previous slice's order, which changes
334        // only where edges cross or enter, so the crossings come nearly in
335        // order and a pass of insertion settles them. Edges that meet at the
336        // left side within the tolerance then go in the order they leave it.
337        insertion_sort(&mut self.crossings, |upper, lower| {
338            upper
339                .start
340                .total_cmp(&lower.start)
341                .then_with(|| upper.end.total_cmp(&lower.end))
342                .is_gt()
343        });
344        insertion_sort(&mut self.crossings, |upper, lower| {
345            (lower.start - upper.start).abs() <= TOLERANCE && upper.end > lower.end
346        });
347        self.active.clear();
348        self.active
349            .extend(self.crossings.iter().map(|crossing| crossing.edge));
350        let cut = self
351            .crossings
352            .windows(2)
353            .filter_map(|pair| {
354                let gap_start = pair[1].start - pair[0].start;
355                let overlap_end = pair[0].end - pair[1].end;
356                (gap_start > TOLERANCE && overlap_end > TOLERANCE)
357                    .then(|| left + (right - left) * gap_start / (gap_start + overlap_end))
358            })
359            .filter(|&x| x > left && x < right)
360            .min_by(f32::total_cmp)?;
361        for crossing in &mut self.crossings {
362            crossing.end = crossing.edge.y_at(cut);
363        }
364        Some(cut)
365    }
366
367    /// Pushes the slices between `left` and `right`: the runs between the
368    /// ordered edges where the winding fills, each side open until
369    /// [`share_sides`] finds its neighbour.
370    fn push_spans(&mut self, left: f32, right: f32, fill_rule: PathFillRule) {
371        let mut winding = 0;
372        let mut top = None;
373        for crossing in &self.crossings {
374            let was_inside = fill_rule.contains(winding);
375            winding += crossing.edge.winding;
376            match (was_inside, fill_rule.contains(winding)) {
377                (false, true) => top = Some(crossing),
378                (true, false) => {
379                    let Some(top) = top.take() else {
380                        continue;
381                    };
382                    if top.start == crossing.start && top.end == crossing.end {
383                        continue;
384                    }
385                    self.slices.push(Trapezoid {
386                        left,
387                        right,
388                        top: [top.start, top.end],
389                        bottom: [crossing.start, crossing.end],
390                        open_left: true,
391                        open_right: true,
392                    });
393                }
394                _ => {}
395            }
396        }
397    }
398}
399
400/// What slicing a contour as one that each upright line crosses at most
401/// twice came to.
402enum ChainSlicing {
403    Sliced,
404    /// The contour is monotone, but the sweep would not slice it either.
405    Unsliceable,
406    NotMonotone,
407}
408
409/// The first of the leftmost vertices and the first of the rightmost ones.
410/// `None` when every vertex lies on one upright line.
411fn horizontal_extremes(points: &[Point]) -> Option<(usize, usize)> {
412    let mut leftmost = 0;
413    let mut rightmost = 0;
414    for (index, point) in points.iter().enumerate() {
415        if point.x < points[leftmost].x {
416            leftmost = index;
417        }
418        if point.x > points[rightmost].x {
419            rightmost = index;
420        }
421    }
422    (leftmost != rightmost).then_some((leftmost, rightmost))
423}
424
425/// The edges from vertex `from` to vertex `to`, stepping `step` through
426/// the contour, as edges from left to right with the winding the contour
427/// runs them in. False when the chain turns back left, or when an upright
428/// edge stands inside it rather than at its ends.
429fn chain_edges(
430    points: &[Point],
431    from: usize,
432    to: usize,
433    step: isize,
434    edges: &mut Vec<Edge>,
435) -> bool {
436    edges.clear();
437    let count = points.len();
438    let mut index = from;
439    let mut upright_after_edge = false;
440    while index != to {
441        let next = index.wrapping_add_signed(step).wrapping_add(count) % count;
442        let (a, b) = (points[index], points[next]);
443        if a.x > b.x {
444            return false;
445        }
446        if a.x < b.x {
447            if upright_after_edge {
448                return false;
449            }
450            let winding = if step > 0 { 1 } else { -1 };
451            edges.push(Edge {
452                x0: a.x,
453                y0: a.y,
454                x1: b.x,
455                y1: b.y,
456                winding,
457            });
458        } else if a.y != b.y && !edges.is_empty() {
459            upright_after_edge = true;
460        }
461        index = next;
462    }
463    true
464}
465
466/// The crossings of edges `a` and `b` across the slice from `left` to
467/// `right`, upper first, ordered as [`PathSlicer::order_crossings`] orders
468/// them, and where they cross inside the slice, which ends it early.
469fn ordered_pair(a: Edge, b: Edge, left: f32, right: f32) -> (Crossing, Crossing, Option<f32>) {
470    let crossing = |edge: Edge| Crossing {
471        edge,
472        start: edge.y_at(left),
473        end: edge.y_at(right),
474    };
475    let (mut upper, mut lower) = (crossing(a), crossing(b));
476    if upper
477        .start
478        .total_cmp(&lower.start)
479        .then_with(|| upper.end.total_cmp(&lower.end))
480        .is_gt()
481    {
482        std::mem::swap(&mut upper, &mut lower);
483    }
484    if (lower.start - upper.start).abs() <= TOLERANCE && upper.end > lower.end {
485        std::mem::swap(&mut upper, &mut lower);
486    }
487    let gap_start = lower.start - upper.start;
488    let overlap_end = upper.end - lower.end;
489    let cut = (gap_start > TOLERANCE && overlap_end > TOLERANCE)
490        .then(|| left + (right - left) * gap_start / (gap_start + overlap_end))
491        .filter(|&x| x > left && x < right);
492    if let Some(cut) = cut {
493        upper.end = upper.edge.y_at(cut);
494        lower.end = lower.edge.y_at(cut);
495    }
496    (upper, lower, cut)
497}
498
499/// Moves each item of `items` back past those before it that `misplaced`
500/// says belong after it.
501fn insertion_sort<T: Copy>(items: &mut [T], misplaced: impl Fn(&T, &T) -> bool) {
502    for index in 1..items.len() {
503        let mut at = index;
504        while at > 0 && misplaced(&items[at - 1], &items[at]) {
505            items.swap(at - 1, at);
506            at -= 1;
507        }
508    }
509}
510
511/// Marks the sides where `before`'s slices meet `after`'s as shared. `None`
512/// when a side meets only part of the other column's fill.
513fn share_sides(before: &mut [Trapezoid], after: &mut [Trapezoid]) -> Option<()> {
514    for slice in before.iter_mut() {
515        let side = [slice.top[1], slice.bottom[1]];
516        slice.open_right =
517            !side_shared(side, after.iter().map(|next| [next.top[0], next.bottom[0]]))?;
518    }
519    for slice in after.iter_mut() {
520        let side = [slice.top[0], slice.bottom[0]];
521        slice.open_left = !side_shared(
522            side,
523            before
524                .iter()
525                .map(|previous| [previous.top[1], previous.bottom[1]]),
526        )?;
527    }
528    Some(())
529}
530
531/// Whether `side` lies within the fill `others` make on the same line, the
532/// runs of them that touch counted as one: `Some(false)` when it meets none
533/// of them, `None` when it meets only part of one.
534fn side_shared(side: [f32; 2], others: impl Iterator<Item = [f32; 2]>) -> Option<bool> {
535    let mut runs = others.peekable();
536    while let Some([start, mut end]) = runs.next() {
537        while let Some(&[next_start, next_end]) = runs.peek() {
538            if next_start > end + TOLERANCE {
539                break;
540            }
541            end = end.max(next_end);
542            runs.next();
543        }
544        if side[0] >= start - TOLERANCE && side[1] <= end + TOLERANCE {
545            return Some(true);
546        }
547        if side[1].min(end) - side[0].max(start) > TOLERANCE {
548            return None;
549        }
550    }
551    Some(false)
552}
553
554impl PathFillRule {
555    /// Whether a point the path winds around `winding` times is filled.
556    pub(crate) fn contains(self, winding: i32) -> bool {
557        match self {
558            Self::NonZero => winding != 0,
559            Self::EvenOdd => winding % 2 != 0,
560        }
561    }
562}