Skip to main content

alpha2_geometry/
scene.rs

1//! The vector scene: a content tree's fills at one time and size, in device pixels, back first.
2
3use crate::StableHash;
4use alpha2_model::FillRule;
5use kurbo::{Affine, BezPath};
6use std::sync::Arc;
7
8/// A half-open rectangle of device pixels.
9#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)]
10pub struct IRect {
11    /// Left edge.
12    pub x0: i32,
13    /// Top edge.
14    pub y0: i32,
15    /// Right edge, exclusive.
16    pub x1: i32,
17    /// Bottom edge, exclusive.
18    pub y1: i32,
19}
20
21impl IRect {
22    /// A rectangle from its edges.
23    #[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    /// Width, zero when empty.
29    #[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    /// Height, zero when empty.
35    #[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    /// Whether it holds no pixel.
41    #[must_use]
42    pub fn is_empty(self) -> bool {
43        self.x1 <= self.x0 || self.y1 <= self.y0
44    }
45
46    /// The pixels in both.
47    #[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    /// The smallest rectangle holding both; an empty one adds nothing.
58    #[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    /// The bounds of `path`'s control points plus one antialiasing pixel, clipped to `canvas`.
75    #[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        // Clamped to the canvas first, so the conversion cannot overflow.
86        #[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/// What kind of paint covers a fill.
99#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
100pub enum PaintKind {
101    /// One color.
102    Solid,
103    /// Along the line from `a` to `b`.
104    Linear,
105    /// Around `a`, offset 1 at radius `b[0]` (a fraction of the shorter side).
106    Radial,
107    /// Around `a`, from angle `b[0]` radians clockwise from up.
108    Conic,
109}
110
111/// One stop of a scene paint: its offset and straight, sRGB-encoded color.
112#[derive(Copy, Clone, Debug, PartialEq)]
113pub struct SceneStop {
114    /// Position along the gradient, in `0..=1`.
115    pub offset: f32,
116    /// Straight sRGB color and alpha.
117    pub color: [f32; 4],
118}
119
120/// The paint of a fill, with points normalized to the paint's space and a transform from device
121/// pixels into that space.
122#[derive(Clone, Debug, PartialEq)]
123pub struct ScenePaint {
124    /// What kind of paint.
125    pub kind: PaintKind,
126    /// First point: a linear gradient's start, another gradient's centre.
127    pub a: [f32; 2],
128    /// Second point: a linear gradient's end, or in `b[0]` a radial radius or a conic start angle
129    /// in radians.
130    pub b: [f32; 2],
131    /// 0 pad, 1 repeat, 2 reflect.
132    pub spread: u32,
133    /// One stop for a solid color, two to eight for a gradient.
134    pub stops: Vec<SceneStop>,
135    /// Device pixels to paint-space pixels (the inverse of the enclosing group transforms).
136    pub inverse: Affine,
137    /// Paint-space size in pixels. Normalized points are fractions of it, and a radial radius is a
138    /// fraction of its shorter side.
139    pub space: [f32; 2],
140}
141
142/// One drawing operation of a [`VectorScene`].
143#[derive(Clone, Debug, PartialEq)]
144pub enum VectorOp {
145    /// Draws everything up to the matching [`VectorOp::PopLayer`] into a layer composited at
146    /// `opacity`.
147    PushLayer {
148        /// The layer's opacity, in `0..1`.
149        opacity: f32,
150        /// The device pixels its contents reach.
151        bounds: IRect,
152    },
153    /// Ends the innermost layer.
154    PopLayer,
155    /// Fills `path` with `paint`. Strokes are fills of their outline with the nonzero rule.
156    Fill {
157        /// The outline, in device pixels.
158        path: Arc<BezPath>,
159        /// Which regions are inside.
160        rule: FillRule,
161        /// What covers it.
162        paint: ScenePaint,
163        /// Opacity, in `0..=1`.
164        opacity: f32,
165        /// The device pixels it can cover.
166        bounds: IRect,
167    },
168}
169
170/// Everything a content tree draws at one time and size.
171#[derive(Clone, Debug, PartialEq)]
172pub struct VectorScene {
173    /// The operations, back first.
174    pub ops: Vec<VectorOp>,
175    /// The device pixels any of them can cover.
176    pub bounds: IRect,
177    /// Width of the canvas it is drawn on.
178    pub width: u32,
179    /// Height of the canvas it is drawn on.
180    pub height: u32,
181    /// A key over all of the above; equal keys draw equal pictures.
182    pub key: u128,
183}
184
185impl VectorScene {
186    /// A scene of `ops` on a `width` by `height` canvas, with bounds and key computed.
187    #[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    /// The fills it draws.
235    #[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}