From bbc1dfcf43dccf13b77aa4b36e9d106debb06f48 Mon Sep 17 00:00:00 2001 From: raffitz Date: Thu, 25 Mar 2021 20:16:36 +0000 Subject: [PATCH] Add PNG output of the charts --- Cargo.lock | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 2 + src/error.rs | 11 +++++- src/main.rs | 105 +++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 219 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 42ce965..b36cc03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,17 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" + [[package]] name = "ansi_term" version = "0.11.0" @@ -20,6 +32,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + [[package]] name = "beef" version = "0.5.0" @@ -32,6 +50,18 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "bytemuck" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bed57e2090563b83ba8f83366628ce535a7584c9afa4c9fc0612a03925c6df58" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" version = "2.33.3" @@ -47,12 +77,51 @@ dependencies = [ "vec_map", ] +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "fallible_collections" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74bebf0efe2e883c1619c455e3f1764333064694ebd5125d2faddabfb5963186" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "flate2" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +dependencies = [ + "ahash", +] + [[package]] name = "hermit-abi" version = "0.1.17" @@ -64,9 +133,21 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.80" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" +checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7" + +[[package]] +name = "lodepng" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c60778a2380faaea48875fd8d007bae8aea7786c47dd7a455c1c813c3a4c36d7" +dependencies = [ + "fallible_collections", + "flate2", + "libc", + "rgb", +] [[package]] name = "logos" @@ -92,6 +173,16 @@ dependencies = [ "utf8-ranges", ] +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + [[package]] name = "pomelo" version = "0.1.5" @@ -136,6 +227,15 @@ version = "0.6.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" +[[package]] +name = "rgb" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287f3c3f8236abb92d8b7e36797f19159df4b58f0a658cc3fb6dd3004b1f3bd3" +dependencies = [ + "bytemuck", +] + [[package]] name = "strsim" version = "0.8.0" @@ -191,8 +291,10 @@ name = "voxelmap" version = "0.1.0" dependencies = [ "clap", + "lodepng", "logos", "pomelo", + "rgb", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a58446e..7bb9d7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,5 @@ description = "Converts mathematical descriptions of objects to voxel maps" clap = "2.33" logos = "0.12" pomelo = "0.1.5" +lodepng = "3.4" +rgb = "0.8" diff --git a/src/error.rs b/src/error.rs index 1d6d236..13eed02 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,6 @@ -#[derive(Debug, PartialEq)] +use lodepng::Error as LPNGError; + +#[derive(Debug)] pub enum Error { ParserError, UnrecognisedBinaryOperator, @@ -11,6 +13,7 @@ pub enum Error { IllegalVarInBoundary, IllegarBoundedVar, UnboundedVar, + LodePNG(LPNGError), } impl From<()> for Error { @@ -18,3 +21,9 @@ impl From<()> for Error { Error::ParserError } } + +impl From for Error { + fn from(lode: LPNGError) -> Self { + Error::LodePNG(lode) + } +} diff --git a/src/main.rs b/src/main.rs index 33ea9f5..6376407 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; use logos::Logos; use pomelo::pomelo; +use rgb::*; use std::collections::HashMap; use std::fs; use std::io::{Error, ErrorKind, Read}; @@ -17,7 +18,8 @@ macro_rules! scale_message { fn io_error(err: Error, path: &str) -> String { match err.kind() { ErrorKind::NotFound => format!("{} not found", path), - ErrorKind::PermissionDenied => format!("Permission to read {} denied", path), + ErrorKind::PermissionDenied => format!("Permission to access {} denied", path), + ErrorKind::AlreadyExists => format!("{} already exists", path), _ => format!("Unexpected error accessing {}", path), } } @@ -266,6 +268,14 @@ fn main() -> Result<(), error::Error> { } }), ) + .arg( + Arg::with_name("offset") + .short("o") + .long("offset") + .help("Offset the computation by half a block") + .takes_value(false) + .multiple(false), + ) .arg( Arg::with_name("FILE") .help("The file describing the shape to map") @@ -278,12 +288,28 @@ fn main() -> Result<(), error::Error> { } }), ) + .arg( + Arg::with_name("OUTPUT_DIR") + .help("The folder where the output images will be stored") + .required(true) + .index(2) + .validator(move |path: String| -> Result<(), String> { + match fs::create_dir(&path) { + Ok(_) => Ok(()), + Err(error) => Err(io_error(error, &path)), + } + }), + ) .get_matches(); let scale = matches.value_of("scale").map(|s| s.parse::().unwrap()); let mut object_description = fs::File::open(matches.value_of("FILE").unwrap()).unwrap(); + let output_folder = matches.value_of("OUTPUT_DIR").unwrap(); + + let offset = matches.is_present("offset"); + let mut data = String::new(); if object_description.read_to_string(&mut data).is_ok() { @@ -337,7 +363,8 @@ fn main() -> Result<(), error::Error> { } } let var_arg = Some(&vars); - let min = limit.min.eval(&ident_arg, &var_arg)?.floor() as i64; + let min = + (limit.min.eval(&ident_arg, &var_arg)?.floor() as i64) - if offset { 1 } else { 0 }; let max = limit.max.eval(&ident_arg, &var_arg)?.ceil() as i64; match limit.var { 'x' => { @@ -375,6 +402,80 @@ fn main() -> Result<(), error::Error> { if unbounded { return Err(Error::UnboundedVar); } + + let min_x: i64 = min_x.unwrap(); + let max_x: i64 = max_x.unwrap(); + let min_y: i64 = min_y.unwrap(); + let max_y: i64 = max_y.unwrap(); + let min_z: i64 = min_z.unwrap(); + let max_z: i64 = max_z.unwrap(); + + let width: i64 = 1 + max_x - min_x; + let height: i64 = 1 + max_y - min_y; + + let pix_width: usize = 6 * (width as usize) + 1; + let pix_height: usize = 6 * (height as usize) + 1; + let pix_size: usize = pix_width * pix_height; + + for z in min_z..=max_z { + let name = format! {"{}/layer{:04}.png",output_folder,1 + z - min_z}; + + let filled_in = RGBA8::new(0, 0, 0, 255); // Black + let empty = RGBA8::new(255, 255, 255, 255); // White + let multiple_filled_in = RGBA8::new(0, 0, 255, 255); // Blue + let multiple_empty = RGBA8::new(255, 255, 0, 255); // Yellow + let grid = RGBA8::new(255, 128, 128, 255); // Coral (Grid) + + let mut pixels: Vec = Vec::with_capacity(pix_size); + + pixels.resize(pix_size, grid); + + for y in min_y..=max_y { + let square_start_y = 6 * (y - min_y) as usize; + let grid_y = y.abs() % 10 == 0; + for x in min_x..=max_x { + let x_f: f64 = (x as f64) + if offset { 0.5_f64 } else { 0_f64 }; + let y_f: f64 = (y as f64) + if offset { 0.5_f64 } else { 0_f64 }; + let z_f: f64 = (z as f64) + if offset { 0.5_f64 } else { 0_f64 }; + let rho: f64 = (x_f.powi(2) + y_f.powi(2)).sqrt(); + let phi: f64 = y_f.atan2(x_f); + let r: f64 = (z_f.powi(2) + rho.powi(2)).sqrt(); + let tht: f64 = (z_f / rho).atan(); + + vars.insert('x', x_f); + vars.insert('y', y_f); + vars.insert('z', z_f); + vars.insert('ρ', rho); + vars.insert('φ', phi); + vars.insert('r', r); + vars.insert('θ', tht); + + let var_arg = Some(&vars); + + let square_start_x = 6 * (x - min_x) as usize; + + let grid = if grid_y { true } else { x.abs() % 10 == 0 }; + let is_filled = tree.eval(&ident_arg, &var_arg)?; + + let color = match (is_filled, grid) { + (false, false) => empty, + (false, true) => multiple_empty, + (true, false) => filled_in, + (true, true) => multiple_filled_in, + }; + + for pix_x in 0..5 { + for pix_y in 0..5 { + pixels[(1 + square_start_y + pix_y) * pix_width + + 1 + + square_start_x + + pix_x] = color; + } + } + } + } + lodepng::encode32_file(name, &pixels, pix_width, pix_height)?; + } } Ok(())