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 2fb99f0..5f867f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,16 @@ package.repository = "https://codeberg.org/p6nj/bent" package.keywords = ["databending", "data-bending", "bending", "bend", "art"] [workspace.dependencies] +fundsp = "0.18.2" +image = "0.25.1" +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" +derive-new = "0.6.0" +infer = "0.16.0" +indicatif = { version = "0.17.8", features = ["rayon"] } +getset = "0.1.2" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..68a49da --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/bent-funny-zone/Cargo.toml b/bent-funny-zone/Cargo.toml index aec3e0f..1d7e5a2 100644 --- a/bent-funny-zone/Cargo.toml +++ b/bent-funny-zone/Cargo.toml @@ -11,7 +11,14 @@ keywords.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = "1.0.86" -bmp = "0.5.0" -dasp_sample = "0.11.0" -fundsp = { version = "0.18.1", default-features = false, features = ["std"] } +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 = { workspace = true } +infer = { workspace = true } +indicatif = { workspace = true } diff --git a/bent-funny-zone/src/main.rs b/bent-funny-zone/src/main.rs index a4f4ea6..22e1e15 100644 --- a/bent-funny-zone/src/main.rs +++ b/bent-funny-zone/src/main.rs @@ -1,75 +1,131 @@ -use std::fs::File; +use std::path::PathBuf; -use anyhow::Result; -use bmp::{open, Pixel}; -use dasp_sample::{FromSample, Sample, ToSample, U24}; +use anyhow::{Context, Result}; +use bingus::rawdata::RawData; +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, ImageFormat, RgbImage}; +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()) + .add_filter( + "images", + &ImageFormat::all() + .map(ImageFormat::extensions_str) + .flatten() + .collect::>(), + ) + .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 bmp = open("bmp/bigsample.bmp")?; - for y in 0..bmp.get_height() { - for x in 0..bmp.get_width() { - if x > 100 && x < 300 { - bmp.set_pixel( - x, - y, - sample_to_rgb(rgb_to_sample::(bmp.get_pixel(x, y))), - ); - } - } - } - bmp.to_writer(&mut File::create("bmp/out.bmp")?)?; + let dp = DataPath::new(Files::prompt()?); + let img = RawData::::new( + ImageReader::open(dp.files.input)? + .decode() + .context("can't use this picture")? + .into_rgb8(), + ); + let processed = RawData::::new( + RgbImage::from_raw( + img.width(), + img.height(), + img.par_chunks_exact(3) + .progress_count((img.width() * img.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(U24::new_unchecked) + .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!( + // bytes, + // bytes + // .chunks_exact(3) + // .map(|px: &[u8]| f64::from_ne_bytes(array::from_fn(|i| px[i % px.len()]))) + // .flat_map(|px: f64| -> [u8; 3] { + // let px = px.to_ne_bytes(); + // array::from_fn(move |i| px[i]) + // }) + // .collect::>() + // ); Ok(()) } -fn rgb_to_sample>(pix: Pixel) -> T { - (((U24::from(pix.r)) << 16.into()) | (U24::from(pix.g) << 8.into()) | U24::from(pix.b)) - .to_sample() -} - -fn sample_to_rgb>(sample: T) -> Pixel { - let rgb: U24 = sample.to_sample(); - let rgo = ((rgb) >> 8.into()) << 8.into(); - let roo = ((rgo) >> 16.into()) << 16.into(); - Pixel::new( - ((roo) >> 16.into()).inner() as u8, - ((rgo - roo) >> 8.into()).inner() as u8, - (rgb - rgo).inner() as u8, - ) -} - -#[cfg(test)] -mod tests { - use bmp::Pixel; - use dasp_sample::{Sample, I24, I48, U24, U48}; - - use super::{rgb_to_sample, sample_to_rgb}; - - #[test] - fn sample_convert_consistency() { - let original = U24::from(42); - assert_eq!(original.clone(), original.to_sample::().to_sample()) - } - - #[test] - fn rgb2s2rgb_type_consistency() { - let pix = Pixel::new(45, 18, 143); - assert_eq!( - sample_to_rgb(rgb_to_sample::(pix)), - sample_to_rgb(rgb_to_sample::(pix)) - ); - assert_eq!( - sample_to_rgb(rgb_to_sample::(pix)), - sample_to_rgb(rgb_to_sample::(pix)) - ); - assert_eq!( - sample_to_rgb(rgb_to_sample::(pix)), - sample_to_rgb(rgb_to_sample::(pix)) - ); - } - - #[test] - fn rgb2s2rgb() { - let pix = Pixel::new(45, 18, 143); - assert_eq!(pix, (sample_to_rgb(rgb_to_sample::(pix)))); - } +fn working_dir() -> PathBuf { + picture_dir() + .or(download_dir()) + .or(home_dir()) + .unwrap_or("/".into()) } diff --git a/bent/Cargo.toml b/bent/Cargo.toml index 7c3fd50..e00aed4 100644 --- a/bent/Cargo.toml +++ b/bent/Cargo.toml @@ -10,4 +10,11 @@ keywords.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["cdylib"] + [dependencies] +wasm-bindgen = "0.2" + +[profile.release] +lto = true diff --git a/bent/LICENSE b/bent/LICENSE new file mode 100644 index 0000000..68a49da --- /dev/null +++ b/bent/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/bent/assets/bent.svg b/bent/assets/bent.svg new file mode 100644 index 0000000..60b8b98 --- /dev/null +++ b/bent/assets/bent.svg @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/bent/assets/path-generator.py b/bent/assets/path-generator.py new file mode 100644 index 0000000..1af8d68 --- /dev/null +++ b/bent/assets/path-generator.py @@ -0,0 +1,15 @@ +p = lambda x: print(x, end=" ") + + +def line(coords: tuple[tuple[int]]): + p(f"M {coords[0][0]} {coords[0][1]} L {coords[1][0]} {coords[1][1]}") + + +n = 100 +line(((0, 0), (n, n))) +line(((n, 0), (0, n))) +for i in range(1, n): + line(((i, 0), (n - i, n))) + line(((0, i), (n, n - i))) + +print() diff --git a/bent/index.html b/bent/index.html new file mode 100644 index 0000000..6a253de --- /dev/null +++ b/bent/index.html @@ -0,0 +1,36 @@ + + + + + + BENT + + + + + + + +
+

