diff --git a/.gitignore b/.gitignore index 55d94da..249bc38 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ /target *.lock -bmp/out.bmp +bmp/out.* diff --git a/Cargo.toml b/Cargo.toml index 42856bd..704f945 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,7 @@ image = "0.24.9" anyhow = "1.0.86" bingus = { version = "0.1.0", path = "bingus" } num-traits = "0.2.19" +dasp_sample = "0.11.0" +rayon = "1.10.0" +dirs = "5.0.1" +rfd = "0.14.1" diff --git a/bent-funny-zone/Cargo.toml b/bent-funny-zone/Cargo.toml index ccc2cda..6306660 100644 --- a/bent-funny-zone/Cargo.toml +++ b/bent-funny-zone/Cargo.toml @@ -15,3 +15,9 @@ image = { workspace = true } fundsp = { workspace = true } anyhow = { workspace = true } bingus = { workspace = true } +dasp_sample = { workspace = true } +rayon = { workspace = true } +dirs = { workspace = true } +rfd = { workspace = true } +derive-new = "0.6.0" +indicatif = { version = "0.17.8", features = ["rayon"] } diff --git a/bent-funny-zone/src/main.rs b/bent-funny-zone/src/main.rs index 16427a5..e04dc8b 100644 --- a/bent-funny-zone/src/main.rs +++ b/bent-funny-zone/src/main.rs @@ -1,27 +1,102 @@ -use std::array; +use std::path::PathBuf; -use anyhow::Result; -use fundsp::{ - hacker::{bell_hz, pipei, U12}, - math::db_amp, -}; -use image::io::Reader as ImageReader; +use anyhow::{Context, Result}; +use dasp_sample::{Sample, U24}; +use derive_new::new; +use dirs::{download_dir, home_dir, picture_dir}; +use fundsp::math::uparc; +use image::{io::Reader as ImageReader, ImageBuffer, ImageFormat, Rgb}; +use indicatif::{ParallelProgressIterator, ProgressStyle}; +use rayon::{iter::ParallelIterator, slice::ParallelSlice}; +use rfd::FileDialog; + +struct Files { + input: PathBuf, + output: PathBuf, +} + +#[derive(new)] +struct DataPath { + files: Files, +} + +impl Files { + fn try_new(input: PathBuf, output: PathBuf) -> Result { + Ok(Files { + input: Self::verify_image_file(input, "input")?, + output: Self::verify_image_file(output, "output")?, + }) + } + fn verify_image_file(path: PathBuf, label: &'static str) -> Result { + ImageFormat::from_path(&path).with_context(|| { + format!("your {label} image must have a supported extension from this list:") + + &ImageFormat::all() + .map(|format| { + format!( + "\n\t{:?} ({})", + format, + format + .extensions_str() + .iter() + .fold(String::new(), |acc, x| acc + &format!(".{x}, ")) + .strip_suffix(", ") + .unwrap() + ) + }) + .reduce(|acc, x| acc + &x) + .unwrap() + })?; + Ok(path) + } + fn prompt() -> Result { + Files::try_new( + FileDialog::new() + .set_title("Input image") + .set_directory(working_dir()) + .pick_file() + .context("no input image selected")?, + FileDialog::new() + .set_title("Output image") + .set_directory(working_dir()) + .save_file() + .context("no output filename provided")?, + ) + } +} fn main() -> Result<()> { - let mut img = ImageReader::open("bmp/bigsample.bmp")?.decode()?.to_rgb8(); - let mut equalizer = - pipei::(|i| bell_hz(1000.0 + 1000.0 * i as f32, 1.0, db_amp(0.0))); - img.to_vec() - .chunks_exact(3) - .map(|px: &[u8]| f32::from_ne_bytes(array::from_fn(|i| px[i % px.len()]))) - .map(|x| equalizer.filter_mono(x)) - .flat_map(|px| -> [u8; 3] { - let px = px.to_ne_bytes(); - array::from_fn(move |i| px[i]) - }) - .zip(img.as_mut()) - .for_each(|(filtered, original)| *original = filtered); - img.save("bmp/out.bmp")?; + let dp = DataPath::new(Files::prompt()?); + let img = ImageReader::open(dp.files.input)? + .decode() + .context("can't use this picture")? + .to_rgb8(); + let (width, height) = (img.width(), img.height()); + let processed: ImageBuffer, Vec> = ImageBuffer::from_raw( + width, + height, + img.into_raw() + .par_chunks_exact(3) + .progress_count((width * height).into()) + .with_style(ProgressStyle::with_template( + "[{eta}] {bar:40.green/red} {pos}/{len} pixels", + )?) + .map(|px: &[u8]| (px[2] as i32) | ((px[1] as i32) << 8) | ((px[0] as i32) << 16)) + .map(|px| U24::new_unchecked(px)) + .map(|x| uparc(x.to_sample())) + .flat_map(|sample: f32| { + let rgb: U24 = sample.to_sample(); + let rgo = ((rgb) >> 8.into()) << 8.into(); + let roo = ((rgo) >> 16.into()) << 16.into(); + [ + ((roo) >> 16.into()).inner() as u8, + ((rgo - roo) >> 8.into()).inner() as u8, + (rgb - rgo).inner() as u8, + ] + }) + .collect(), + ) + .unwrap(); + processed.save(dp.files.output)?; // let bytes: Vec = (1u8..7).into_iter().collect(); // assert_eq!( @@ -37,3 +112,10 @@ fn main() -> Result<()> { // ); Ok(()) } + +fn working_dir() -> PathBuf { + picture_dir() + .or(download_dir()) + .or(home_dir()) + .unwrap_or("/".into()) +}