vello_common/
strip_generator.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Abstraction for generating strips from paths.
5
6use crate::fearless_simd::Level;
7use crate::flatten::{FlattenCtx, Line};
8use crate::kurbo::{Affine, PathEl, Stroke};
9use crate::peniko::Fill;
10use crate::strip::Strip;
11use crate::tile::Tiles;
12use crate::{flatten, strip};
13use alloc::vec::Vec;
14use peniko::kurbo::StrokeCtx;
15
16/// A storage for storing strip-related data.
17#[derive(Debug, Default)]
18pub struct StripStorage {
19    /// The strips in the storage.
20    pub strips: Vec<Strip>,
21    /// The alphas in the storage.
22    pub alphas: Vec<u8>,
23    generation_mode: GenerationMode,
24}
25
26/// The generation mode of the strip storage.
27#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
28pub enum GenerationMode {
29    #[default]
30    /// Clear strips before generating the new ones.
31    Replace,
32    /// Don't clear strips, append to the existing buffer.
33    Append,
34}
35
36impl StripStorage {
37    /// Reset the storage.
38    pub fn clear(&mut self) {
39        self.strips.clear();
40        self.alphas.clear();
41    }
42
43    /// Set the generation mode of the storage.
44    pub fn set_generation_mode(&mut self, mode: GenerationMode) {
45        self.generation_mode = mode;
46    }
47
48    /// Whether the strip storage is empty.
49    pub fn is_empty(&self) -> bool {
50        self.strips.is_empty() && self.alphas.is_empty()
51    }
52}
53
54/// An object for easily generating strips for a filled/stroked path.
55#[derive(Debug)]
56pub struct StripGenerator {
57    level: Level,
58    line_buf: Vec<Line>,
59    flatten_ctx: FlattenCtx,
60    stroke_ctx: StrokeCtx,
61    tiles: Tiles,
62    width: u16,
63    height: u16,
64}
65
66impl StripGenerator {
67    /// Create a new strip generator.
68    pub fn new(width: u16, height: u16, level: Level) -> Self {
69        Self {
70            level,
71            line_buf: Vec::new(),
72            tiles: Tiles::new(level),
73            flatten_ctx: FlattenCtx::default(),
74            stroke_ctx: StrokeCtx::default(),
75            width,
76            height,
77        }
78    }
79
80    /// Generate the strips for a filled path.
81    pub fn generate_filled_path(
82        &mut self,
83        path: impl IntoIterator<Item = PathEl>,
84        fill_rule: Fill,
85        transform: Affine,
86        aliasing_threshold: Option<u8>,
87        strip_storage: &mut StripStorage,
88    ) {
89        flatten::fill(
90            self.level,
91            path,
92            transform,
93            &mut self.line_buf,
94            &mut self.flatten_ctx,
95        );
96        self.make_strips(strip_storage, fill_rule, aliasing_threshold);
97    }
98
99    /// Generate the strips for a stroked path.
100    pub fn generate_stroked_path(
101        &mut self,
102        path: impl IntoIterator<Item = PathEl>,
103        stroke: &Stroke,
104        transform: Affine,
105        aliasing_threshold: Option<u8>,
106        strip_storage: &mut StripStorage,
107    ) {
108        flatten::stroke(
109            self.level,
110            path,
111            stroke,
112            transform,
113            &mut self.line_buf,
114            &mut self.flatten_ctx,
115            &mut self.stroke_ctx,
116        );
117        self.make_strips(strip_storage, Fill::NonZero, aliasing_threshold);
118    }
119
120    /// Reset the strip generator.
121    pub fn reset(&mut self) {
122        self.line_buf.clear();
123        self.tiles.reset();
124    }
125
126    fn make_strips(
127        &mut self,
128        strip_storage: &mut StripStorage,
129        fill_rule: Fill,
130        aliasing_threshold: Option<u8>,
131    ) {
132        self.tiles
133            .make_tiles(&self.line_buf, self.width, self.height);
134        self.tiles.sort_tiles();
135
136        if strip_storage.generation_mode == GenerationMode::Replace {
137            strip_storage.strips.clear();
138        }
139
140        strip::render(
141            self.level,
142            &self.tiles,
143            &mut strip_storage.strips,
144            &mut strip_storage.alphas,
145            fill_rule,
146            aliasing_threshold,
147            &self.line_buf,
148        );
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use crate::fearless_simd::Level;
155    use crate::kurbo::{Affine, Rect, Shape};
156    use crate::peniko::Fill;
157    use crate::strip_generator::{StripGenerator, StripStorage};
158
159    #[test]
160    fn reset() {
161        let mut generator = StripGenerator::new(100, 100, Level::fallback());
162        let mut storage = StripStorage::default();
163        let rect = Rect::new(0.0, 0.0, 100.0, 100.0);
164
165        generator.generate_filled_path(
166            rect.to_path(0.1),
167            Fill::NonZero,
168            Affine::IDENTITY,
169            None,
170            &mut storage,
171        );
172
173        assert!(!generator.line_buf.is_empty());
174        assert!(!storage.is_empty());
175
176        generator.reset();
177        storage.clear();
178
179        assert!(generator.line_buf.is_empty());
180        assert!(storage.is_empty());
181    }
182}