Skip to main content

pfx_post/
image.rs

1#[derive(Clone, Debug, PartialEq)]
2pub struct Image {
3    pub width: u32,
4    pub height: u32,
5    pub pixels: Vec<[f32; 4]>,
6}
7
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct ImageError;
10
11impl std::fmt::Display for ImageError {
12    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
13        f.write_str("pixel count does not match the image size")
14    }
15}
16
17impl std::error::Error for ImageError {}
18
19impl Image {
20    pub fn new(width: u32, height: u32) -> Self {
21        Self::solid(width, height, [0.0, 0.0, 0.0, 1.0])
22    }
23
24    pub fn solid(width: u32, height: u32, pixel: [f32; 4]) -> Self {
25        let n = (width as usize).saturating_mul(height as usize);
26        Self {
27            width,
28            height,
29            pixels: vec![pixel; n],
30        }
31    }
32
33    pub fn from_pixels(width: u32, height: u32, pixels: Vec<[f32; 4]>) -> Result<Self, ImageError> {
34        if pixels.len() != (width as usize).saturating_mul(height as usize) {
35            return Err(ImageError);
36        }
37        Ok(Self {
38            width,
39            height,
40            pixels,
41        })
42    }
43
44    pub fn from_fn(width: u32, height: u32, mut f: impl FnMut(u32, u32) -> [f32; 4]) -> Self {
45        let mut pixels = Vec::with_capacity((width as usize).saturating_mul(height as usize));
46        for y in 0..height {
47            for x in 0..width {
48                pixels.push(f(x, y));
49            }
50        }
51        Self {
52            width,
53            height,
54            pixels,
55        }
56    }
57
58    pub fn index(&self, x: u32, y: u32) -> usize {
59        (y * self.width + x) as usize
60    }
61
62    pub fn at(&self, x: u32, y: u32) -> [f32; 4] {
63        self.pixels[self.index(x, y)]
64    }
65
66    pub fn get(&self, x: i32, y: i32) -> [f32; 4] {
67        if self.width == 0 || self.height == 0 {
68            return [0.0; 4];
69        }
70        let x = x.clamp(0, self.width as i32 - 1) as u32;
71        let y = y.clamp(0, self.height as i32 - 1) as u32;
72        self.at(x, y)
73    }
74
75    pub fn sample(&self, x: f32, y: f32) -> [f32; 4] {
76        if self.width == 0 || self.height == 0 {
77            return [0.0; 4];
78        }
79        let max_x = self.width as f32 - 1.0;
80        let max_y = self.height as f32 - 1.0;
81        let x = x.clamp(0.0, max_x);
82        let y = y.clamp(0.0, max_y);
83        let x0 = x.floor() as i32;
84        let y0 = y.floor() as i32;
85        let tx = x - x0 as f32;
86        let ty = y - y0 as f32;
87        let a = self.get(x0, y0);
88        let b = self.get(x0 + 1, y0);
89        let c = self.get(x0, y0 + 1);
90        let d = self.get(x0 + 1, y0 + 1);
91        let mut out = [0.0; 4];
92        for i in 0..4 {
93            let ab = a[i] + (b[i] - a[i]) * tx;
94            let cd = c[i] + (d[i] - c[i]) * tx;
95            out[i] = ab + (cd - ab) * ty;
96        }
97        out
98    }
99
100    pub fn map_rgb(&self, mut f: impl FnMut(u32, u32, [f32; 3]) -> [f32; 3]) -> Self {
101        let mut out = self.clone();
102        for y in 0..self.height {
103            for x in 0..self.width {
104                let i = self.index(x, y);
105                let p = self.pixels[i];
106                let c = f(x, y, [p[0], p[1], p[2]]);
107                out.pixels[i] = [c[0], c[1], c[2], p[3]];
108            }
109        }
110        out
111    }
112}
113
114pub fn rgb(p: [f32; 4]) -> [f32; 3] {
115    [p[0], p[1], p[2]]
116}
117
118pub fn px(c: [f32; 3], a: f32) -> [f32; 4] {
119    [c[0], c[1], c[2], a]
120}