1#![feature(array_windows)]
2use std::{fs::File, io::Write};
3
4pub enum Color {
5 Black,
6 Grey,
7 White,
8 Red,
9 Green,
10 Blue,
11 Yellow,
12 Cyan,
13 Magenta,
14 Rgb(u8, u8, u8),
15 Hex(u32),
16 RgbFloat(f64, f64, f64),
17}
18
19#[derive(Clone, Copy)]
20pub struct Pixel {
21 r: u8,
22 g: u8,
23 b: u8,
24}
25
26impl Pixel {
27 pub fn new(r: u8, g: u8, b: u8) -> Self {
28 Self { r, g, b }
29 }
30
31 pub fn color(c: Color) -> Self {
32 match c {
33 Color::Black => Pixel::new(0, 0, 0),
34 Color::Grey => Pixel::new(0xff / 2, 0xff / 2, 0xff / 2),
35 Color::White => Pixel::new(0xff, 0xff, 0xff),
36 Color::Red => Pixel::new(0xff, 0, 0),
37 Color::Green => Pixel::new(0, 0xff, 0),
38 Color::Blue => Pixel::new(0, 0, 0xff),
39 Color::Yellow => Pixel::new(0xff, 0xff, 0),
40 Color::Cyan => Pixel::new(0, 0xff, 0xff),
41 Color::Magenta => Pixel::new(0xff, 0, 0xff),
42 Color::Rgb(r, g, b) => Pixel::new(r, g, b),
43 Color::RgbFloat(r, g, b) => {
44 let convert = |v| (v * 255.0) as u8;
45 Pixel::new(convert(r), convert(g), convert(b))
46 }
47 Color::Hex(h) => {
48 let bytes = h.to_le_bytes();
49 Pixel::new(bytes[2], bytes[1], bytes[0])
50 }
51 }
52 }
53
54 pub fn empty() -> Self {
55 Self { r: 0, g: 0, b: 0 }
56 }
57
58 pub fn ppm(&self) -> String {
59 format!("{} {} {}", self.r, self.g, self.b)
60 }
61}
62
63#[derive(Debug)]
64pub enum PaperError {
65 OutOfBounds,
66}
67
68pub struct Paper {
69 width: usize,
70 height: usize,
71 pixels: Vec<Pixel>,
72}
73
74impl Paper {
75 pub fn new(width: usize, height: usize) -> Self {
76 Self {
77 width,
78 height,
79 pixels: vec![Pixel::empty(); width * height],
80 }
81 }
82
83 fn ppm(&self) -> String {
84 let header = format!("P3\n{} {}\n255", self.width, self.height);
85 let pixels = self
86 .pixels
87 .iter()
88 .map(|pixel| pixel.ppm())
89 .collect::<Vec<String>>()
90 .join("\n");
91 return [header, pixels, String::new()].join("\n");
92 }
93
94 pub fn generate(&self, path: String) -> std::io::Result<()> {
95 let mut file = File::create(path)?;
96 file.write_all(self.ppm().as_bytes())?;
97 Ok(())
98 }
99
100 pub fn set(&mut self, x: usize, y: usize, pixel: Pixel) {
101 self.pixels[y * self.width + x] = pixel
102 }
103
104 pub fn set_checked(&mut self, x: usize, y: usize, pixel: Pixel) -> Result<(), PaperError> {
105 if !(x < self.width && y < self.height) {
106 return Err(PaperError::OutOfBounds);
107 }
108
109 self.set(x, y, pixel);
110 Ok(())
111 }
112
113 pub fn set_horizontal(&mut self, y: usize, pixel: Pixel) {
114 let index = y * self.width;
115 let mut row = vec![pixel; self.width];
116 self.pixels[index..index + self.width].swap_with_slice(&mut row)
117 }
118
119 pub fn set_vertical(&mut self, x: usize, pixel: Pixel) {
120 self.pixels
121 .chunks_mut(self.width)
122 .for_each(|row| row[x] = pixel)
123 }
124
125 pub fn fill(&mut self, pixel: Pixel) {
126 self.pixels.iter_mut().for_each(|p| *p = pixel)
127 }
128
129 pub fn rect(
130 &mut self,
131 x: usize,
132 y: usize,
133 width: usize,
134 height: usize,
135 pixel: Pixel,
136 ) -> Result<(), PaperError> {
137 if !(x + width < self.width && y + height < self.height) {
138 return Err(PaperError::OutOfBounds);
139 }
140
141 for row in y..y + height {
142 for col in x..x + width {
143 self.set(col, row, pixel)
144 }
145 }
146
147 Ok(())
148 }
149
150 pub fn circle(
151 &mut self,
152 x: usize,
153 y: usize,
154 radius: usize,
155 pixel: Pixel,
156 ) -> Result<(), PaperError> {
157 if !(radius < x && radius < y && x + radius < self.width && y + radius < self.height) {
158 return Err(PaperError::OutOfBounds);
159 }
160
161 let odd = 1 - radius % 2;
162
163 for row in y - radius..y + radius {
164 let v = y as isize - row as isize;
165 for col in x - radius..x + radius {
166 let h = x as isize - col as isize;
185 let d_squared = h * h + v * v;
186 if (d_squared as usize) <= radius * radius + (1 * odd) {
189 self.set(col, row, pixel)
190 }
191 }
192 }
193
194 Ok(())
195 }
196
197 pub fn line(&mut self, x0: usize, y0: usize, x1: usize, y1: usize, pixel: Pixel) {
198 let dx = x1.abs_diff(x0) as isize;
199 let sx = if x0 < x1 { 1 } else { -1 };
200 let dy = -(y1.abs_diff(y0) as isize);
201 let sy = if y0 < y1 { 1 } else { -1 };
202 let mut error = dx + dy;
203 let mut x0 = x0 as isize;
204 let mut y0 = y0 as isize;
205 loop {
206 if self.set_checked(x0 as usize, y0 as usize, pixel).is_err() {
207 break;
208 };
209 if x0 == x1 as isize && y0 == y1 as isize {
210 break;
211 }
212 if 2 * error >= dy {
213 if x0 == x1 as isize {
214 break;
215 }
216 error = error + dy;
217 x0 = x0 + sx;
218 }
219 if 2 * error <= dx {
220 if y0 == y1 as isize {
221 break;
222 }
223 error = error + dx;
224 y0 = y0 + sy;
225 }
226 }
227 }
228
229 pub fn stroke(&mut self, curve: Vec<(usize, usize)>, pixel: Pixel) {
230 for &[(x0, y0), (x1, y1)] in curve.array_windows() {
231 self.line(x0, y0, x1, y1, pixel)
232 }
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 #[test]
241 fn pixel_ppm() {
242 let pixel = Pixel::new(20, 30, 40);
243 let ppm = pixel.ppm();
244 assert_eq!(ppm, "20 30 40");
245 }
246
247 #[test]
248 fn ppm() {
249 let mut paper = Paper::new(2, 3);
250 paper.line(0, 0, 1, 2, Pixel::color(Color::Hex(0xffeeaa)));
251 let ppm = "P3
2522 3
253255
254255 238 170
2550 0 0
2560 0 0
257255 238 170
2580 0 0
259255 238 170
260";
261 assert_eq!(paper.ppm(), ppm);
262 }
263}