1use crate::StableHash;
4use alpha2_model::FillRule;
5use kurbo::{Affine, BezPath};
6use std::sync::Arc;
7
8#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)]
10pub struct IRect {
11 pub x0: i32,
13 pub y0: i32,
15 pub x1: i32,
17 pub y1: i32,
19}
20
21impl IRect {
22 #[must_use]
24 pub const fn new(x0: i32, y0: i32, x1: i32, y1: i32) -> Self {
25 Self { x0, y0, x1, y1 }
26 }
27
28 #[must_use]
30 pub fn width(self) -> u32 {
31 u32::try_from(self.x1.saturating_sub(self.x0).max(0)).unwrap_or(0)
32 }
33
34 #[must_use]
36 pub fn height(self) -> u32 {
37 u32::try_from(self.y1.saturating_sub(self.y0).max(0)).unwrap_or(0)
38 }
39
40 #[must_use]
42 pub fn is_empty(self) -> bool {
43 self.x1 <= self.x0 || self.y1 <= self.y0
44 }
45
46 #[must_use]
48 pub fn intersect(self, other: Self) -> Self {
49 Self {
50 x0: self.x0.max(other.x0),
51 y0: self.y0.max(other.y0),
52 x1: self.x1.min(other.x1),
53 y1: self.y1.min(other.y1),
54 }
55 }
56
57 #[must_use]
59 pub fn union(self, other: Self) -> Self {
60 if self.is_empty() {
61 return other;
62 }
63 if other.is_empty() {
64 return self;
65 }
66 Self {
67 x0: self.x0.min(other.x0),
68 y0: self.y0.min(other.y0),
69 x1: self.x1.max(other.x1),
70 y1: self.y1.max(other.y1),
71 }
72 }
73
74 #[must_use]
76 pub fn covering(path: &BezPath, canvas: Self) -> Self {
77 let bounds = kurbo::Shape::bounding_box(path);
78 if !(bounds.x0.is_finite()
79 && bounds.y0.is_finite()
80 && bounds.x1.is_finite()
81 && bounds.y1.is_finite())
82 {
83 return Self::default();
84 }
85 #[allow(clippy::cast_possible_truncation)]
87 let edge =
88 |value: f64, low: i32, high: i32| value.clamp(f64::from(low), f64::from(high)) as i32;
89 Self {
90 x0: edge((bounds.x0 - 1.0).floor(), canvas.x0, canvas.x1),
91 y0: edge((bounds.y0 - 1.0).floor(), canvas.y0, canvas.y1),
92 x1: edge((bounds.x1 + 1.0).ceil(), canvas.x0, canvas.x1),
93 y1: edge((bounds.y1 + 1.0).ceil(), canvas.y0, canvas.y1),
94 }
95 }
96}
97
98#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
100pub enum PaintKind {
101 Solid,
103 Linear,
105 Radial,
107 Conic,
109}
110
111#[derive(Copy, Clone, Debug, PartialEq)]
113pub struct SceneStop {
114 pub offset: f32,
116 pub color: [f32; 4],
118}
119
120#[derive(Clone, Debug, PartialEq)]
123pub struct ScenePaint {
124 pub kind: PaintKind,
126 pub a: [f32; 2],
128 pub b: [f32; 2],
131 pub spread: u32,
133 pub stops: Vec<SceneStop>,
135 pub inverse: Affine,
137 pub space: [f32; 2],
140}
141
142#[derive(Clone, Debug, PartialEq)]
144pub enum VectorOp {
145 PushLayer {
148 opacity: f32,
150 bounds: IRect,
152 },
153 PopLayer,
155 Fill {
157 path: Arc<BezPath>,
159 rule: FillRule,
161 paint: ScenePaint,
163 opacity: f32,
165 bounds: IRect,
167 },
168}
169
170#[derive(Clone, Debug, PartialEq)]
172pub struct VectorScene {
173 pub ops: Vec<VectorOp>,
175 pub bounds: IRect,
177 pub width: u32,
179 pub height: u32,
181 pub key: u128,
183}
184
185impl VectorScene {
186 #[must_use]
188 pub fn new(ops: Vec<VectorOp>, width: u32, height: u32) -> Self {
189 let mut hash = StableHash::new();
190 hash.bytes(b"vector-scene");
191 hash.u32(width);
192 hash.u32(height);
193 let mut bounds = IRect::default();
194 for op in &ops {
195 match op {
196 VectorOp::PushLayer {
197 opacity,
198 bounds: layer,
199 } => {
200 hash.u32(1);
201 hash.f32(*opacity);
202 hash_rect(&mut hash, *layer);
203 }
204 VectorOp::PopLayer => hash.u32(2),
205 VectorOp::Fill {
206 path,
207 rule,
208 paint,
209 opacity,
210 bounds: covered,
211 } => {
212 hash.u32(3);
213 hash.path(path);
214 hash.u32(match rule {
215 FillRule::Nonzero => 0,
216 FillRule::Evenodd => 1,
217 });
218 hash_paint(&mut hash, paint);
219 hash.f32(*opacity);
220 hash_rect(&mut hash, *covered);
221 bounds = bounds.union(*covered);
222 }
223 }
224 }
225 Self {
226 ops,
227 bounds,
228 width,
229 height,
230 key: hash.finish(),
231 }
232 }
233
234 #[must_use]
236 pub fn fill_count(&self) -> usize {
237 self.ops
238 .iter()
239 .filter(|op| matches!(op, VectorOp::Fill { .. }))
240 .count()
241 }
242}
243
244fn hash_rect(hash: &mut StableHash, rect: IRect) {
245 for edge in [rect.x0, rect.y0, rect.x1, rect.y1] {
246 hash.u32(edge.cast_unsigned());
247 }
248}
249
250fn hash_paint(hash: &mut StableHash, paint: &ScenePaint) {
251 hash.u32(paint.kind as u32);
252 for value in paint.a.iter().chain(&paint.b) {
253 hash.f32(*value);
254 }
255 hash.u32(paint.spread);
256 hash.u64(paint.stops.len() as u64);
257 for stop in &paint.stops {
258 hash.f32(stop.offset);
259 for channel in stop.color {
260 hash.f32(channel);
261 }
262 }
263 for coefficient in paint.inverse.as_coeffs() {
264 hash.f64(coefficient);
265 }
266 hash.f32(paint.space[0]);
267 hash.f32(paint.space[1]);
268}