1use crate::error::{Error, Result};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct RgbaImage {
8 pub width: u32,
10 pub height: u32,
12 pub pixels: Vec<u8>,
14}
15
16impl RgbaImage {
17 pub fn new(width: u32, height: u32) -> Self {
19 RgbaImage { width, height, pixels: vec![0; width as usize * height as usize * 4] }
20 }
21
22 pub fn fill(&mut self, pixel: [u8; 4]) {
24 for chunk in self.pixels.as_chunks_mut::<4>().0.iter_mut() {
25 chunk.copy_from_slice(&pixel);
26 }
27 }
28
29 pub fn pixel(&self, x: u32, y: u32) -> [u8; 4] {
31 let at = (y as usize * self.width as usize + x as usize) * 4;
32 self.pixels[at..at + 4].try_into().expect("4 bytes")
33 }
34
35 pub fn set_pixel(&mut self, x: u32, y: u32, pixel: [u8; 4]) {
37 let at = (y as usize * self.width as usize + x as usize) * 4;
38 self.pixels[at..at + 4].copy_from_slice(&pixel);
39 }
40
41 pub fn row_mut(&mut self, y: u32) -> &mut [u8] {
43 let stride = self.width as usize * 4;
44 &mut self.pixels[y as usize * stride..(y as usize + 1) * stride]
45 }
46
47 pub fn alpha_bbox(&self) -> Option<(u32, u32, u32, u32)> {
49 let (mut x0, mut y0, mut x1, mut y1) = (u32::MAX, u32::MAX, 0, 0);
50 for y in 0..self.height {
51 for x in 0..self.width {
52 if self.pixel(x, y)[3] != 0 {
53 x0 = x0.min(x);
54 y0 = y0.min(y);
55 x1 = x1.max(x + 1);
56 y1 = y1.max(y + 1);
57 }
58 }
59 }
60 (x0 != u32::MAX).then_some((x0, y0, x1, y1))
61 }
62
63 pub fn resize_area(&self, width: u32, height: u32) -> RgbaImage {
65 let mut result = RgbaImage::new(width.max(1), height.max(1));
66 if self.width == 0 || self.height == 0 {
67 return result;
68 }
69 let sx = self.width as f64 / result.width as f64;
70 let sy = self.height as f64 / result.height as f64;
71 for ty in 0..result.height {
72 let y0 = ty as f64 * sy;
73 let y1 = y0 + sy;
74 for tx in 0..result.width {
75 let x0 = tx as f64 * sx;
76 let x1 = x0 + sx;
77 let mut sum = [0f64; 4];
78 let mut area = 0f64;
79 let mut y = y0.floor() as u32;
80 while (y as f64) < y1 && y < self.height {
81 let wy = (y1.min(y as f64 + 1.0) - y0.max(y as f64)).max(0.0);
82 let mut x = x0.floor() as u32;
83 while (x as f64) < x1 && x < self.width {
84 let wx = (x1.min(x as f64 + 1.0) - x0.max(x as f64)).max(0.0);
85 let weight = wx * wy;
86 let pixel = self.pixel(x, y);
87 let alpha = pixel[3] as f64 / 255.0;
88 sum[0] += pixel[0] as f64 * alpha * weight;
89 sum[1] += pixel[1] as f64 * alpha * weight;
90 sum[2] += pixel[2] as f64 * alpha * weight;
91 sum[3] += alpha * weight;
92 area += weight;
93 x += 1;
94 }
95 y += 1;
96 }
97 if area > 0.0 && sum[3] > 0.0 {
98 let alpha = sum[3] / area;
99 result.set_pixel(
100 tx,
101 ty,
102 [
103 (sum[0] / sum[3]).round().clamp(0.0, 255.0) as u8,
104 (sum[1] / sum[3]).round().clamp(0.0, 255.0) as u8,
105 (sum[2] / sum[3]).round().clamp(0.0, 255.0) as u8,
106 (alpha * 255.0).round().clamp(0.0, 255.0) as u8,
107 ],
108 );
109 }
110 }
111 }
112 result
113 }
114
115 pub fn halve(&self) -> RgbaImage {
117 let image = self;
118 let (w, h) = ((image.width / 2).max(1), (image.height / 2).max(1));
119 let mut out = RgbaImage::new(w, h);
120 for y in 0..h {
121 for x in 0..w {
122 let mut sum = [0u32; 4];
123 for (dx, dy) in [(0, 0), (1, 0), (0, 1), (1, 1)] {
124 let p = image.pixel((2 * x + dx).min(image.width - 1), (2 * y + dy).min(image.height - 1));
125 let a = p[3] as u32;
126 sum[0] += p[0] as u32 * a;
127 sum[1] += p[1] as u32 * a;
128 sum[2] += p[2] as u32 * a;
129 sum[3] += a;
130 }
131 let total = sum[3].max(1);
133 let pixel = [
134 ((sum[0] + total / 2) / total) as u8,
135 ((sum[1] + total / 2) / total) as u8,
136 ((sum[2] + total / 2) / total) as u8,
137 ((sum[3] + 2) / 4) as u8,
138 ];
139 out.set_pixel(x, y, pixel);
140 }
141 }
142 out
143 }
144
145 pub fn paste(&mut self, other: &RgbaImage, x: i64, y: i64) {
148 for sy in 0..other.height as i64 {
149 let ty = y + sy;
150 if ty < 0 || ty >= self.height as i64 {
151 continue;
152 }
153 for sx in 0..other.width as i64 {
154 let tx = x + sx;
155 if tx < 0 || tx >= self.width as i64 {
156 continue;
157 }
158 self.set_pixel(tx as u32, ty as u32, other.pixel(sx as u32, sy as u32));
159 }
160 }
161 }
162
163 pub fn draw_over(&mut self, other: &RgbaImage, x: i64, y: i64) {
166 for sy in 0..other.height as i64 {
167 let ty = y + sy;
168 if ty < 0 || ty >= self.height as i64 {
169 continue;
170 }
171 for sx in 0..other.width as i64 {
172 let tx = x + sx;
173 if tx < 0 || tx >= self.width as i64 {
174 continue;
175 }
176 let s = other.pixel(sx as u32, sy as u32);
177 if s[3] == 0 {
178 continue;
179 }
180 let d = self.pixel(tx as u32, ty as u32);
181 let (sa, da) = (s[3] as f32 / 255.0, d[3] as f32 / 255.0);
182 let a = sa + da * (1.0 - sa);
183 let mix = |c: usize| (s[c] as f32 * sa + d[c] as f32 * da * (1.0 - sa)) / a;
184 let out = [mix(0), mix(1), mix(2), a * 255.0].map(|v| v.round().clamp(0.0, 255.0) as u8);
185 self.set_pixel(tx as u32, ty as u32, out);
186 }
187 }
188 }
189
190 pub fn to_png(&self) -> Result<Vec<u8>> {
192 encode_png(self.width, self.height, png::ColorType::Rgba, &self.pixels)
193 }
194
195 pub fn from_png(data: &[u8]) -> Result<Self> {
197 decode_png(data)
198 }
199}
200
201#[derive(Debug, Clone, PartialEq, Eq)]
203pub struct GrayImage {
204 pub width: u32,
206 pub height: u32,
208 pub pixels: Vec<u8>,
210}
211
212impl GrayImage {
213 pub fn new(width: u32, height: u32) -> Self {
215 GrayImage { width, height, pixels: vec![0; width as usize * height as usize] }
216 }
217
218 pub fn to_png(&self) -> Result<Vec<u8>> {
220 encode_png(self.width, self.height, png::ColorType::Grayscale, &self.pixels)
221 }
222}
223
224pub fn luma(r: u8, g: u8, b: u8) -> u8 {
226 ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114 + 500) / 1000) as u8
227}
228
229fn encode_png(width: u32, height: u32, color: png::ColorType, pixels: &[u8]) -> Result<Vec<u8>> {
230 let mut output = Vec::new();
231 let mut encoder = png::Encoder::new(&mut output, width, height);
232 encoder.set_color(color);
233 encoder.set_depth(png::BitDepth::Eight);
234 encoder.set_compression(png::Compression::Fast);
235 let mut writer = encoder.write_header().map_err(|error| Error::format(error.to_string()))?;
236 writer.write_image_data(pixels).map_err(|error| Error::format(error.to_string()))?;
237 writer.finish().map_err(|error| Error::format(error.to_string()))?;
238 Ok(output)
239}
240
241pub fn decode_png(data: &[u8]) -> Result<RgbaImage> {
243 let mut decoder = png::Decoder::new(std::io::Cursor::new(data));
244 decoder.set_transformations(png::Transformations::EXPAND | png::Transformations::STRIP_16);
245 let mut reader = decoder.read_info().map_err(|error| Error::format(format!("PNG: {error}")))?;
246 let size = reader.output_buffer_size().ok_or_else(|| Error::format("PNG too large"))?;
247 let mut buffer = vec![0; size];
248 let info = reader.next_frame(&mut buffer).map_err(|error| Error::format(format!("PNG: {error}")))?;
249 let (width, height) = (info.width, info.height);
250 let pixels = &buffer[..info.buffer_size()];
251 let mut image = RgbaImage::new(width, height);
252 let count = width as usize * height as usize;
253 match info.color_type {
254 png::ColorType::Rgba => image.pixels.copy_from_slice(&pixels[..count * 4]),
255 png::ColorType::Rgb => {
256 for (target, source) in image.pixels.as_chunks_mut::<4>().0.iter_mut().zip(pixels.as_chunks::<3>().0.iter())
257 {
258 target.copy_from_slice(&[source[0], source[1], source[2], 255]);
259 }
260 }
261 png::ColorType::GrayscaleAlpha => {
262 for (target, source) in image.pixels.as_chunks_mut::<4>().0.iter_mut().zip(pixels.as_chunks::<2>().0.iter())
263 {
264 target.copy_from_slice(&[source[0], source[0], source[0], source[1]]);
265 }
266 }
267 png::ColorType::Grayscale => {
268 for (target, source) in image.pixels.as_chunks_mut::<4>().0.iter_mut().zip(pixels.iter()) {
269 target.copy_from_slice(&[*source, *source, *source, 255]);
270 }
271 }
272 png::ColorType::Indexed => return Err(Error::format("PNG palette was not expanded")),
273 }
274 Ok(image)
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280
281 #[test]
282 fn png_round_trip() {
283 let mut image = RgbaImage::new(3, 2);
284 image.set_pixel(2, 1, [1, 2, 3, 4]);
285 let decoded = decode_png(&image.to_png().unwrap()).unwrap();
286 assert_eq!(decoded, image);
287 }
288
289 #[test]
290 fn area_resize_averages() {
291 let mut image = RgbaImage::new(2, 2);
292 image.fill([100, 100, 100, 255]);
293 image.set_pixel(0, 0, [200, 200, 200, 255]);
294 let small = image.resize_area(1, 1);
295 assert_eq!(small.pixel(0, 0), [125, 125, 125, 255]);
296 }
297}