1use crate::drawing::traits::Drawable;
6use crate::utils;
7use crate::{CoreError, Result};
8pub mod iterators;
9
10use image::{ImageBuffer, ImageReader, Rgba};
11use minifb::{Key, Window, WindowOptions};
12use std::path::Path;
13
14pub struct Image {
27 width: u32,
28 height: u32,
29 data: Vec<u8>,
30}
31
32impl Image {
33 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
38 let dyn_img = ImageReader::open(path)?.decode()?;
39 let rgba = dyn_img.to_rgba8();
40 let (width, height) = rgba.dimensions();
41 Ok(Image {
42 width,
43 height,
44 data: rgba.into_raw(),
45 })
46 }
47
48 pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
53 let buffer: ImageBuffer<Rgba<u8>, _> =
54 ImageBuffer::from_raw(self.width, self.height, self.data.clone())
55 .ok_or_else(|| std::io::Error::other("Invalid buffer"))?;
56
57 buffer.save(path)?;
58
59 Ok(())
60 }
61
62 pub fn new(width: u32, height: u32, data: Vec<u8>) -> Result<Self> {
64 if data.len() != (width as usize) * (height as usize) * 4 {
65 return Err(std::io::Error::new(
66 std::io::ErrorKind::InvalidInput,
67 "Data buffer length does not match width * height * 4",
68 )
69 .into());
70 }
71
72 Ok(Image {
73 width,
74 height,
75 data,
76 })
77 }
78
79 pub fn display(&self, title: &str) -> Result<()> {
84 let dims = self.dimensions();
85 let width = dims[0] as usize;
86 let height = dims[1] as usize;
87
88 let mut window = Window::new(
90 title,
91 width,
92 height,
93 WindowOptions {
94 resize: false,
95 ..Default::default()
96 },
97 )?;
98 window.set_target_fps(1);
99
100 let rgba_bytes: &[u8] = &self.data;
102 let mut buffer: Vec<u32> = Vec::with_capacity(rgba_bytes.len() / 4);
103 for chunk in rgba_bytes.chunks(4) {
104 buffer.push(u32::from_be_bytes([chunk[3], chunk[0], chunk[1], chunk[2]]));
105 }
106
107 while window.is_open() && !window.is_key_down(Key::Escape) {
108 window.update_with_buffer(&buffer, width, height)?;
109 }
110
111 Ok(())
112 }
113
114 pub fn get_pixel(&self, position: [u32; 2]) -> Result<[u8; 4]> {
117 let dims = self.dimensions();
118 if position[0] >= dims[0] || position[1] >= dims[1] {
119 return Err(CoreError::OutOfBounds(format!(
120 "The image dimensions are {dims:?}. Getting pixel {position:?} is not possible."
121 )));
122 }
123
124 let idx = ((position[1] * dims[0] + position[0]) * 4) as usize;
125 let pixel = [
126 self.data[idx],
127 self.data[idx + 1],
128 self.data[idx + 2],
129 self.data[idx + 3],
130 ];
131 Ok(pixel)
132 }
133
134 pub fn set_pixel(&mut self, position: [u32; 2], color: [u8; 4]) -> Result<()> {
136 let dims = self.dimensions();
137 if position[0] >= dims[0] || position[1] >= dims[1] {
138 return Err(CoreError::OutOfBounds(format!(
139 "The image dimensions are {dims:?}. Setting pixel {position:?} is not possible."
140 )));
141 }
142
143 let idx = ((position[1] * dims[0] + position[0]) * 4) as usize;
144 self.data[idx..idx + 4].copy_from_slice(&color);
145
146 Ok(())
147 }
148
149 pub fn alpha_blend_pixel(&mut self, position: [u32; 2], color: [u8; 4]) -> Result<()> {
151 let dims = self.dimensions();
152 if position[0] >= dims[0] || position[1] >= dims[1] {
153 return Err(CoreError::OutOfBounds(format!(
154 "The image dimensions are {dims:?}. Setting pixel {position:?} is not possible."
155 )));
156 }
157
158 let color_fg = color;
159 let color_bg = self.get_pixel(position)?;
160 let blend_color = utils::alpha_blend(color_fg, color_bg);
161
162 self.set_pixel(position, blend_color)?;
163
164 Ok(())
165 }
166
167 pub fn draw<D: Drawable>(&mut self, shape: D) -> Result<()> {
169 shape.draw_on(self)?;
170 Ok(())
171 }
172
173 pub fn is_empty(&self) -> bool {
175 self.data.is_empty()
176 }
177
178 pub fn dimensions(&self) -> [u32; 2] {
180 [self.width, self.height]
181 }
182}