file picker, progress bar, perf enhancements
This commit is contained in:
parent
5c2607b574
commit
5d39e227b6
4 changed files with 114 additions and 22 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1,3 @@
|
||||||
/target
|
/target
|
||||||
*.lock
|
*.lock
|
||||||
bmp/out.bmp
|
bmp/out.*
|
||||||
|
|
|
@ -14,3 +14,7 @@ image = "0.24.9"
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
bingus = { version = "0.1.0", path = "bingus" }
|
bingus = { version = "0.1.0", path = "bingus" }
|
||||||
num-traits = "0.2.19"
|
num-traits = "0.2.19"
|
||||||
|
dasp_sample = "0.11.0"
|
||||||
|
rayon = "1.10.0"
|
||||||
|
dirs = "5.0.1"
|
||||||
|
rfd = "0.14.1"
|
||||||
|
|
|
@ -15,3 +15,9 @@ image = { workspace = true }
|
||||||
fundsp = { workspace = true }
|
fundsp = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
bingus = { 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"] }
|
||||||
|
|
|
@ -1,27 +1,102 @@
|
||||||
use std::array;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{Context, Result};
|
||||||
use fundsp::{
|
use dasp_sample::{Sample, U24};
|
||||||
hacker::{bell_hz, pipei, U12},
|
use derive_new::new;
|
||||||
math::db_amp,
|
use dirs::{download_dir, home_dir, picture_dir};
|
||||||
};
|
use fundsp::math::uparc;
|
||||||
use image::io::Reader as ImageReader;
|
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<Self> {
|
||||||
|
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<PathBuf> {
|
||||||
|
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<Self> {
|
||||||
|
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<()> {
|
fn main() -> Result<()> {
|
||||||
let mut img = ImageReader::open("bmp/bigsample.bmp")?.decode()?.to_rgb8();
|
let dp = DataPath::new(Files::prompt()?);
|
||||||
let mut equalizer =
|
let img = ImageReader::open(dp.files.input)?
|
||||||
pipei::<U12, _, _>(|i| bell_hz(1000.0 + 1000.0 * i as f32, 1.0, db_amp(0.0)));
|
.decode()
|
||||||
img.to_vec()
|
.context("can't use this picture")?
|
||||||
.chunks_exact(3)
|
.to_rgb8();
|
||||||
.map(|px: &[u8]| f32::from_ne_bytes(array::from_fn(|i| px[i % px.len()])))
|
let (width, height) = (img.width(), img.height());
|
||||||
.map(|x| equalizer.filter_mono(x))
|
let processed: ImageBuffer<Rgb<u8>, Vec<u8>> = ImageBuffer::from_raw(
|
||||||
.flat_map(|px| -> [u8; 3] {
|
width,
|
||||||
let px = px.to_ne_bytes();
|
height,
|
||||||
array::from_fn(move |i| px[i])
|
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,
|
||||||
|
]
|
||||||
})
|
})
|
||||||
.zip(img.as_mut())
|
.collect(),
|
||||||
.for_each(|(filtered, original)| *original = filtered);
|
)
|
||||||
img.save("bmp/out.bmp")?;
|
.unwrap();
|
||||||
|
processed.save(dp.files.output)?;
|
||||||
|
|
||||||
// let bytes: Vec<u8> = (1u8..7).into_iter().collect();
|
// let bytes: Vec<u8> = (1u8..7).into_iter().collect();
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
|
@ -37,3 +112,10 @@ fn main() -> Result<()> {
|
||||||
// );
|
// );
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn working_dir() -> PathBuf {
|
||||||
|
picture_dir()
|
||||||
|
.or(download_dir())
|
||||||
|
.or(home_dir())
|
||||||
|
.unwrap_or("/".into())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue