1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
use crate::grid_coord_2d::GridCoord2D;
use crate::room4::Wall4;
use crate::wall4_grid::Wall4Grid;
use std::ops::Index;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ImageFormat {
/// Portable Pixmap (black white)
PPM,
/// Portable bitmap (colors)
PBM,
}
/// Renderer for generating PPM or PBM images from a maze.
pub struct ImageRenderer {
format: ImageFormat,
// Optional: Customize colors for PPM
wall_color: (u8, u8, u8), // RGB for walls
path_color: (u8, u8, u8), // RGB for paths
}
impl ImageRenderer {
/// Creates a new ImageRenderer with the specified format.
///
/// For PPM, you can optionally specify wall and path colors.
///
/// ## Example
/// ```
/// use amaze::renderers::{ImageRenderer, ImageFormat};
///
/// let renderer = ImageRenderer::new(ImageFormat::PPM);
/// ```
pub fn new(format: ImageFormat) -> Self {
Self {
format,
wall_color: (12, 12, 72), // Default: (Almost) Black walls
path_color: (255, 255, 255), // Default: White paths
}
}
/// Sets custom colors for walls and paths (only applicable for PPM).
///
/// ## Example
/// ```
/// # use amaze::renderers::{ImageRenderer, ImageFormat};
/// let mut renderer = ImageRenderer::new(ImageFormat::PPM);
/// renderer.set_colors((255, 0, 0), (255, 255, 255)); // Red walls, White paths
/// ```
pub fn set_colors(&mut self, wall_color: (u8, u8, u8), path_color: (u8, u8, u8)) {
self.wall_color = wall_color;
self.path_color = path_color;
}
/// Renders the maze into the specified image format.
///
/// Returns the image data as a `String`.
///
/// ## Example
/// ```
/// # use amaze::renderers::{ImageRenderer, ImageFormat};
/// # use amaze::generators::RecursiveBacktracker4;
/// # let gen = RecursiveBacktracker4::default();
/// # let grid = gen.generate(6, 6);
/// let renderer = ImageRenderer::new(ImageFormat::PBM);
/// let image_data = renderer.render(&grid);
/// ```
pub fn render(&self, grid: &Wall4Grid) -> String {
match self.format {
ImageFormat::PPM => self.render_ppm(grid),
ImageFormat::PBM => self.render_pbm(grid),
}
}
/// Renders the maze as a PPM image.
fn render_ppm(&self, grid: &Wall4Grid) -> String {
let image_width = grid.width() * 2 + 1;
let image_height = grid.height() * 2 + 1;
let mut ppm = String::new();
// PPM Header
ppm.push_str(&format!("P3\n{} {}\n255\n", image_width, image_height));
// Initialize all pixels to walls (black)
let mut pixels = vec![vec![self.wall_color; image_width]; image_height];
// Carve out paths
for y in 0..grid.height() {
for x in 0..grid.width() {
let cell = GridCoord2D::new(x, y);
let img_x = x * 2 + 1;
let img_y = y * 2 + 1;
// Set the cell position to path color
pixels[img_y][img_x] = self.path_color;
// Check and carve passages
let wall = grid.index(cell);
if !wall.contains(Wall4::NORTH) && y > 0 {
pixels[img_y - 1][img_x] = self.path_color;
}
if !wall.contains(Wall4::SOUTH) && y < grid.height() - 1 {
pixels[img_y + 1][img_x] = self.path_color;
}
if !wall.contains(Wall4::EAST) && x < grid.width() - 1 {
pixels[img_y][img_x + 1] = self.path_color;
}
if !wall.contains(Wall4::WEST) && x > 0 {
pixels[img_y][img_x - 1] = self.path_color;
}
}
}
// Convert pixel data to PPM format
for row in pixels {
for (i, pixel) in row.iter().enumerate() {
ppm.push_str(&format!("{} {} {} ", pixel.0, pixel.1, pixel.2));
// Optional: Add line breaks every 5 pixels for readability
if i % 5 == 4 {
ppm.push('\n');
}
}
ppm.push('\n');
}
ppm
}
/// Renders the maze as a PBM image.
fn render_pbm(&self, grid: &Wall4Grid) -> String {
let image_width = grid.width() * 2 + 1;
let image_height = grid.height() * 2 + 1;
let mut pbm = String::new();
// PBM Header
pbm.push_str(&format!("P1\n{} {}\n", image_width, image_height));
// Initialize all pixels to walls (1)
let mut pixels = vec![vec![1; image_width]; image_height];
// Carve out paths
for y in 0..grid.height() {
for x in 0..grid.width() {
let cell = GridCoord2D::new(x, y);
let img_x = x * 2 + 1;
let img_y = y * 2 + 1;
// Set the cell position to path (0)
pixels[img_y][img_x] = 0;
// Check and carve passages
let wall = grid.index(cell);
if !wall.contains(Wall4::NORTH) && y > 0 {
pixels[img_y - 1][img_x] = 0;
}
if !wall.contains(Wall4::SOUTH) && y < grid.height() - 1 {
pixels[img_y + 1][img_x] = 0;
}
if !wall.contains(Wall4::EAST) && x < grid.width() - 1 {
pixels[img_y][img_x + 1] = 0;
}
if !wall.contains(Wall4::WEST) && x > 0 {
pixels[img_y][img_x - 1] = 0;
}
}
}
// Convert pixel data to PBM format
for row in pixels {
for pixel in row {
pbm.push_str(&format!("{} ", pixel));
}
pbm.push('\n');
}
pbm
}
}