Skip to main content

ferroui_base/
pixel_vector.rs

1//! Defines a vector in device pixels.
2
3use std::fmt;
4use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
5
6use crate::PixelPoint;
7
8/// Defines a vector in device pixels. Integer arithmetic wraps on overflow.
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
10pub struct PixelVector {
11    /// The X component.
12    pub x: i32,
13    /// The Y component.
14    pub y: i32,
15}
16
17impl PixelVector {
18    /// Initializes a new instance of the [`PixelVector`] structure.
19    #[inline]
20    pub const fn new(x: i32, y: i32) -> Self {
21        Self { x, y }
22    }
23
24    /// Length of the vector. The squared length is computed in (wrapping) 32-bit
25    /// integer arithmetic, so very large vectors yield NaN or a wrapped length.
26    #[inline]
27    pub fn length(&self) -> f64 {
28        (self
29            .x
30            .wrapping_mul(self.x)
31            .wrapping_add(self.y.wrapping_mul(self.y)) as f64)
32            .sqrt()
33    }
34
35    /// Check if two vectors are equal (bitwise).
36    #[inline]
37    pub const fn equals(&self, other: PixelVector) -> bool {
38        self.x == other.x && self.y == other.y
39    }
40
41    /// Check if two vectors are nearly equal (numerically). For integer components
42    /// this is the same as equality.
43    ///
44    /// # Panics
45    /// Panics if a component difference is `i32::MIN`, whose absolute value is not
46    /// representable.
47    pub fn nearly_equals(&self, other: PixelVector) -> bool {
48        // Smallest positive (subnormal) single-precision value.
49        const TOLERANCE: f32 = f32::from_bits(1);
50        const OVERFLOW: &str = "Negating the minimum value of a twos complement number is invalid.";
51
52        let dx = self.x.wrapping_sub(other.x).checked_abs().expect(OVERFLOW);
53        let dy = self.y.wrapping_sub(other.y).checked_abs().expect(OVERFLOW);
54        (dx as f32) < TOLERANCE && (dy as f32) < TOLERANCE
55    }
56
57    /// Returns a new vector with the specified X component.
58    #[inline]
59    pub const fn with_x(&self, x: i32) -> PixelVector {
60        PixelVector::new(x, self.y)
61    }
62
63    /// Returns a new vector with the specified Y component.
64    #[inline]
65    pub const fn with_y(&self, y: i32) -> PixelVector {
66        PixelVector::new(self.x, y)
67    }
68}
69
70/// Converts the [`PixelVector`] to a [`PixelPoint`] (an explicit conversion in the
71/// reference API).
72impl From<PixelVector> for PixelPoint {
73    #[inline]
74    fn from(a: PixelVector) -> PixelPoint {
75        PixelPoint::new(a.x, a.y)
76    }
77}
78
79/// Calculates the dot product of two vectors.
80impl Mul for PixelVector {
81    type Output = i32;
82    #[inline]
83    fn mul(self, b: PixelVector) -> i32 {
84        self.x
85            .wrapping_mul(b.x)
86            .wrapping_add(self.y.wrapping_mul(b.y))
87    }
88}
89
90/// Scales a vector.
91impl Mul<i32> for PixelVector {
92    type Output = PixelVector;
93    #[inline]
94    fn mul(self, scale: i32) -> PixelVector {
95        PixelVector::new(self.x.wrapping_mul(scale), self.y.wrapping_mul(scale))
96    }
97}
98
99/// Scales a vector.
100///
101/// # Panics
102/// Panics on division by zero and on `i32::MIN / -1`.
103impl Div<i32> for PixelVector {
104    type Output = PixelVector;
105    #[inline]
106    fn div(self, scale: i32) -> PixelVector {
107        PixelVector::new(self.x / scale, self.y / scale)
108    }
109}
110
111impl Neg for PixelVector {
112    type Output = PixelVector;
113    #[inline]
114    fn neg(self) -> PixelVector {
115        PixelVector::new(self.x.wrapping_neg(), self.y.wrapping_neg())
116    }
117}
118
119impl Add for PixelVector {
120    type Output = PixelVector;
121    #[inline]
122    fn add(self, b: PixelVector) -> PixelVector {
123        PixelVector::new(self.x.wrapping_add(b.x), self.y.wrapping_add(b.y))
124    }
125}
126
127impl Sub for PixelVector {
128    type Output = PixelVector;
129    #[inline]
130    fn sub(self, b: PixelVector) -> PixelVector {
131        PixelVector::new(self.x.wrapping_sub(b.x), self.y.wrapping_sub(b.y))
132    }
133}
134
135impl AddAssign for PixelVector {
136    #[inline]
137    fn add_assign(&mut self, b: PixelVector) {
138        *self = *self + b;
139    }
140}
141
142impl SubAssign for PixelVector {
143    #[inline]
144    fn sub_assign(&mut self, b: PixelVector) {
145        *self = *self - b;
146    }
147}
148
149impl MulAssign<i32> for PixelVector {
150    #[inline]
151    fn mul_assign(&mut self, scale: i32) {
152        *self = *self * scale;
153    }
154}
155
156impl DivAssign<i32> for PixelVector {
157    #[inline]
158    fn div_assign(&mut self, scale: i32) {
159        *self = *self / scale;
160    }
161}
162
163impl fmt::Display for PixelVector {
164    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165        write!(f, "{}, {}", self.x, self.y)
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    // The reference test-suite has no tests for this type.
172    use super::*;
173
174    #[test]
175    fn arithmetic_and_display() {
176        let a = PixelVector::new(3, 4);
177        let b = PixelVector::new(1, 2);
178        assert_eq!(a * b, 11);
179        assert_eq!(a * 2, PixelVector::new(6, 8));
180        assert_eq!(a / 2, PixelVector::new(1, 2));
181        assert_eq!(-a, PixelVector::new(-3, -4));
182        assert_eq!(a + b, PixelVector::new(4, 6));
183        assert_eq!(a - b, PixelVector::new(2, 2));
184        assert_eq!(a.length(), 5.0);
185        assert_eq!(a.to_string(), "3, 4");
186        assert_eq!(PixelPoint::from(a), PixelPoint::new(3, 4));
187        assert!(a.nearly_equals(a));
188        assert!(!a.nearly_equals(b));
189        assert_eq!(a.with_x(9).with_y(8), PixelVector::new(9, 8));
190    }
191
192    #[test]
193    #[should_panic]
194    fn division_by_zero_panics() {
195        let _ = PixelVector::new(1, 1) / 0;
196    }
197}