Skip to main content

vello_common/
geometry.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Geometry utilities.
5
6use crate::kurbo::Rect;
7use bytemuck::{Pod, Zeroable};
8use core::num::TryFromIntError;
9use core::ops::Add;
10
11/// A size represented by two 16-bit unsigned integers.
12#[repr(C)]
13#[derive(Copy, Clone, Debug, Pod, Zeroable, PartialEq, Eq)]
14pub struct SizeU16(pub [u16; 2]);
15
16impl SizeU16 {
17    /// A zero size.
18    pub const ZERO: Self = Self::new(0);
19
20    /// Create a new square size.
21    pub const fn new(size: u16) -> Self {
22        Self([size; 2])
23    }
24
25    /// Create a new size from its width and height.
26    pub const fn from_wh(width: u16, height: u16) -> Self {
27        Self([width, height])
28    }
29
30    /// The width of this size.
31    pub const fn width(self) -> u16 {
32        self.0[0]
33    }
34
35    /// The height of this size.
36    pub const fn height(self) -> u16 {
37        self.0[1]
38    }
39
40    /// Return the maximum of the two sizes.
41    pub fn max(self, other: Self) -> Self {
42        Self::from_wh(
43            self.width().max(other.width()),
44            self.height().max(other.height()),
45        )
46    }
47
48    /// Return the minimum of the two sizes.
49    pub fn min(self, other: Self) -> Self {
50        Self::from_wh(
51            self.width().min(other.width()),
52            self.height().min(other.height()),
53        )
54    }
55
56    /// Clamp both dimensions to the given range.
57    pub fn clamp(self, min: u16, max: u16) -> Self {
58        Self::from_wh(self.width().clamp(min, max), self.height().clamp(min, max))
59    }
60}
61
62impl From<[u16; 2]> for SizeU16 {
63    fn from(value: [u16; 2]) -> Self {
64        Self(value)
65    }
66}
67
68impl Add for SizeU16 {
69    type Output = Self;
70
71    fn add(self, rhs: Self) -> Self::Output {
72        // Shouldn't overflow for our use cases.
73        Self::from_wh(
74            self.width().checked_add(rhs.width()).unwrap(),
75            self.height().checked_add(rhs.height()).unwrap(),
76        )
77    }
78}
79
80impl Add<u16> for SizeU16 {
81    type Output = Self;
82
83    fn add(self, rhs: u16) -> Self::Output {
84        self + Self::new(rhs)
85    }
86}
87
88/// Padding for the four sides of a region.
89#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
90pub struct PaddingU16 {
91    /// The left padding.
92    pub left: u16,
93    /// The top padding.
94    pub top: u16,
95    /// The right padding.
96    pub right: u16,
97    /// The bottom padding.
98    pub bottom: u16,
99}
100
101impl PaddingU16 {
102    /// Padding with all sides set to zero.
103    pub const ZERO: Self = Self::new(0, 0, 0, 0);
104
105    /// Create padding from its left, top, right, and bottom amounts.
106    pub const fn new(left: u16, top: u16, right: u16, bottom: u16) -> Self {
107        Self {
108            left,
109            top,
110            right,
111            bottom,
112        }
113    }
114}
115
116/// An axis-aligned rectangle with `u16` coordinates, stored as two corners `(x0, y0)` and
117/// `(x1, y1)`.
118///
119/// `(x0, y0)` is the top-left (minimum) corner and `(x1, y1)` is the bottom-right (maximum) corner.
120/// The rectangle is considered to be empty when `x0 >= x1` or `y0 >= y1`.
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
122pub struct RectU16 {
123    /// The minimum x coordinate (left edge).
124    pub x0: u16,
125    /// The minimum y coordinate (top edge).
126    pub y0: u16,
127    /// The maximum x coordinate (right edge, exclusive).
128    pub x1: u16,
129    /// The maximum y coordinate (bottom edge, exclusive).
130    pub y1: u16,
131}
132
133impl RectU16 {
134    /// A rectangle with all coordinates set to zero.
135    pub const ZERO: Self = Self {
136        x0: 0,
137        y0: 0,
138        x1: 0,
139        y1: 0,
140    };
141
142    /// An empty, maximally inverted rectangle, useful as a starting value for incremental union
143    /// operations.
144    ///
145    /// Has `(x0, y0) = (u16::MAX, u16::MAX)` and `(x1, y1) = (0, 0)`.
146    pub const INVERTED: Self = Self {
147        x0: u16::MAX,
148        y0: u16::MAX,
149        x1: 0,
150        y1: 0,
151    };
152
153    /// Create a new rectangle from its corner coordinates.
154    #[inline(always)]
155    pub const fn new(x0: u16, y0: u16, x1: u16, y1: u16) -> Self {
156        Self { x0, y0, x1, y1 }
157    }
158
159    /// The width of the rectangle (`x1 - x0`), saturating at zero.
160    #[inline(always)]
161    pub const fn width(self) -> u16 {
162        self.x1.saturating_sub(self.x0)
163    }
164
165    /// The height of the rectangle (`y1 - y0`), saturating at zero.
166    #[inline(always)]
167    pub const fn height(self) -> u16 {
168        self.y1.saturating_sub(self.y0)
169    }
170
171    /// Returns `true` if the rectangle has zero area (`x0 >= x1` or `y0 >= y1`).
172    #[inline(always)]
173    pub const fn is_empty(self) -> bool {
174        self.x0 >= self.x1 || self.y0 >= self.y1
175    }
176
177    /// Check if a point `(x, y)` is contained within this rectangle.
178    ///
179    /// Returns `true` if `x0 <= x < x1` and `y0 <= y < y1`.
180    #[inline(always)]
181    pub const fn contains(self, x: u16, y: u16) -> bool {
182        (x >= self.x0) & (x < self.x1) & (y >= self.y0) & (y < self.y1)
183    }
184
185    /// Compute the intersection of two rectangles.
186    ///
187    /// The result may have zero area if the rectangles do not overlap, but is never inverted.
188    #[inline(always)]
189    pub const fn intersect(self, other: Self) -> Self {
190        let x0 = const_max(self.x0, other.x0);
191        let y0 = const_max(self.y0, other.y0);
192        let x1 = const_min(self.x1, other.x1);
193        let y1 = const_min(self.y1, other.y1);
194
195        Self::new(x0, y0, const_max(x1, x0), const_max(y1, y0))
196    }
197
198    /// Expand this rectangle by the given left, top, right, and bottom padding.
199    #[inline(always)]
200    pub const fn expand(self, padding: PaddingU16) -> Self {
201        Self {
202            x0: self.x0.saturating_sub(padding.left),
203            y0: self.y0.saturating_sub(padding.top),
204            x1: self.x1.saturating_add(padding.right),
205            y1: self.y1.saturating_add(padding.bottom),
206        }
207    }
208
209    /// Return this rectangle relative to `origin`, clamping negative coordinates to zero.
210    #[inline(always)]
211    pub fn relative_to_origin(self, origin: (u16, u16)) -> Self {
212        self.shift((-(origin.0 as i32), -(origin.1 as i32)))
213    }
214
215    /// Return a shifted version of the rectangle, clamping negative coordinates to zero.
216    #[inline]
217    pub fn shift(self, shift: (i32, i32)) -> Self {
218        Self {
219            x0: (self.x0 as i32)
220                .saturating_add(shift.0)
221                .clamp(0, u16::MAX as i32) as u16,
222            y0: (self.y0 as i32)
223                .saturating_add(shift.1)
224                .clamp(0, u16::MAX as i32) as u16,
225            x1: (self.x1 as i32)
226                .saturating_add(shift.0)
227                .clamp(0, u16::MAX as i32) as u16,
228            y1: (self.y1 as i32)
229                .saturating_add(shift.1)
230                .clamp(0, u16::MAX as i32) as u16,
231        }
232    }
233
234    /// Expand this rectangle to also cover `other` (union in place).
235    ///
236    /// The union of `self` with a [`Self::INVERTED`] returns `self`.
237    #[inline(always)]
238    pub const fn union(&mut self, other: Self) {
239        self.x0 = const_min(self.x0, other.x0);
240        self.y0 = const_min(self.y0, other.y0);
241        self.x1 = const_max(self.x1, other.x1);
242        self.y1 = const_max(self.y1, other.y1);
243    }
244
245    /// Return the rect as a [`Rect`].
246    pub fn as_rect(self) -> Rect {
247        Rect::new(
248            self.x0 as f64,
249            self.y0 as f64,
250            self.x1 as f64,
251            self.y1 as f64,
252        )
253    }
254}
255
256impl From<RectU16> for SizeU16 {
257    fn from(rect: RectU16) -> Self {
258        Self::from_wh(rect.width(), rect.height())
259    }
260}
261
262// TODO: Remove these types once we've completely moved to u16 everywhere in Vello Hybrid.
263
264/// An offset represented by two 32-bit unsigned integers.
265#[repr(C)]
266#[derive(Copy, Clone, Debug, Pod, Zeroable, PartialEq, Eq)]
267pub struct OffsetU32(pub [u32; 2]);
268
269impl OffsetU32 {
270    /// A zero offset.
271    pub const ZERO: Self = Self::new(0);
272
273    /// Create a new offset with equal x and y coordinates.
274    pub const fn new(offset: u32) -> Self {
275        Self([offset; 2])
276    }
277
278    /// Create a new offset from its x and y coordinates.
279    pub const fn from_xy(x: u32, y: u32) -> Self {
280        Self([x, y])
281    }
282
283    /// The x coordinate of this offset.
284    pub const fn x(self) -> u32 {
285        self.0[0]
286    }
287
288    /// The y coordinate of this offset.
289    pub const fn y(self) -> u32 {
290        self.0[1]
291    }
292}
293
294impl From<[u32; 2]> for OffsetU32 {
295    fn from(value: [u32; 2]) -> Self {
296        Self(value)
297    }
298}
299
300/// A size represented by two 32-bit unsigned integers.
301#[repr(C)]
302#[derive(Copy, Clone, Debug, Pod, Zeroable, PartialEq, Eq)]
303pub struct SizeU32(pub [u32; 2]);
304
305impl SizeU32 {
306    /// A zero size.
307    pub const ZERO: Self = Self::new(0);
308
309    /// Create a new square size.
310    pub const fn new(size: u32) -> Self {
311        Self([size; 2])
312    }
313
314    /// Create a new size from its width and height.
315    pub const fn from_wh(width: u32, height: u32) -> Self {
316        Self([width, height])
317    }
318
319    /// The width of this size.
320    pub const fn width(self) -> u32 {
321        self.0[0]
322    }
323
324    /// The height of this size.
325    pub const fn height(self) -> u32 {
326        self.0[1]
327    }
328
329    /// Return the maximum of the two sizes.
330    pub fn max(self, other: Self) -> Self {
331        Self::from_wh(
332            self.width().max(other.width()),
333            self.height().max(other.height()),
334        )
335    }
336
337    /// Return the minimum of the two sizes.
338    pub fn min(self, other: Self) -> Self {
339        Self::from_wh(
340            self.width().min(other.width()),
341            self.height().min(other.height()),
342        )
343    }
344
345    /// Clamp both dimensions to the given range.
346    pub fn clamp(self, min: u32, max: u32) -> Self {
347        Self::from_wh(self.width().clamp(min, max), self.height().clamp(min, max))
348    }
349}
350
351impl From<[u32; 2]> for SizeU32 {
352    fn from(value: [u32; 2]) -> Self {
353        Self(value)
354    }
355}
356
357impl From<(u32, u32)> for SizeU32 {
358    fn from((width, height): (u32, u32)) -> Self {
359        Self::from_wh(width, height)
360    }
361}
362
363impl From<SizeU32> for (u32, u32) {
364    fn from(size: SizeU32) -> Self {
365        (size.width(), size.height())
366    }
367}
368
369impl From<SizeU16> for SizeU32 {
370    fn from(size: SizeU16) -> Self {
371        Self::from_wh(u32::from(size.width()), u32::from(size.height()))
372    }
373}
374
375impl TryFrom<SizeU32> for SizeU16 {
376    type Error = TryFromIntError;
377
378    fn try_from(size: SizeU32) -> Result<Self, Self::Error> {
379        Ok(Self::from_wh(
380            u16::try_from(size.width())?,
381            u16::try_from(size.height())?,
382        ))
383    }
384}
385
386impl Add for SizeU32 {
387    type Output = Self;
388
389    fn add(self, rhs: Self) -> Self::Output {
390        Self::from_wh(self.width() + rhs.width(), self.height() + rhs.height())
391    }
392}
393
394impl Add<u32> for SizeU32 {
395    type Output = Self;
396
397    fn add(self, rhs: u32) -> Self::Output {
398        self + Self::new(rhs)
399    }
400}
401
402/// An axis-aligned rectangle with `u32` coordinates.
403#[repr(C)]
404#[derive(Copy, Clone, Debug, Pod, Zeroable, PartialEq, Eq)]
405pub struct RectU32 {
406    /// The minimum x coordinate.
407    pub x0: u32,
408    /// The minimum y coordinate.
409    pub y0: u32,
410    /// The exclusive maximum x coordinate.
411    pub x1: u32,
412    /// The exclusive maximum y coordinate.
413    pub y1: u32,
414}
415
416impl RectU32 {
417    /// Create a new rectangle from its corner coordinates.
418    pub const fn new(x0: u32, y0: u32, x1: u32, y1: u32) -> Self {
419        Self { x0, y0, x1, y1 }
420    }
421
422    /// The width of this rectangle.
423    pub const fn width(self) -> u32 {
424        self.x1.saturating_sub(self.x0)
425    }
426
427    /// The height of this rectangle.
428    pub const fn height(self) -> u32 {
429        self.y1.saturating_sub(self.y0)
430    }
431}
432
433#[inline(always)]
434const fn const_max(a: u16, b: u16) -> u16 {
435    if a > b { a } else { b }
436}
437
438#[inline(always)]
439const fn const_min(a: u16, b: u16) -> u16 {
440    if a < b { a } else { b }
441}
442
443#[cfg(test)]
444mod tests {
445    use super::RectU16;
446
447    #[test]
448    fn rect_u16_relative_to_origin() {
449        let rect = RectU16::new(10, 20, 30, 40);
450
451        assert_eq!(rect.relative_to_origin((5, 12)), RectU16::new(5, 8, 25, 28));
452    }
453
454    #[test]
455    fn rect_u16_relative_to_origin_clamps_to_zero() {
456        let rect = RectU16::new(10, 20, 30, 40);
457
458        assert_eq!(rect.relative_to_origin((20, 35)), RectU16::new(0, 0, 10, 5));
459    }
460
461    #[test]
462    fn disjoint_intersection_is_empty_but_not_inverted() {
463        let intersection = RectU16::new(0, 0, 4, 4).intersect(RectU16::new(8, 1, 12, 3));
464
465        assert_eq!(intersection, RectU16::new(8, 1, 8, 3));
466        assert!(intersection.is_empty());
467        assert!(intersection.x0 <= intersection.x1);
468        assert!(intersection.y0 <= intersection.y1);
469    }
470}