use crate::drawing::traits::Drawable;
use crate::utils;
use crate::{CoreError, Result};
pub mod iterators;
use image::{ImageBuffer, ImageReader, Rgba};
use minifb::{Key, Window, WindowOptions};
use std::path::Path;
pub struct Image {
width: u32,
height: u32,
data: Vec<u8>,
}
impl Image {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let dyn_img = ImageReader::open(path)?.decode()?;
let rgba = dyn_img.to_rgba8();
let (width, height) = rgba.dimensions();
Ok(Image {
width,
height,
data: rgba.into_raw(),
})
}
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let buffer: ImageBuffer<Rgba<u8>, _> =
ImageBuffer::from_raw(self.width, self.height, self.data.clone())
.ok_or_else(|| std::io::Error::other("Invalid buffer"))?;
buffer.save(path)?;
Ok(())
}
pub fn new(width: u32, height: u32, data: Vec<u8>) -> Result<Self> {
if data.len() != (width as usize) * (height as usize) * 4 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Data buffer length does not match width * height * 4",
)
.into());
}
Ok(Image {
width,
height,
data,
})
}
pub fn display(&self, title: &str) -> Result<()> {
let dims = self.dimensions();
let width = dims[0] as usize;
let height = dims[1] as usize;
let mut window = Window::new(
title,
width,
height,
WindowOptions {
resize: false,
..Default::default()
},
)?;
window.set_target_fps(1);
let rgba_bytes: &[u8] = &self.data;
let mut buffer: Vec<u32> = Vec::with_capacity(rgba_bytes.len() / 4);
for chunk in rgba_bytes.chunks(4) {
buffer.push(u32::from_be_bytes([chunk[3], chunk[0], chunk[1], chunk[2]]));
}
while window.is_open() && !window.is_key_down(Key::Escape) {
window.update_with_buffer(&buffer, width, height)?;
}
Ok(())
}
pub fn get_pixel(&self, position: [u32; 2]) -> Result<[u8; 4]> {
let dims = self.dimensions();
if position[0] >= dims[0] || position[1] >= dims[1] {
return Err(CoreError::OutOfBounds(format!(
"The image dimensions are {dims:?}. Getting pixel {position:?} is not possible."
)));
}
let idx = ((position[1] * dims[0] + position[0]) * 4) as usize;
let pixel = [
self.data[idx],
self.data[idx + 1],
self.data[idx + 2],
self.data[idx + 3],
];
Ok(pixel)
}
pub fn set_pixel(&mut self, position: [u32; 2], color: [u8; 4]) -> Result<()> {
let dims = self.dimensions();
if position[0] >= dims[0] || position[1] >= dims[1] {
return Err(CoreError::OutOfBounds(format!(
"The image dimensions are {dims:?}. Setting pixel {position:?} is not possible."
)));
}
let idx = ((position[1] * dims[0] + position[0]) * 4) as usize;
self.data[idx..idx + 4].copy_from_slice(&color);
Ok(())
}
pub fn alpha_blend_pixel(&mut self, position: [u32; 2], color: [u8; 4]) -> Result<()> {
let dims = self.dimensions();
if position[0] >= dims[0] || position[1] >= dims[1] {
return Err(CoreError::OutOfBounds(format!(
"The image dimensions are {dims:?}. Setting pixel {position:?} is not possible."
)));
}
let color_fg = color;
let color_bg = self.get_pixel(position)?;
let blend_color = utils::alpha_blend(color_fg, color_bg);
self.set_pixel(position, blend_color)?;
Ok(())
}
pub fn draw<D: Drawable>(&mut self, shape: D) -> Result<()> {
shape.draw_on(self)?;
Ok(())
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn dimensions(&self) -> [u32; 2] {
[self.width, self.height]
}
}