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#[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 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 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 #[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 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 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 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 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 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 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}