Skip to main content

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