2023-02-21 04:03:36 +00:00
|
|
|
use generator::{done, Gn};
|
|
|
|
use itertools::Itertools;
|
|
|
|
use std::{
|
|
|
|
fs::File,
|
|
|
|
io::{BufRead, BufReader, Read, Write},
|
|
|
|
path::Path,
|
|
|
|
};
|
2023-02-19 00:28:55 +00:00
|
|
|
|
2023-02-21 04:03:36 +00:00
|
|
|
use anyhow::Result;
|
2023-02-19 00:28:55 +00:00
|
|
|
use nalgebra::Vector3;
|
|
|
|
|
|
|
|
/// A pixel color represented by a red, green, and blue value in the range 0-1.
|
|
|
|
pub type Color = Vector3<f64>;
|
|
|
|
|
|
|
|
/// A representation of an image
|
2023-02-21 04:03:36 +00:00
|
|
|
#[derive(Derivative)]
|
|
|
|
#[derivative(Debug)]
|
2023-02-19 00:28:55 +00:00
|
|
|
pub struct Image {
|
|
|
|
/// Width in pixels
|
|
|
|
pub width: usize,
|
|
|
|
|
|
|
|
/// Height in pixels
|
|
|
|
pub height: usize,
|
|
|
|
|
|
|
|
/// Pixel data in row-major form.
|
2023-02-21 04:03:36 +00:00
|
|
|
#[derivative(Debug = "ignore")]
|
2023-02-19 00:28:55 +00:00
|
|
|
pub data: Vec<Color>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Image {
|
2023-02-21 04:03:36 +00:00
|
|
|
pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
|
|
|
|
let file = File::open(path.as_ref())?;
|
|
|
|
Self::read(file)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Parse image from a Read
|
|
|
|
pub fn read(r: impl Read + Send) -> Result<Self> {
|
|
|
|
let mut line_reader = BufReader::new(r);
|
|
|
|
|
|
|
|
let mut header = String::new();
|
|
|
|
line_reader.read_line(&mut header)?;
|
|
|
|
let parts = header.split(" ").collect::<Vec<_>>();
|
|
|
|
|
|
|
|
let width = parts[1].parse::<usize>()?;
|
|
|
|
let height = parts[2].parse::<usize>()?;
|
|
|
|
|
|
|
|
let numbers = Gn::<()>::new_scoped(move |mut s| {
|
|
|
|
macro_rules! gen_try {
|
|
|
|
($expr:expr) => {
|
|
|
|
match $expr {
|
|
|
|
Ok(v) => v,
|
|
|
|
Err(e) => {
|
|
|
|
s.yield_(Err(anyhow::Error::from(e)));
|
|
|
|
done!();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
for line in line_reader.lines() {
|
|
|
|
let line = gen_try!(line);
|
|
|
|
let parts = line.split_whitespace();
|
|
|
|
|
|
|
|
for part in parts {
|
|
|
|
let int = gen_try!(part.parse::<f64>());
|
|
|
|
s.yield_(Ok(int));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
done!()
|
|
|
|
});
|
|
|
|
|
|
|
|
let mut data = Vec::with_capacity(width * height);
|
|
|
|
for mut chunk in &(numbers).chunks(3) {
|
|
|
|
let (r, g, b) = match chunk.next_tuple() {
|
|
|
|
Some(v) => v,
|
|
|
|
None => bail!("Not enough elements"),
|
|
|
|
};
|
|
|
|
|
|
|
|
let color = Color::new(r?, g?, b?);
|
|
|
|
data.push(color);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Image {
|
|
|
|
width,
|
|
|
|
height,
|
|
|
|
data,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-02-19 00:28:55 +00:00
|
|
|
/// Write the image in PPM format to a file.
|
|
|
|
pub fn write(&self, mut w: impl Write) -> Result<()> {
|
|
|
|
// Header
|
|
|
|
let header = format!("P3 {} {} 255\n", self.width, self.height);
|
|
|
|
w.write_all(header.as_bytes())?;
|
|
|
|
|
|
|
|
// Pixel data
|
|
|
|
assert_eq!(self.data.len(), self.width * self.height);
|
|
|
|
|
|
|
|
for pixel in self.data.iter() {
|
|
|
|
let pixel = pixel * 256.0;
|
|
|
|
let red = pixel.x as u8;
|
|
|
|
let green = pixel.y as u8;
|
|
|
|
let blue = pixel.z as u8;
|
|
|
|
let pixel = format!("{red} {green} {blue}\n");
|
|
|
|
w.write_all(pixel.as_bytes())?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|