diff --git a/Cargo.toml b/Cargo.toml index 93dc7a6..5e284d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ sdl2 = "0.32" simba = "0.1.2" csv = "1.1.3" clap = "2.33" +png = "0.16" [dependencies.nalgebra] version = "0.21" diff --git a/src/image.rs b/src/image.rs index 08a460e..ec624a7 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,4 +1,7 @@ use std::convert::TryInto; +use std::fs::File; +use std::io::BufWriter; +use std::path::Path; use nalgebra::{clamp, convert, Vector3}; @@ -60,6 +63,32 @@ impl ImageRgbU8 { 3 } + pub fn update(&mut self, start_row: usize, start_column: usize, image: &ImageRgbU8) { + assert!(start_column + image.width <= self.width); + assert!(start_row + image.height <= self.height); + for row in 0..image.height { + let source_start = image.calculate_index(row, 0); + let source_end = image.calculate_index(row, image.width - 1) + 3; + let destination_start = self.calculate_index(start_row + row, start_column); + let destination_end = + self.calculate_index(start_row + row, start_column + image.width - 1) + 3; + self.pixel_data[destination_start..destination_end] + .copy_from_slice(&image.pixel_data[source_start..source_end]); + } + } + + pub fn write_png(&self, filename: &Path) -> Result<(), std::io::Error> { + let file = File::create(filename)?; + let ref mut file_buffer = BufWriter::new(file); + + let mut encoder = png::Encoder::new(file_buffer, self.width as u32, self.height as u32); + encoder.set_color(png::ColorType::RGB); + encoder.set_depth(png::BitDepth::Eight); + let mut writer = encoder.write_header()?; + writer.write_image_data(self.pixel_data.as_slice())?; + Ok(()) + } + fn calculate_index(&self, row: usize, column: usize) -> usize { assert!(row < self.height && column < self.width); (((self.height - (row + 1)) * self.width + column) * Self::num_channels()) as usize diff --git a/src/main.rs b/src/main.rs index b68993d..5465aee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ use nalgebra::{Point3, Vector3}; use clap::Arg; -use std::path::Path; +use std::path::PathBuf; use std::sync::{mpsc, Arc}; use std::time::Duration; @@ -31,6 +31,7 @@ use vanrijn::util::{Tile, TileIterator}; struct CommandLineParameters { width: usize, height: usize, + output_file: Option, } fn parse_args() -> CommandLineParameters { @@ -46,11 +47,24 @@ fn parse_args() -> CommandLineParameters { .number_of_values(2) .required(true), ) + .arg( + Arg::with_name("output_png") + .long("out") + .value_name("FILENAME") + .help("Filename for output PNG.") + .takes_value(true) + .required(false), + ) .get_matches(); let mut size_iter = matches.values_of("size").unwrap(); let width = size_iter.next().unwrap().parse().unwrap(); let height = size_iter.next().unwrap().parse().unwrap(); - CommandLineParameters { width, height } + let output_file = matches.value_of_os("output_png").map(|f| PathBuf::from(f)); + CommandLineParameters { + width, + height, + output_file, + } } fn update_texture(tile: &Tile, image: &ImageRgbU8, texture: &mut Texture) { @@ -68,6 +82,10 @@ fn update_texture(tile: &Tile, image: &ImageRgbU8, texture: &mut Texture) { .expect("Couldn't update texture."); } +fn update_image(tile: &Tile, tile_image: &ImageRgbU8, image: &mut ImageRgbU8) { + image.update(tile.start_row, tile.start_column, tile_image); +} + fn init_canvas( image_width: usize, image_height: usize, @@ -90,6 +108,8 @@ pub fn main() -> Result<(), Box> { let image_width = parameters.width; let image_height = parameters.height; + let mut rendered_image = ImageRgbU8::new(image_width, image_height); + let (sdl_context, mut canvas) = init_canvas(image_width, image_height)?; let texture_creator = canvas.texture_creator(); @@ -165,6 +185,7 @@ pub fn main() -> Result<(), Box> { let mut tile_rx = Some(tile_rx); let worker_boss = std::thread::spawn(move || { + let end_tx = tile_tx.clone(); TileIterator::new(image_width as usize, image_height as usize, 32) .map(move |tile| (tile, tile_tx.clone())) .par_bridge() @@ -173,18 +194,25 @@ pub fn main() -> Result<(), Box> { // There's nothing we can do if this fails, and we're already // at the end of the function anyway, so just ignore result. - tx.send((tile, rendered_tile)).ok() + tx.send(Some((tile, rendered_tile))).ok() }); + end_tx.send(None).ok(); }); 'running: loop { if let Some(ref tile_rx) = tile_rx { - for (tile, tile_image) in tile_rx.try_iter() { - let mut tile_image_rgbu8 = ImageRgbU8::new(tile.width(), tile.height()); - ClampingToneMapper {}.apply_tone_mapping(&tile_image, &mut tile_image_rgbu8); - update_texture(&tile, &tile_image_rgbu8, &mut rendered_image_texture); - canvas.copy(&rendered_image_texture, None, None).unwrap(); - canvas.present(); + for message in tile_rx.try_iter() { + if let Some((tile, tile_image)) = message { + let mut tile_image_rgbu8 = ImageRgbU8::new(tile.width(), tile.height()); + ClampingToneMapper {}.apply_tone_mapping(&tile_image, &mut tile_image_rgbu8); + update_texture(&tile, &tile_image_rgbu8, &mut rendered_image_texture); + update_image(&tile, &tile_image_rgbu8, &mut rendered_image); + canvas.copy(&rendered_image_texture, None, None).unwrap(); + canvas.present(); + } else if let Some(image_filename) = parameters.output_file { + rendered_image.write_png(&image_filename)?; + break 'running; + } } }