BENT

+
+ +
+

+ + +

+ + +
+ + + \ No newline at end of file diff --git a/bent/src/js.rs b/bent/src/js.rs new file mode 100644 index 0000000..a78e4b1 --- /dev/null +++ b/bent/src/js.rs @@ -0,0 +1,7 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console)] + pub fn log(a: &str); +} diff --git a/bent/src/lib.rs b/bent/src/lib.rs new file mode 100644 index 0000000..d26acc3 --- /dev/null +++ b/bent/src/lib.rs @@ -0,0 +1,10 @@ +use wasm_bindgen::prelude::*; +#[allow(unused_macros)] +#[macro_use] +pub mod macros; +pub mod js; + +#[wasm_bindgen] +pub fn greet(name: &str) { + println!("Hello, {name}!"); +} diff --git a/bent/src/macros.rs b/bent/src/macros.rs new file mode 100644 index 0000000..7d9c941 --- /dev/null +++ b/bent/src/macros.rs @@ -0,0 +1,27 @@ +macro_rules! println { + ($($t:tt)*) => (crate::js::log(&format_args!($($t)*).to_string())) +} + +macro_rules! dbg { + // NOTE: We cannot use `concat!` to make a static string as a format argument + // of `eprintln!` because `file!` could contain a `{` or + // `$val` expression could be a block (`{ .. }`), in which case the `eprintln!` + // will be malformed. + () => { + println!("[{}:{}:{}]", file!(), line!(), column!()) + }; + ($val:expr $(,)?) => { + // Use of `match` here is intentional because it affects the lifetimes + // of temporaries - https://stackoverflow.com/a/48732525/1063961 + match $val { + tmp => { + println!("[{}:{}:{}] {} = {:#?}", + file!(), line!(), column!(), stringify!($val), &tmp); + tmp + } + } + }; + ($($val:expr),+ $(,)?) => { + ($(dbg!($val)),+,) + }; +} diff --git a/bent/src/main.rs b/bent/src/main.rs deleted file mode 100644 index e7a11a9..0000000 --- a/bent/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/bingus/Cargo.toml b/bingus/Cargo.toml index 8027b64..5f28930 100644 --- a/bingus/Cargo.toml +++ b/bingus/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bingus" -version = "0.1.0" +version = "0.1.2" edition.workspace = true license.workspace = true description.workspace = true @@ -10,4 +10,4 @@ keywords.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dasp_sample = { workspace = true } +derive-new = { workspace = true } diff --git a/bingus/src/lib.rs b/bingus/src/lib.rs index 51ab99d..e295326 100644 --- a/bingus/src/lib.rs +++ b/bingus/src/lib.rs @@ -1,43 +1,2 @@ -use dasp_sample::{FromSample, Sample, U24, U48}; - -pub trait IntoSample -where - Self: Sized, -{ - fn _prepare(self) -> F; - fn into_sample>(self) -> S { - Sample::from_sample(self._prepare()) - } -} - -impl IntoSample for u8 { - fn _prepare(self) -> u8 { - self - } -} - -impl IntoSample for [u8; 2] { - fn _prepare(self) -> u16 { - ((self[0]._prepare() as u16) << 8) | self[1] as u16 - } -} - -impl IntoSample for [u8; 3] { - fn _prepare(self) -> U24 { - (U24::from([self[0], self[1]]._prepare()) << 8.into()) | U24::from(self[2]) - } -} - -impl IntoSample for [u8; 4] { - fn _prepare(self) -> u32 { - (([self[0], self[1], self[2]]._prepare().inner() as u32) << 8) | self[3] as u32 - } -} - -impl IntoSample for [u8; 6] { - fn _prepare(self) -> U48 { - ((U48::from([self[0], self[1], self[2], self[3]]._prepare()) << 16u8.into()) - | (U48::from(self[4]) << 8u8.into())) - | U48::from(self[5]) - } -} +#![forbid(unused_crate_dependencies)] +pub mod rawdata; diff --git a/bingus/src/rawdata.rs b/bingus/src/rawdata.rs new file mode 100644 index 0000000..d0ac87b --- /dev/null +++ b/bingus/src/rawdata.rs @@ -0,0 +1,36 @@ +use std::ops::Deref; + +use derive_new::new; + +pub type Bytes = [u8]; + +#[derive(new)] +pub struct RawData>(D); + +impl From> for Vec +where + D: Deref, +{ + fn from(value: RawData) -> Self { + value.to_owned() + } +} + +impl Clone for RawData +where + D: Clone + Deref, +{ + fn clone(&self) -> Self { + Self(self.deref().clone()) + } +} + +impl Deref for RawData +where + D: Deref, +{ + type Target = D; + fn deref(&self) -> &Self::Target { + &self.0 + } +}