Skip to main content

alpha2_geometry/
scene.rs

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