1use crate::StableHash;
5use alpha2_model::FillRule;
6use kurbo::{Affine, BezPath};
7use std::sync::Arc;
8
9#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)]
11pub struct IRect {
12 pub x0: i32,
14 pub y0: i32,
16 pub x1: i32,
18 pub y1: i32,
20}
21
22impl IRect {
23 #[must_use]
25 pub const fn new(x0: i32, y0: i32, x1: i32, y1: i32) -> Self {
26 Self { x0, y0, x1, y1 }
27 }
28
29 #[must_use]
31 pub fn width(self) -> u32 {
32 u32::try_from(self.x1.saturating_sub(self.x0).max(0)).unwrap_or(0)
33 }
34
35 #[must_use]
37 pub fn height(self) -> u32 {
38 u32::try_from(self.y1.saturating_sub(self.y0).max(0)).unwrap_or(0)
39 }
40
41 #[must_use]
43 pub fn is_empty(self) -> bool {
44 self.x1 <= self.x0 || self.y1 <= self.y0
45 }
46
47 #[must_use]
49 pub fn intersect(self, other: Self) -> Self {
50 Self {
51 x0: self.x0.max(other.x0),
52 y0: self.y0.max(other.y0),
53 x1: self.x1.min(other.x1),
54 y1: self.y1.min(other.y1),
55 }
56 }
57
58 #[must_use]
60 pub fn union(self, other: Self) -> Self {
61 if self.is_empty() {
62 return other;
63 }
64 if other.is_empty() {
65 return self;
66 }
67 Self {
68 x0: self.x0.min(other.x0),
69 y0: self.y0.min(other.y0),
70 x1: self.x1.max(other.x1),
71 y1: self.y1.max(other.y1),
72 }
73 }
74
75 #[must_use]
78 pub fn covering(path: &BezPath, canvas: Self) -> Self {
79 let bounds = kurbo::Shape::bounding_box(path);
80 if !(bounds.x0.is_finite()
81 && bounds.y0.is_finite()
82 && bounds.x1.is_finite()
83 && bounds.y1.is_finite())
84 {
85 return Self::default();
86 }
87 #[allow(clippy::cast_possible_truncation)]
89 let edge =
90 |value: f64, low: i32, high: i32| value.clamp(f64::from(low), f64::from(high)) as i32;
91 Self {
92 x0: edge((bounds.x0 - 1.0).floor(), canvas.x0, canvas.x1),
93 y0: edge((bounds.y0 - 1.0).floor(), canvas.y0, canvas.y1),
94 x1: edge((bounds.x1 + 1.0).ceil(), canvas.x0, canvas.x1),
95 y1: edge((bounds.y1 + 1.0).ceil(), canvas.y0, canvas.y1),
96 }
97 }
98}
99
100#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
102pub enum PaintKind {
103 Solid,
105 Linear,
107 Radial,
109 Conic,
111}
112
113#[derive(Copy, Clone, Debug, PartialEq)]
115pub struct SceneStop {
116 pub offset: f32,
118 pub color: [f32; 4],
120}
121
122#[derive(Clone, Debug, PartialEq)]
126pub struct ScenePaint {
127 pub kind: PaintKind,
129 pub a: [f32; 2],
131 pub b: [f32; 2],
134 pub spread: u32,
136 pub stops: Vec<SceneStop>,
138 pub inverse: Affine,
141 pub space: [f32; 2],
146}
147
148#[derive(Clone, Debug, PartialEq)]
150pub enum VectorOp {
151 PushLayer {
154 opacity: f32,
156 bounds: IRect,
158 },
159 PopLayer,
161 Fill {
164 path: Arc<BezPath>,
166 rule: FillRule,
168 paint: ScenePaint,
170 opacity: f32,
172 bounds: IRect,
174 },
175}
176
177#[derive(Clone, Debug, PartialEq)]
179pub struct VectorScene {
180 pub ops: Vec<VectorOp>,
182 pub bounds: IRect,
184 pub width: u32,
186 pub height: u32,
188 pub key: u128,
191}
192
193impl VectorScene {
194 #[must_use]
197 pub fn new(ops: Vec<VectorOp>, width: u32, height: u32) -> Self {
198 let mut hash = StableHash::new();
199 hash.bytes(b"vector-scene");
200 hash.u32(width);
201 hash.u32(height);
202 let mut bounds = IRect::default();
203 for op in &ops {
204 match op {
205 VectorOp::PushLayer {
206 opacity,
207 bounds: layer,
208 } => {
209 hash.u32(1);
210 hash.f32(*opacity);
211 hash_rect(&mut hash, *layer);
212 }
213 VectorOp::PopLayer => hash.u32(2),
214 VectorOp::Fill {
215 path,
216 rule,
217 paint,
218 opacity,
219 bounds: covered,
220 } => {
221 hash.u32(3);
222 hash.path(path);
223 hash.u32(match rule {
224 FillRule::Nonzero => 0,
225 FillRule::Evenodd => 1,
226 });
227 hash_paint(&mut hash, paint);
228 hash.f32(*opacity);
229 hash_rect(&mut hash, *covered);
230 bounds = bounds.union(*covered);
231 }
232 }
233 }
234 Self {
235 ops,
236 bounds,
237 width,
238 height,
239 key: hash.finish(),
240 }
241 }
242
243 #[must_use]
245 pub fn fill_count(&self) -> usize {
246 self.ops
247 .iter()
248 .filter(|op| matches!(op, VectorOp::Fill { .. }))
249 .count()
250 }
251}
252
253fn hash_rect(hash: &mut StableHash, rect: IRect) {
254 for edge in [rect.x0, rect.y0, rect.x1, rect.y1] {
255 hash.u32(edge.cast_unsigned());
256 }
257}
258
259fn hash_paint(hash: &mut StableHash, paint: &ScenePaint) {
260 hash.u32(paint.kind as u32);
261 for value in paint.a.iter().chain(&paint.b) {
262 hash.f32(*value);
263 }
264 hash.u32(paint.spread);
265 hash.u64(paint.stops.len() as u64);
266 for stop in &paint.stops {
267 hash.f32(stop.offset);
268 for channel in stop.color {
269 hash.f32(channel);
270 }
271 }
272 for coefficient in paint.inverse.as_coeffs() {
273 hash.f64(coefficient);
274 }
275 hash.f32(paint.space[0]);
276 hash.f32(paint.space[1]);
277}