use crate::maze::grid::cell::Cell;
use crate::maze::{formatters::Formatter, grid::Grid};
use crate::utils::color::Color;
use crate::utils::types::Coords;
use image::{ImageBuffer, RgbImage};
use super::ImageWrapper;
pub struct Image {
wall_width: usize,
passage_width: usize,
margin: usize,
background_color: Color,
foreground_color: Color,
}
impl Image {
pub const fn new() -> Image {
Image {
wall_width: 40,
passage_width: 40,
background_color: Color::RGB(250, 250, 250),
foreground_color: Color::RGB(0, 0, 0),
margin: 50,
}
}
pub const fn wall(mut self, width: usize) -> Self {
self.wall_width = width;
self
}
pub const fn passage(mut self, width: usize) -> Self {
self.passage_width = width;
self
}
pub const fn background(mut self, color: Color) -> Self {
self.background_color = color;
self
}
pub const fn foreground(mut self, color: Color) -> Self {
self.foreground_color = color;
self
}
pub const fn margin(mut self, value: usize) -> Self {
self.margin = value;
self
}
const fn cell_width(&self) -> usize {
self.wall_width * 2 + self.passage_width
}
const fn sizes(&self, grid: &Grid) -> (usize, usize) {
let maze_width = self.cell_width() * grid.width() - (grid.width() - 1) * self.wall_width;
let maze_height = self.cell_width() * grid.height() - (grid.height() - 1) * self.wall_width;
let image_width = maze_width + self.margin * 2;
let image_height = maze_height + self.margin * 2;
(image_width, image_height)
}
fn fill_background(&self, image: &mut RgbImage) {
for (_, _, pixel) in image.enumerate_pixels_mut() {
*pixel = match self.background_color {
Color::RGB(r, g, b) => image::Rgb([r, g, b]),
}
}
}
fn draw_maze(&self, image: &mut RgbImage, grid: &Grid) {
for y in 0..grid.height() {
for x in 0..grid.width() {
self.draw_cell((x, y), grid, image);
}
}
}
fn draw_cell(&self, coords: Coords, grid: &Grid, image: &mut RgbImage) {
let (x, y) = coords;
let inner_cell_width = self.cell_width() - self.wall_width;
let cell_x = x * inner_cell_width + self.margin;
let cell_y = y * inner_cell_width + self.margin;
for py in cell_y..cell_y + self.cell_width() {
for px in cell_x..cell_x + self.cell_width() {
if px >= cell_x
&& px <= cell_x + self.wall_width
&& py >= cell_y
&& py <= cell_y + self.wall_width
&& grid.is_carved(coords, Cell::NORTH)
&& grid.is_carved(coords, Cell::WEST)
{
continue;
}
if px >= cell_x + self.wall_width
&& px <= cell_x + inner_cell_width
&& py >= cell_y
&& py <= cell_y + self.wall_width
&& grid.is_carved(coords, Cell::NORTH)
{
continue;
}
if px >= cell_x + inner_cell_width
&& px <= cell_x + self.cell_width()
&& py >= cell_y
&& py <= cell_y + self.wall_width
&& grid.is_carved(coords, Cell::NORTH)
&& grid.is_carved(coords, Cell::EAST)
{
continue;
}
if px >= cell_x
&& px <= cell_x + self.wall_width
&& py >= cell_y + self.wall_width
&& py <= cell_y + inner_cell_width
&& grid.is_carved(coords, Cell::WEST)
{
continue;
}
if px >= cell_x + self.wall_width
&& px <= cell_x + inner_cell_width
&& py >= cell_y + self.wall_width
&& py <= cell_y + inner_cell_width
{
continue;
}
if px >= cell_x + inner_cell_width
&& px <= cell_x + self.cell_width()
&& py >= cell_y + self.wall_width
&& py <= cell_y + inner_cell_width
&& grid.is_carved(coords, Cell::EAST)
{
continue;
}
if px >= cell_x
&& px <= cell_x + self.wall_width
&& py >= cell_y + inner_cell_width
&& py <= cell_y + self.cell_width()
&& grid.is_carved(coords, Cell::SOUTH)
&& grid.is_carved(coords, Cell::WEST)
{
continue;
}
if px >= cell_x + self.wall_width
&& px <= cell_x + inner_cell_width
&& py >= cell_y + inner_cell_width
&& py <= cell_y + self.cell_width()
&& grid.is_carved(coords, Cell::SOUTH)
{
continue;
}
if px >= cell_x + inner_cell_width
&& px <= cell_x + self.cell_width()
&& py >= cell_y + inner_cell_width
&& py <= cell_y + self.cell_width()
&& grid.is_carved(coords, Cell::SOUTH)
&& grid.is_carved(coords, Cell::EAST)
{
continue;
}
*image.get_pixel_mut(px as u32, py as u32) = match self.foreground_color {
Color::RGB(r, g, b) => image::Rgb([r, g, b]),
}
}
}
let is_rightmost_cell = x == grid.width() - 1;
let is_bottommost_cell = y == grid.height() - 1;
if is_rightmost_cell || is_bottommost_cell {
let cell_right_x = cell_x + self.cell_width() - 1;
let cell_bottom_y = cell_y + self.cell_width() - 1;
for py in cell_y..=cell_bottom_y {
for px in cell_x..=cell_right_x {
if (is_rightmost_cell && px > cell_right_x - self.wall_width)
|| (is_bottommost_cell && py > cell_bottom_y - self.wall_width)
{
*image.get_pixel_mut(px as u32, py as u32) = match self.foreground_color {
Color::RGB(r, g, b) => image::Rgb([r, g, b]),
}
}
}
}
}
}
}
impl Default for Image {
fn default() -> Self {
Self::new()
}
}
impl Formatter<ImageWrapper> for Image {
fn format(&self, grid: &Grid) -> ImageWrapper {
let (width, height) = self.sizes(grid);
let mut image: RgbImage = ImageBuffer::new(width as u32, height as u32);
self.fill_background(&mut image);
self.draw_maze(&mut image, grid);
ImageWrapper(image)
}
}
#[cfg(test)]
mod tests {
use image::EncodableLayout;
use crate::maze::grid::cell::Cell;
use super::*;
#[test]
fn new_call_default_params() {
let image = Image::new();
assert_eq!(40, image.wall_width);
assert_eq!(40, image.passage_width);
assert_eq!(Color::RGB(250, 250, 250), image.background_color);
assert_eq!(Color::RGB(0, 0, 0), image.foreground_color);
assert_eq!(50, image.margin);
}
#[test]
fn params_change() {
let image = Image::new()
.wall(10)
.passage(5)
.background(Color::RGB(1, 1, 1))
.foreground(Color::RGB(100, 100, 100))
.margin(20);
assert_eq!(10, image.wall_width);
assert_eq!(5, image.passage_width);
assert_eq!(Color::RGB(1, 1, 1), image.background_color);
assert_eq!(Color::RGB(100, 100, 100), image.foreground_color);
assert_eq!(20, image.margin);
}
#[test]
fn format_zero_margin() {
let formatter = Image::new().wall(1).passage(1).margin(0);
let grid = generate_maze();
let actual = formatter.format(&grid).into_inner();
let expected = image::open("tests/fixtures/zero_margin_maze.png").unwrap();
assert_eq!(actual.as_bytes(), expected.as_bytes());
}
#[test]
fn format_nonzero_margin() {
let formatter = Image::new().wall(1).passage(1).margin(1);
let grid = generate_maze();
let actual = formatter.format(&grid).into_inner();
let expected = image::open("tests/fixtures/nonzero_margin_maze.png").unwrap();
assert_eq!(actual.as_bytes(), expected.as_bytes());
}
fn generate_maze() -> Grid {
let mut grid = Grid::new(4, 4);
grid.carve_passage((0, 0), Cell::SOUTH).unwrap();
grid.carve_passage((0, 1), Cell::EAST).unwrap();
grid.carve_passage((0, 2), Cell::EAST).unwrap();
grid.carve_passage((0, 2), Cell::SOUTH).unwrap();
grid.carve_passage((0, 3), Cell::EAST).unwrap();
grid.carve_passage((1, 0), Cell::EAST).unwrap();
grid.carve_passage((1, 1), Cell::EAST).unwrap();
grid.carve_passage((1, 1), Cell::SOUTH).unwrap();
grid.carve_passage((1, 2), Cell::EAST).unwrap();
grid.carve_passage((1, 3), Cell::EAST).unwrap();
grid.carve_passage((2, 0), Cell::EAST).unwrap();
grid.carve_passage((2, 2), Cell::EAST).unwrap();
grid.carve_passage((2, 3), Cell::EAST).unwrap();
grid.carve_passage((3, 1), Cell::NORTH).unwrap();
grid.carve_passage((3, 1), Cell::SOUTH).unwrap();
grid
}
}