From 15d87fbde659ea212ad05334af3cda35e0cec4f1 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sun, 9 Jun 2024 15:45:13 -0400 Subject: [PATCH] Create the converter to PPM --- ray-tracer/src/lib.rs | 1 + ray-tracer/src/ppm.rs | 121 +++++++++++++++++++++++++++++++++ ray-tracer/src/types/canvas.rs | 26 +++++-- 3 files changed, 142 insertions(+), 6 deletions(-) create mode 100644 ray-tracer/src/ppm.rs diff --git a/ray-tracer/src/lib.rs b/ray-tracer/src/lib.rs index cd40856..a498975 100644 --- a/ray-tracer/src/lib.rs +++ b/ray-tracer/src/lib.rs @@ -1 +1,2 @@ +pub mod ppm; pub mod types; diff --git a/ray-tracer/src/ppm.rs b/ray-tracer/src/ppm.rs new file mode 100644 index 0000000..4689d8a --- /dev/null +++ b/ray-tracer/src/ppm.rs @@ -0,0 +1,121 @@ +use crate::types::Canvas; + +fn color_float_to_int(val: f64) -> u8 { + (val * 256.).clamp(0., 255.) as u8 +} + +fn join_to_line_limit(data: impl IntoIterator) -> Vec { + let mut lines = vec![]; + + let mut line = String::new(); + let mut iter = data.into_iter(); + while let Some(element) = iter.next() { + if line.is_empty() { + line = line + &element; + } else if line.len() + 1 + element.len() < 70 { + line = line + " " + &element; + } else { + lines.push(line); + line = element; + } + } + + if !line.is_empty() { + lines.push(line); + } + + println!("{:?}", lines); + + lines +} + +#[derive(Debug)] +struct PPM(String); + +impl From for PPM { + fn from(c: Canvas) -> Self { + // let v = vec![0.; c.width() * c.height() * 3]; + let header = format!("P3\n{} {}\n255\n", c.width(), c.height()); + + let mut data = vec![]; + for y in 0..c.height() { + let mut row = vec![]; + for x in 0..c.width() { + let pixel = c.pixel(x, y); + row.push(color_float_to_int(pixel.red()).to_string()); + row.push(color_float_to_int(pixel.green()).to_string()); + row.push(color_float_to_int(pixel.blue()).to_string()); + } + let mut lines = join_to_line_limit(row); + data.append(&mut lines); + } + + let data = data.join("\n"); + + Self(format!("{header}{data}\n")) + } +} + +impl std::ops::Deref for PPM { + type Target = String; + fn deref(&self) -> &String { + &self.0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::Color; + + #[test] + fn construct_ppm_header() { + let c = Canvas::new(5, 3); + let ppm = PPM::from(c); + + assert!(ppm.starts_with( + "P3\n\ + 5 3\n\ + 255\n" + )); + } + + #[test] + fn construct_full_ppm_data() { + let mut c = Canvas::new(5, 3); + *c.pixel_mut(0, 0) = Color::new(1.5, 0., 0.); + *c.pixel_mut(2, 1) = Color::new(0., 0.5, 0.); + *c.pixel_mut(4, 2) = Color::new(-0.5, 0., 1.); + + let expected = "P3\n\ + 5 3\n\ + 255\n\ + 255 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n\ + 0 0 0 0 0 0 0 128 0 0 0 0 0 0 0\n\ + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255\n"; + + let ppm = PPM::from(c); + assert_eq!(*ppm, expected); + } + + #[test] + fn ppm_line_length_limit() { + let mut c = Canvas::new(10, 2); + for y in 0..2 { + for x in 0..10 { + *c.pixel_mut(x, y) = Color::new(1., 0.8, 0.6); + } + } + + let expected = "P3\n\ + 10 2\n\ + 255\n\ + 255 204 153 255 204 153 255 204 153 255 204 153 255 204 153 255 204\n\ + 153 255 204 153 255 204 153 255 204 153 255 204 153\n\ + 255 204 153 255 204 153 255 204 153 255 204 153 255 204 153 255 204\n\ + 153 255 204 153 255 204 153 255 204 153 255 204 153\n"; + + let ppm = PPM::from(c); + assert_eq!(*ppm, expected); + } +} diff --git a/ray-tracer/src/types/canvas.rs b/ray-tracer/src/types/canvas.rs index 82c0310..edbfc43 100644 --- a/ray-tracer/src/types/canvas.rs +++ b/ray-tracer/src/types/canvas.rs @@ -25,17 +25,31 @@ impl Canvas { self.height } - pub fn pixel(&self, row: usize, column: usize) -> &Color { - &self.pixels[self.addr(row, column)] + pub fn pixel(&self, x: usize, y: usize) -> &Color { + &self.pixels[self.addr(y, x)] } - pub fn pixel_mut<'a>(&'a mut self, row: usize, column: usize) -> &'a mut Color { - let addr = self.addr(row, column); + pub fn pixel_mut<'a>(&'a mut self, x: usize, y: usize) -> &'a mut Color { + let addr = self.addr(y, x); &mut self.pixels[addr] } #[inline] - fn addr(&self, row: usize, column: usize) -> usize { - row * self.width() + column + fn addr(&self, y: usize, x: usize) -> usize { + y * self.width() + x + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn addresses() { + let c = Canvas::new(5, 3); + + assert_eq!(c.addr(0, 0), 0); + assert_eq!(c.addr(1, 0), 5); + assert_eq!(c.addr(2, 0), 10); } }