buffer_graphics_lib/
image.rs

1use crate::renderable_image::RenderableImage;
2use crate::renderable_macros::DrawOffset;
3use crate::scaling::{scale_epx, scale_nearest_neighbor};
4use crate::{Graphics, GraphicsError};
5use graphics_shapes::coord::Coord;
6use ici_files::image::IndexedImage;
7use ici_files::prelude::*;
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10use std::fmt::{Debug, Formatter};
11
12/// Images are a rectangle of pixels that can be manipulated and drawn on screen/saved to disk
13#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
14#[derive(Clone, Eq, PartialEq)]
15pub struct Image {
16    pixels: Vec<Color>,
17    width: usize,
18    height: usize,
19    is_transparent: bool,
20}
21
22impl Debug for Image {
23    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
24        write!(f, "Image: {}x{}", self.width, self.height)
25    }
26}
27
28impl Image {
29    /// Create an image of width x height size using provided pixels
30    pub fn new(pixels: Vec<Color>, width: usize, height: usize) -> Result<Self, GraphicsError> {
31        let is_transparent = pixels.iter().any(|c| c.is_transparent());
32        if width * height != pixels.len() {
33            Err(GraphicsError::ImageInitSize(width * height, pixels.len()))
34        } else {
35            Ok(Image {
36                pixels,
37                width,
38                height,
39                is_transparent,
40            })
41        }
42    }
43
44    /// Create a white image of width x height size
45    pub fn new_blank(width: usize, height: usize) -> Self {
46        let pixels = vec![WHITE; width * height];
47        Image::new(pixels, width, height).expect(
48            "Failed to create blank image, please create GitHub issue for buffer-graphics-lib",
49        )
50    }
51
52    pub fn from_indexed(indexed_image: &IndexedImage) -> Image {
53        let mut pixels = Graphics::create_buffer_u8(
54            indexed_image.width() as usize,
55            indexed_image.height() as usize,
56        );
57        let mut graphics = Graphics::new_u8_rgba(&mut pixels, indexed_image.width() as usize, indexed_image.height() as usize)
58            .expect("Creating buffer to make image from indexed image, please raise an issue on GitHub buffer-graphics-lib");
59        graphics.draw_indexed_image((0, 0), indexed_image);
60        graphics.copy_to_image()
61    }
62}
63
64impl Image {
65    #[inline(always)]
66    pub fn width(&self) -> usize {
67        self.width
68    }
69
70    #[inline(always)]
71    pub fn height(&self) -> usize {
72        self.height
73    }
74
75    /// Returns true if any pixels are transparent
76    #[inline(always)]
77    pub fn is_transparent(&self) -> bool {
78        self.is_transparent
79    }
80
81    #[inline(always)]
82    pub fn pixels(&self) -> &[Color] {
83        &self.pixels
84    }
85
86    #[inline]
87    fn recalc_transparency(&mut self) {
88        self.is_transparent = self.pixels().iter().any(|c| c.is_transparent());
89    }
90
91    #[inline]
92    pub fn get_pixel(&self, x: usize, y: usize) -> Color {
93        let addr = y * self.width + x;
94        self.pixels[addr]
95    }
96
97    #[inline]
98    pub fn set_pixel(&mut self, x: usize, y: usize, value: Color) {
99        let addr = y * self.width + x;
100        self.pixels[addr] = value;
101        if !self.is_transparent && value.is_transparent() {
102            self.is_transparent = true;
103        }
104    }
105
106    #[inline]
107    pub fn blend_pixel(&mut self, x: usize, y: usize, value: Color) {
108        let new_color = self.get_pixel(x, y).blend(value);
109        let addr = y * self.width + x;
110        self.pixels[addr] = new_color;
111        self.recalc_transparency();
112    }
113
114    /// Flip image horizontally
115    pub fn flip_horizontal(&mut self) {
116        let half_width = (self.width as f32 / 2.).floor() as usize;
117        for y in 0..self.height {
118            for x in 0..half_width {
119                let y = y * self.width;
120                unsafe {
121                    std::ptr::swap_nonoverlapping(
122                        &mut self.pixels[y + x],
123                        &mut self.pixels[y + self.width - 1 - x],
124                        1,
125                    );
126                }
127            }
128        }
129    }
130
131    /// Flip image vertically
132    pub fn flip_vertical(&mut self) {
133        let half_height = (self.height as f32 / 2.).floor() as usize;
134        for y in 0..half_height {
135            unsafe {
136                std::ptr::swap_nonoverlapping(
137                    &mut self.pixels[y * self.width],
138                    &mut self.pixels[(self.height - 1 - y) * self.width],
139                    self.width,
140                );
141            }
142        }
143    }
144
145    /// Rotate 90° clockwise
146    pub fn rotate_cw(&mut self) -> Image {
147        let mut output = Image::new_blank(self.height, self.width);
148        for y in 0..self.height {
149            for x in 0..self.width {
150                let new_y = x;
151                let new_x = output.width - y - 1;
152                output.set_pixel(new_x, new_y, self.get_pixel(x, y));
153            }
154        }
155        output
156    }
157
158    /// Rotate 90° counterclockwise
159    pub fn rotate_ccw(&mut self) -> Image {
160        let mut output = Image::new_blank(self.height, self.width);
161        for y in 0..self.height {
162            for x in 0..self.width {
163                let new_y = output.height - x - 1;
164                let new_x = y;
165                output.set_pixel(new_x, new_y, self.get_pixel(x, y));
166            }
167        }
168        output
169    }
170
171    /// Blend with another image
172    pub fn blend(&mut self, other: &Image) -> Result<(), GraphicsError> {
173        if self.width != other.width || self.height != other.height {
174            return Err(GraphicsError::ImageBlendSize(
175                self.width,
176                self.height,
177                other.width,
178                other.height,
179            ));
180        }
181
182        for y in 0..self.height {
183            for x in 0..self.width {
184                self.blend_pixel(x, y, other.get_pixel(x, y));
185            }
186        }
187
188        self.recalc_transparency();
189        Ok(())
190    }
191
192    /// Return a new image after scaling
193    pub fn scale(&self, algo: Scaling) -> Image {
194        match algo {
195            Scaling::NearestNeighbour { x_scale, y_scale } => {
196                scale_nearest_neighbor(self, usize::from(x_scale), usize::from(y_scale))
197            }
198            Scaling::Epx2x => scale_epx(self),
199            Scaling::Epx4x => scale_epx(&scale_epx(self)),
200        }
201    }
202
203    #[inline]
204    pub fn to_renderable<P: Into<Coord>>(self, xy: P, draw_offset: DrawOffset) -> RenderableImage {
205        RenderableImage::new(self, xy, draw_offset)
206    }
207}
208
209impl Tint for Image {
210    fn tint_add(&mut self, r_diff: isize, g_diff: isize, b_diff: isize, a_diff: isize) {
211        for pixel in self.pixels.iter_mut() {
212            pixel.tint_add(r_diff, g_diff, b_diff, a_diff);
213        }
214        self.recalc_transparency();
215    }
216
217    fn tint_mul(&mut self, r_diff: f32, g_diff: f32, b_diff: f32, a_diff: f32) {
218        for pixel in self.pixels.iter_mut() {
219            pixel.tint_mul(r_diff, g_diff, b_diff, a_diff);
220        }
221        self.recalc_transparency();
222    }
223}
224
225#[cfg(test)]
226mod test {
227    use crate::image::Image;
228    use ici_files::prelude::{Color, Scaling};
229    use ici_files::Tint;
230
231    fn make_image() -> Image {
232        Image::new(
233            vec![
234                Color::gray(1),
235                Color::gray(2),
236                Color::gray(3),
237                Color::gray(4),
238                Color::gray(5),
239                Color::gray(6),
240                Color::gray(7),
241                Color::gray(8),
242                Color::gray(9),
243            ],
244            3,
245            3,
246        )
247        .unwrap()
248    }
249
250    #[test]
251    fn constructor() {
252        let image = make_image();
253
254        assert_eq!(image.width, 3);
255        assert_eq!(image.height, 3);
256        assert_eq!(image.pixels.len(), 9);
257        assert!(!image.is_transparent);
258        assert_eq!(
259            image.pixels(),
260            vec![
261                Color::gray(1),
262                Color::gray(2),
263                Color::gray(3),
264                Color::gray(4),
265                Color::gray(5),
266                Color::gray(6),
267                Color::gray(7),
268                Color::gray(8),
269                Color::gray(9),
270            ]
271        );
272    }
273
274    #[test]
275    fn test_flip() {
276        let image = make_image();
277        assert_eq!(
278            image.pixels(),
279            vec![
280                Color::gray(1),
281                Color::gray(2),
282                Color::gray(3),
283                Color::gray(4),
284                Color::gray(5),
285                Color::gray(6),
286                Color::gray(7),
287                Color::gray(8),
288                Color::gray(9),
289            ]
290        );
291        let mut horz = make_image();
292        horz.flip_horizontal();
293        assert_eq!(
294            horz.pixels(),
295            vec![
296                Color::gray(3),
297                Color::gray(2),
298                Color::gray(1),
299                Color::gray(6),
300                Color::gray(5),
301                Color::gray(4),
302                Color::gray(9),
303                Color::gray(8),
304                Color::gray(7),
305            ]
306        );
307        let mut vert = make_image();
308        vert.flip_vertical();
309        assert_eq!(
310            vert.pixels(),
311            vec![
312                Color::gray(7),
313                Color::gray(8),
314                Color::gray(9),
315                Color::gray(4),
316                Color::gray(5),
317                Color::gray(6),
318                Color::gray(1),
319                Color::gray(2),
320                Color::gray(3),
321            ]
322        );
323        let mut horz_vert = make_image();
324        horz_vert.flip_horizontal();
325        horz_vert.flip_vertical();
326        assert_eq!(
327            horz_vert.pixels(),
328            vec![
329                Color::gray(9),
330                Color::gray(8),
331                Color::gray(7),
332                Color::gray(6),
333                Color::gray(5),
334                Color::gray(4),
335                Color::gray(3),
336                Color::gray(2),
337                Color::gray(1),
338            ]
339        );
340        let mut vert_horz = make_image();
341        vert_horz.flip_horizontal();
342        vert_horz.flip_vertical();
343        assert_eq!(
344            vert_horz.pixels(),
345            vec![
346                Color::gray(9),
347                Color::gray(8),
348                Color::gray(7),
349                Color::gray(6),
350                Color::gray(5),
351                Color::gray(4),
352                Color::gray(3),
353                Color::gray(2),
354                Color::gray(1),
355            ]
356        );
357    }
358
359    #[test]
360    fn tint_add() {
361        let mut image = make_image();
362        image.tint_add(10, 20, 30, -50);
363        assert_eq!(
364            image.pixels(),
365            vec![
366                Color::new(11, 21, 31, 205),
367                Color::new(12, 22, 32, 205),
368                Color::new(13, 23, 33, 205),
369                Color::new(14, 24, 34, 205),
370                Color::new(15, 25, 35, 205),
371                Color::new(16, 26, 36, 205),
372                Color::new(17, 27, 37, 205),
373                Color::new(18, 28, 38, 205),
374                Color::new(19, 29, 39, 205),
375            ]
376        );
377    }
378
379    #[test]
380    fn tint_mul() {
381        let mut image = make_image();
382        image.tint_mul(0.5, 1.0, 2.0, 1.0);
383        assert_eq!(
384            image.pixels(),
385            vec![
386                Color::new(1, 1, 2, 255),
387                Color::new(1, 2, 4, 255),
388                Color::new(2, 3, 6, 255),
389                Color::new(2, 4, 8, 255),
390                Color::new(3, 5, 10, 255),
391                Color::new(3, 6, 12, 255),
392                Color::new(4, 7, 14, 255),
393                Color::new(4, 8, 16, 255),
394                Color::new(5, 9, 18, 255),
395            ]
396        );
397    }
398
399    #[test]
400    fn rect_scaling() {
401        let image = Image::new(
402            vec![
403                Color::gray(1),
404                Color::gray(2),
405                Color::gray(3),
406                Color::gray(4),
407                Color::gray(5),
408                Color::gray(6),
409            ],
410            3,
411            2,
412        )
413        .unwrap();
414
415        let epx2 = image.scale(Scaling::Epx2x);
416        let epx4 = image.scale(Scaling::Epx4x);
417        let nn2 = image.scale(Scaling::nearest_neighbour(2, 2).unwrap());
418        let nn3 = image.scale(Scaling::nearest_neighbour(3, 3).unwrap());
419
420        assert_eq!(image.width, 3);
421        assert_eq!(image.height, 2);
422        assert_eq!(epx2.width, 6);
423        assert_eq!(epx2.height, 4);
424        assert_eq!(epx4.width, 12);
425        assert_eq!(epx4.height, 8);
426        assert_eq!(nn2.width, 6);
427        assert_eq!(nn2.height, 4);
428        assert_eq!(nn3.width, 9);
429        assert_eq!(nn3.height, 6);
430    }
431
432    #[test]
433    fn square_scaling() {
434        let image = make_image();
435        let epx2 = image.scale(Scaling::Epx2x);
436        let epx4 = image.scale(Scaling::Epx4x);
437        let nn_double = image.scale(Scaling::nn_double());
438        let nn2 = image.scale(Scaling::nearest_neighbour(2, 2).unwrap());
439        let nn3 = image.scale(Scaling::nearest_neighbour(3, 3).unwrap());
440        let nn1_1 = image.scale(Scaling::nearest_neighbour(1, 1).unwrap());
441        let nn1_2 = image.scale(Scaling::nearest_neighbour(1, 2).unwrap());
442        let nn2_1 = image.scale(Scaling::nearest_neighbour(2, 1).unwrap());
443
444        assert_eq!(image.width, 3);
445        assert_eq!(image.height, 3);
446        assert_eq!(epx2.width, 6);
447        assert_eq!(epx2.height, 6);
448        assert_eq!(nn_double.width, 6);
449        assert_eq!(nn_double.height, 6);
450        assert_eq!(nn2.width, 6);
451        assert_eq!(nn2.height, 6);
452        assert_eq!(nn3.width, 9);
453        assert_eq!(nn3.height, 9);
454        assert_eq!(epx4.width, 12);
455        assert_eq!(epx4.height, 12);
456        assert_eq!(nn1_1.width, 3);
457        assert_eq!(nn1_1.height, 3);
458        assert_eq!(nn1_2.width, 3);
459        assert_eq!(nn1_2.height, 6);
460        assert_eq!(nn2_1.width, 6);
461        assert_eq!(nn2_1.height, 3);
462    }
463}