Skip to main content

maple_render_core/
pixer.rs

1use image::{Rgba, RgbaImage};
2
3#[derive(Debug, Clone, Copy, Default)]
4pub struct Pixer {
5    pub r: f32,
6    pub g: f32,
7    pub b: f32,
8    pub a: f32,
9}
10
11impl Pixer {
12    #[inline(always)]
13    pub fn new() -> Self {
14        Pixer::default()
15    }
16
17    #[inline(always)]
18    pub fn from_rgba(pixel: &Rgba<u8>) -> Self {
19        Pixer { r: pixel[0] as f32, g: pixel[1] as f32, b: pixel[2] as f32, a: pixel[3] as f32 }
20    }
21
22    #[inline(always)]
23    pub fn to_rgba(&self) -> Rgba<u8> {
24        Rgba([clamp_u8(self.r), clamp_u8(self.g), clamp_u8(self.b), clamp_u8(self.a)])
25    }
26
27    #[inline(always)]
28    pub fn preblend(&mut self) {
29        self.r *= self.a;
30        self.g *= self.a;
31        self.b *= self.a;
32    }
33
34    #[inline(always)]
35    pub fn postblend(&mut self, scale: f32) {
36        if scale > 0.0001 {
37            self.r /= scale;
38            self.g /= scale;
39            self.b /= scale;
40        } else {
41            self.r = 0.0;
42            self.g = 0.0;
43            self.b = 0.0;
44        }
45    }
46
47    pub fn add(&mut self, other: &Pixer) {
48        self.r += other.r;
49        self.g += other.g;
50        self.b += other.b;
51        self.a += other.a;
52    }
53
54    pub fn add_rgba(&mut self, pixel: &Rgba<u8>) {
55        self.r += pixel[0] as f32;
56        self.g += pixel[1] as f32;
57        self.b += pixel[2] as f32;
58        self.a += pixel[3] as f32;
59    }
60
61    pub fn scale(&mut self, factor: f32) {
62        self.r *= factor;
63        self.g *= factor;
64        self.b *= factor;
65        self.a *= factor;
66    }
67
68    pub fn div(&mut self, factor: f32) {
69        if factor.abs() > 0.0001 {
70            self.r /= factor;
71            self.g /= factor;
72            self.b /= factor;
73            self.a /= factor;
74        }
75    }
76}
77
78impl std::ops::Add for Pixer {
79    type Output = Pixer;
80
81    fn add(self, other: Pixer) -> Pixer {
82        Pixer { r: self.r + other.r, g: self.g + other.g, b: self.b + other.b, a: self.a + other.a }
83    }
84}
85
86impl std::ops::AddAssign for Pixer {
87    fn add_assign(&mut self, other: Pixer) {
88        self.r += other.r;
89        self.g += other.g;
90        self.b += other.b;
91        self.a += other.a;
92    }
93}
94
95impl std::ops::Mul<f32> for Pixer {
96    type Output = Pixer;
97
98    fn mul(self, factor: f32) -> Pixer {
99        Pixer { r: self.r * factor, g: self.g * factor, b: self.b * factor, a: self.a * factor }
100    }
101}
102
103impl std::ops::Div<f32> for Pixer {
104    type Output = Pixer;
105
106    fn div(self, factor: f32) -> Pixer {
107        if factor.abs() > 0.0001 {
108            Pixer { r: self.r / factor, g: self.g / factor, b: self.b / factor, a: self.a / factor }
109        } else {
110            Pixer::new()
111        }
112    }
113}
114
115#[inline]
116fn clamp_u8(v: f32) -> u8 {
117    if v <= 0.0 {
118        0
119    } else if v >= 255.0 {
120        255
121    } else {
122        v as u8
123    }
124}
125
126#[inline(always)]
127pub fn safe_pixel(img: &RgbaImage, x: i32, y: i32) -> Rgba<u8> {
128    let w = img.width() as i32;
129    let h = img.height() as i32;
130
131    if x < 0 || y < 0 || x >= w || y >= h {
132        Rgba([0, 0, 0, 0])
133    } else {
134        *img.get_pixel(x as u32, y as u32)
135    }
136}
137
138/// Bilinear interpolation with alpha-weighted averaging.
139///
140/// Coordinates remain `f64` for sub-pixel accuracy (the heavy lifting is the
141/// per-pixel color blend, which is performed in `f32` for throughput). The
142/// `f64` geometry inputs are down-cast to `f32` when constructing the returned
143/// `Pixer`.
144#[inline(always)]
145pub fn sample_linear(img: &RgbaImage, x: f64, y: f64) -> Pixer {
146    let xx = x.floor() as i32;
147    let yy = y.floor() as i32;
148    let fx = x - xx as f64;
149    let fy = y - yy as f64;
150
151    let w00 = ((1.0 - fx) * (1.0 - fy)) as f32;
152    let w10 = (fx * (1.0 - fy)) as f32;
153    let w01 = ((1.0 - fx) * fy) as f32;
154    let w11 = (fx * fy) as f32;
155
156    let w = img.width() as i32;
157    let h = img.height() as i32;
158
159    if xx >= 0 && yy >= 0 && xx + 1 < w && yy + 1 < h {
160        let raw = img.as_raw();
161        let w_us = w as usize;
162        let row_stride = w_us * 4;
163
164        let row0 = yy as usize * row_stride;
165        let row1 = row0 + row_stride;
166        let col = xx as usize * 4;
167
168        let i00 = row0 + col;
169        let i10 = i00 + 4;
170        let i01 = row1 + col;
171        let i11 = i01 + 4;
172
173        let a00 = raw[i00 + 3] as f32;
174        let a10 = raw[i10 + 3] as f32;
175        let a01 = raw[i01 + 3] as f32;
176        let a11 = raw[i11 + 3] as f32;
177
178        let aa = w00 * a00 + w10 * a10 + w01 * a01 + w11 * a11;
179        let aa_safe = if aa < 0.0001 { 0.0001 } else { aa };
180
181        return Pixer {
182            r: (w00 * raw[i00] as f32 * a00
183                + w10 * raw[i10] as f32 * a10
184                + w01 * raw[i01] as f32 * a01
185                + w11 * raw[i11] as f32 * a11)
186                / aa_safe,
187            g: (w00 * raw[i00 + 1] as f32 * a00
188                + w10 * raw[i10 + 1] as f32 * a10
189                + w01 * raw[i01 + 1] as f32 * a01
190                + w11 * raw[i11 + 1] as f32 * a11)
191                / aa_safe,
192            b: (w00 * raw[i00 + 2] as f32 * a00
193                + w10 * raw[i10 + 2] as f32 * a10
194                + w01 * raw[i01 + 2] as f32 * a01
195                + w11 * raw[i11 + 2] as f32 * a11)
196                / aa_safe,
197            a: aa,
198        };
199    }
200
201    let p00 = safe_pixel(img, xx, yy);
202    let p10 = safe_pixel(img, xx + 1, yy);
203    let p01 = safe_pixel(img, xx, yy + 1);
204    let p11 = safe_pixel(img, xx + 1, yy + 1);
205
206    let a00 = p00[3] as f32;
207    let a10 = p10[3] as f32;
208    let a01 = p01[3] as f32;
209    let a11 = p11[3] as f32;
210
211    let aa = w00 * a00 + w10 * a10 + w01 * a01 + w11 * a11;
212    let aa_safe = if aa < 0.0001 { 0.0001 } else { aa };
213
214    Pixer {
215        r: (w00 * p00[0] as f32 * a00
216            + w10 * p10[0] as f32 * a10
217            + w01 * p01[0] as f32 * a01
218            + w11 * p11[0] as f32 * a11)
219            / aa_safe,
220        g: (w00 * p00[1] as f32 * a00
221            + w10 * p10[1] as f32 * a10
222            + w01 * p01[1] as f32 * a01
223            + w11 * p11[1] as f32 * a11)
224            / aa_safe,
225        b: (w00 * p00[2] as f32 * a00
226            + w10 * p10[2] as f32 * a10
227            + w01 * p01[2] as f32 * a01
228            + w11 * p11[2] as f32 * a11)
229            / aa_safe,
230        a: aa,
231    }
232}
233
234pub fn sample_weakly(img: &RgbaImage, x: f64, y: f64) -> Pixer {
235    Pixer::from_rgba(&safe_pixel(img, x as i32, y as i32))
236}
237
238#[inline(always)]
239pub fn distance(x1: f64, y1: f64, x2: f64, y2: f64) -> f64 {
240    ((x1 - x2).powi(2) + (y1 - y2).powi(2)).sqrt()
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    #[test]
248    fn test_pixer_operations() {
249        let mut p = Pixer { r: 100.0, g: 150.0, b: 200.0, a: 255.0 };
250        p.scale(0.5);
251        assert_eq!(p.r, 50.0);
252        assert_eq!(p.g, 75.0);
253        assert_eq!(p.b, 100.0);
254    }
255
256    #[test]
257    fn test_clamp_u8() {
258        assert_eq!(clamp_u8(-10.0), 0);
259        assert_eq!(clamp_u8(128.0), 128);
260        assert_eq!(clamp_u8(300.0), 255);
261    }
262}