use crate::pixels::{ColorTrait, Colors, Pixel};
use crate::utility::{clamp, overlap_colors, to_grey_lumiosity};
use image::{GenericImageView, ImageFormat, RgbaImage};
use std::cmp::{max, min};
use std::fmt;
use std::path::Path;
#[derive(Clone, Debug)]
pub struct Canvas {
pixels: Vec<Pixel>,
height: u32,
width: u32,
}
#[derive(Clone, Debug)]
pub struct Point {
pub x: u32,
pub y: u32,
}
#[derive(Clone, Debug)]
pub struct PixelWithCoordinate {
pub coordinate: Point,
pub pixel: Pixel,
}
#[derive(Clone, Debug)]
pub struct Size {
pub width: u32,
pub height: u32,
}
#[derive(Clone, Debug)]
pub struct Rect {
pub start: Point,
pub size: Size,
}
#[derive(Debug)]
pub enum ImageError {
Decoding(String),
Encoding(String),
Parameter(String),
Limits(String),
Unsupported(String),
IoError(String),
}
impl PartialEq for Size {
fn eq(&self, other: &Self) -> bool {
self.width == other.width && self.height == other.height
}
}
impl Eq for Size {}
impl PartialEq for Canvas {
fn eq(&self, other: &Self) -> bool {
if self.width == other.width && self.height == other.height {
let result = self
.pixels
.iter()
.zip(other.pixels.iter())
.fold(true, |acc, (left, right)| acc && left == right);
return result;
}
false
}
}
impl Eq for Canvas {}
impl fmt::Display for Canvas {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Image with {} pixels and dimensions: ({}, {}).",
self.pixels.len(),
self.width,
self.height
)
}
}
fn map_error(error: &image::ImageError) -> ImageError {
match error {
image::ImageError::Decoding(e) => {
return ImageError::Decoding(e.to_string());
}
image::ImageError::Encoding(e) => {
return ImageError::Encoding(e.to_string());
}
image::ImageError::Parameter(e) => {
return ImageError::Parameter(e.to_string());
}
image::ImageError::Limits(e) => {
return ImageError::Limits(e.to_string());
}
image::ImageError::Unsupported(e) => {
return ImageError::Unsupported(e.to_string());
}
image::ImageError::IoError(e) => {
return ImageError::IoError(e.to_string());
}
}
}
impl Canvas {
pub fn new(width: u32, height: u32) -> Canvas {
let width = max(width, 1);
let height = max(height, 1);
let pixels = vec![Colors::WHITE; (width * height) as usize];
Canvas {
pixels,
height,
width,
}
}
pub fn new_with_data(width: u32, height: u32, data: Vec<Pixel>) -> Canvas {
Canvas {
width,
height,
pixels: data,
}
}
pub fn dimensions(&self) -> Size {
Size {
width: self.width,
height: self.height,
}
}
pub fn pixels(&self) -> std::slice::Iter<'_, Pixel> {
self.pixels.iter()
}
pub fn new_with_background(width: u32, height: u32, color: Pixel) -> Canvas {
let width = max(width, 1);
let height = max(height, 1);
let pixels = vec![color; (width * height) as usize];
Canvas {
pixels,
height,
width,
}
}
pub fn save(&self, filename: &Path) -> Result<(), ImageError> {
let img = RgbaImage::from_vec(
self.width,
self.height,
self.pixels
.iter()
.flat_map(|x| vec![x.r, x.g, x.b, x.a])
.collect(),
);
match img {
Some(image) => {
let res = image.save_with_format(filename, ImageFormat::Png);
match res {
Ok(_) => {
return Ok(());
}
Err(e) => {
return Err(map_error(&e));
}
}
}
None => {
return Ok(());
}
}
}
pub fn load(filename: &Path) -> Result<Canvas, ImageError> {
let img = image::open(filename);
match img {
Ok(image) => {
let (width, height) = image.dimensions();
let mut vec = vec![Colors::WHITE; (width * height) as usize];
for (x, y, pixel) in image.pixels() {
vec[(width * y + x) as usize] = Pixel {
r: pixel[0],
g: pixel[1],
b: pixel[2],
a: pixel[3],
}
}
return Ok(Canvas {
pixels: vec,
height,
width,
});
}
Err(e) => {
return Err(map_error(&e));
}
}
}
pub fn count_pixels(&self, pixel: &Pixel) -> u32 {
self.find_positions_of_pixels(pixel).len() as u32
}
pub fn count_pixels_with_distance(&self, pixel: &Pixel, distance: f32) -> u32 {
self.find_positions_of_pixels_with_distance(pixel, distance)
.len() as u32
}
fn find_positions_of_pixels(&self, pixel: &Pixel) -> Vec<usize> {
self.pixels
.iter()
.enumerate()
.filter(|(_, val)| val == &pixel)
.map(|(i, _)| i)
.collect()
}
fn find_positions_of_pixels_with_distance(&self, pixel: &Pixel, distance: f32) -> Vec<usize> {
self.pixels
.iter()
.enumerate()
.filter(|(_, val)| pixel.distance(&val) < distance)
.map(|(i, _)| i)
.collect()
}
pub fn replace_pixel_with_distance(
mut self,
find_pixel: &Pixel,
distance: f32,
replace_pixel: &Pixel,
) -> Canvas {
let positions = self.find_positions_of_pixels_with_distance(find_pixel, distance);
for pos in positions {
self.pixels[pos] = replace_pixel.clone();
}
self
}
pub fn replace_pixel_with_distance_mut(
&mut self,
find_pixel: &Pixel,
distance: f32,
replace_pixel: &Pixel,
) {
let positions = self.find_positions_of_pixels_with_distance(find_pixel, distance);
for pos in positions {
self.pixels[pos] = replace_pixel.clone();
}
}
pub fn replace_pixel_with(mut self, find_pixel: &Pixel, replace_pixel: &Pixel) -> Canvas {
let positions = self.find_positions_of_pixels(find_pixel);
for pos in positions {
self.pixels[pos] = replace_pixel.clone();
}
self
}
pub fn replace_pixel_with_mut(&mut self, find_pixel: &Pixel, replace_pixel: &Pixel) {
let positions = self.find_positions_of_pixels(find_pixel);
for pos in positions {
self.pixels[pos] = replace_pixel.clone();
}
}
pub fn get_subimage(&self, x: u32, y: u32, width: u32, height: u32) -> Canvas {
let width = min(width, self.width - x);
let height = min(height, self.height - y);
let mut c = Canvas::new(width, height);
for i in 0..width {
for j in 0..height {
c.set_pixel_mut(i, j, &self.get_pixel(x + i, y + j));
}
}
c
}
pub fn draw_subimage_mut(&mut self, x: u32, y: u32, canvas: &Canvas) {
let width = min(canvas.width, self.width - x);
let height = min(canvas.height, self.height - y);
for i in 0..width {
for j in 0..height {
let destination = self.get_pixel(x + i, y + j);
let source = canvas.get_pixel(i, j);
let new_color = overlap_colors(&destination, &source);
self.set_pixel_mut(x + i, y + j, &new_color);
}
}
}
pub fn set_subimage_mut(&mut self, x: u32, y: u32, canvas: &Canvas) {
let width = min(canvas.width, self.width - x);
let height = min(canvas.height, self.height - y);
for i in 0..width {
for j in 0..height {
self.set_pixel_mut(x + i, y + j, &canvas.get_pixel(i, j));
}
}
}
pub fn draw_subimage(mut self, x: u32, y: u32, canvas: &Canvas) -> Canvas {
let width = min(canvas.width, self.width - x);
let height = min(canvas.height, self.height - y);
for i in 0..width {
for j in 0..height {
let destination = self.get_pixel(x + i, y + j);
let source = canvas.get_pixel(i, j);
let new_color = overlap_colors(&destination, &source);
self.set_pixel_mut(x + i, y + j, &new_color);
}
}
self
}
pub fn set_subimage(mut self, x: u32, y: u32, c: &Canvas) -> Canvas {
let width = min(c.width, self.width - x);
let height = min(c.height, self.height - y);
for i in 0..width {
for j in 0..height {
self.set_pixel_mut(x + i, y + j, &c.get_pixel(i, j));
}
}
self
}
pub fn get_pixel(&self, x: u32, y: u32) -> Pixel {
let x = clamp(0, self.width - 1, x);
let y = clamp(0, self.height - 1, y);
let pixel = self.pixels[(self.width * y + x) as usize].clone();
pixel
}
pub fn set_pixel(mut self, x: u32, y: u32, pixel: &Pixel) -> Canvas {
self.pixels[(self.width * y + x) as usize] = pixel.clone();
self
}
pub fn set_pixel_mut(&mut self, x: u32, y: u32, pixel: &Pixel) {
self.pixels[(self.width * y + x) as usize] = pixel.clone();
}
pub fn to_grey(&self) -> Canvas {
let pixels = self.pixels.iter().map(|x| to_grey_lumiosity(x)).collect();
Canvas {
pixels,
height: self.height,
width: self.width,
}
}
pub fn to_grey_mut(&mut self) {
let pixels = self.pixels.iter().map(|x| to_grey_lumiosity(x)).collect();
self.pixels = pixels;
}
pub fn draw_square_mut(&mut self, x: u32, y: u32, w: u32, h: u32, color: &Pixel) {
if x < self.width && y < self.height {
for i in x..min(x + w, self.width) {
for j in y..min(y + h, self.height) {
let current_color = &self.get_pixel(i, j);
let new_color = overlap_colors(¤t_color, &color);
self.set_pixel_mut(i, j, &new_color);
}
}
}
}
pub fn draw_square(mut self, x: u32, y: u32, w: u32, h: u32, color: &Pixel) -> Canvas {
if x < self.width && y < self.height {
for i in x..min(x + w, self.width) {
for j in y..min(y + h, self.height) {
let current_color = &self.get_pixel(i, j);
let new_color = overlap_colors(¤t_color, &color);
self.set_pixel_mut(i, j, &new_color);
}
}
}
self
}
fn in_bounds(&self, x: i64, y: i64) -> bool {
x >= 0 && x < self.width.into() && y >= 0 && y < self.height.into()
}
fn try_get_pixel(&self, x: i64, y: i64) -> Option<&Pixel> {
self.in_bounds(x, y)
.then(|| &self.pixels[(self.width as i64 * y + x) as usize])
}
pub fn fill(mut self, x: u32, y: u32, fill_color: &Pixel) -> Canvas {
let find_color = self.get_pixel(x, y);
if fill_color == &find_color {
return self;
}
let mut to_visit = vec![(x as i64, y as i64)];
while let Some((x, y)) = to_visit.pop() {
self.set_pixel_mut(x as u32, y as u32, fill_color);
for (dx, dy) in [(-1, 0), (1, 0), (0, -1), (0, 1)] {
if self.try_get_pixel(x + dx, y + dy) == Some(&find_color) {
to_visit.push((x + dx, y + dy));
}
}
}
self
}
pub fn filter(&self, filter: fn(&Canvas, u32, u32) -> Pixel) -> Canvas {
let mut canvas = Canvas::new(self.width, self.height);
for x in 0..self.width {
for y in 0..self.height {
let pixel = filter(&self, x, y);
canvas.set_pixel_mut(x, y, &pixel);
}
}
canvas
}
pub fn find_with_predicate(
&self,
predicate: fn(&Pixel, u32, u32) -> bool,
) -> Vec<PixelWithCoordinate> {
let mut vec = Vec::new();
for i in 0..self.width {
for j in 0..self.height {
if predicate(&self.get_pixel(i, j), i, j) {
let p = PixelWithCoordinate {
coordinate: Point { x: i, y: j },
pixel: self.get_pixel(i, j).clone(),
};
vec.push(p);
}
}
}
vec
}
pub fn flip(&self) -> Canvas {
let mut reversed = Vec::with_capacity(self.width as usize * self.height as usize);
for pixels in self.pixels.chunks(self.width as usize) {
let rev: Vec<Pixel> = pixels.iter().rev().map(|x| x.to_owned()).collect();
reversed.extend(rev);
}
Canvas {
pixels: reversed,
width: self.width,
height: self.height,
}
}
pub fn flop(&self) -> Canvas {
let reversed = self.pixels.iter().rev().map(|x| x.to_owned()).collect();
let canvas = Canvas {
pixels: reversed,
width: self.width,
height: self.height,
};
let flipped = canvas.flip();
flipped
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pixels::Pixel;
use crate::utility::count_colors;
#[test]
fn clean_canvas() {
let canvas = Canvas::new(20, 20);
let dimensions = canvas.dimensions();
assert_eq!(
dimensions,
Size {
width: 20,
height: 20
}
);
let counts = count_colors(&canvas);
assert_eq!(counts.keys().len(), 1);
assert_eq!(counts.get(&Pixel::new(255, 255, 255, 255)), Some(&400));
}
#[test]
fn clean_canvas_with_background() {
let color = Pixel::random();
let canvas = Canvas::new_with_background(20, 20, color.clone());
let dimensions = canvas.dimensions();
assert_eq!(
dimensions,
Size {
width: 20,
height: 20
}
);
let counts = count_colors(&canvas);
assert_eq!(counts.keys().len(), 1);
assert_eq!(counts.get(&color), Some(&400));
}
}