diff --git a/Cargo.lock b/Cargo.lock index 6b1e75c..596c87f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,6 +57,15 @@ dependencies = [ "zerocopy 0.7.35", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "aligned-vec" version = "0.5.0" @@ -313,6 +322,17 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -515,6 +535,12 @@ dependencies = [ "wayland-client", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.2.17" @@ -560,6 +586,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "bitflags 1.3.2", + "textwrap", + "unicode-width", +] + [[package]] name = "clipboard-win" version = "5.4.0" @@ -780,6 +817,42 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" +dependencies = [ + "atty", + "cast", + "clap", + "criterion-plot", + "csv", + "itertools 0.10.5", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_cbor", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" +dependencies = [ + "cast", + "itertools 0.10.5", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -821,6 +894,27 @@ dependencies = [ "typenum", ] +[[package]] +name = "csv" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" +dependencies = [ + "memchr", +] + [[package]] name = "ctor-lite" version = "0.1.0" @@ -1076,7 +1170,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" dependencies = [ "bit_field", - "half", + "half 2.5.0", "lebe", "miniz_oxide", "rayon-core", @@ -1442,6 +1536,12 @@ dependencies = [ "svg_fmt", ] +[[package]] +name = "half" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" + [[package]] name = "half" version = "2.5.0" @@ -1498,6 +1598,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.3.9" @@ -1594,7 +1703,7 @@ dependencies = [ "bitflags 2.9.0", "bytemuck", "cosmic-text", - "half", + "half 2.5.0", "iced_core", "iced_futures", "image 0.24.9", @@ -1761,9 +1870,10 @@ dependencies = [ name = "imflow" version = "0.1.0" dependencies = [ + "criterion", "iced", "image 0.25.6", - "itertools", + "itertools 0.12.1", "memmap2", "tokio", "tracing-subscriber", @@ -1806,6 +1916,15 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.12.1" @@ -1815,6 +1934,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "jni" version = "0.21.1" @@ -2695,6 +2820,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "orbclient" version = "0.3.48" @@ -2920,6 +3051,34 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "png" version = "0.17.16" @@ -3095,7 +3254,7 @@ dependencies = [ "built", "cfg-if", "interpolate_name", - "itertools", + "itertools 0.12.1", "libc", "libfuzzer-sys", "log", @@ -3205,6 +3364,35 @@ dependencies = [ "thiserror", ] +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "renderdoc-sys" version = "1.1.0" @@ -3300,6 +3488,12 @@ dependencies = [ "unicode-script", ] +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "same-file" version = "1.0.6" @@ -3349,6 +3543,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half 1.8.3", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.219" @@ -3360,6 +3564,18 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.20" @@ -3657,6 +3873,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -3737,6 +3962,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.9.0" diff --git a/Cargo.toml b/Cargo.toml index 456f2b7..36ff2ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,10 @@ tracing-subscriber = "0.3" zune-image = {version = "0.4.15", features = ["all"]} [profile.dev.package.zune-jpeg] opt-level = 3 + +[dev-dependencies] +criterion = "0.3" + +[[bench]] +name = "image_load" +harness = false diff --git a/benches/image_load.rs b/benches/image_load.rs new file mode 100644 index 0000000..3cde65b --- /dev/null +++ b/benches/image_load.rs @@ -0,0 +1,19 @@ +#![allow(unused)] + +use std::iter; + +use criterion::BenchmarkId; +use criterion::{Criterion, black_box, criterion_group, criterion_main}; +use imflow::image::{Approach, load_thumbnail}; +const PATH: &str = "test_images/20240811-194516_DSC02274.JPG"; + +pub fn criterion_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("from_elem"); + group.bench_function("mmap", |b| b.iter(|| load_thumbnail(PATH, Approach::Mmap))); + group.bench_function("path", |b| b.iter(|| load_thumbnail(PATH, Approach::Path))); + group.bench_function("iced", |b| b.iter(|| load_thumbnail(PATH, Approach::Iced))); + group.finish(); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/src/image.rs b/src/image.rs new file mode 100644 index 0000000..dbaa325 --- /dev/null +++ b/src/image.rs @@ -0,0 +1,77 @@ +use zune_image::codecs::qoi::zune_core::options::DecoderOptions; + +use std::fs::File; + +pub enum Approach { + Mmap, + Path, + Iced, +} +fn convert_rgb_to_rgba(rgb_data: Vec>) -> Vec { + let r_channel = &rgb_data[0]; + + let num_pixels = r_channel.len() / 3; + let mut rgba_data: Vec = Vec::with_capacity(num_pixels * 4); + + for i in 0..num_pixels { + rgba_data.push(r_channel[i * 3]); + rgba_data.push(r_channel[i * 3 + 1]); + rgba_data.push(r_channel[i * 3 + 2]); + rgba_data.push(255); // Fully opaque Alpha value + } + + rgba_data +} +pub fn load_thumbnail( + path: &str, + approach: Approach, +) -> Result { + match approach { + Approach::Mmap => { + let file = File::open(path).map_err(|e| e.to_string())?; + let mmap = unsafe { memmap2::Mmap::map(&file) }.map_err(|e| e.to_string())?; + println!("mapped file"); + let img = zune_image::image::Image::read(&*mmap, DecoderOptions::default()).unwrap(); + let width = img.dimensions().0 as u32; + let height = img.dimensions().1 as u32; + println!("loaded"); + let flat = img.flatten_to_u8(); + println!("flattened"); + let rgba = convert_rgb_to_rgba(flat); + println!("rgbad"); + let conv = iced::widget::image::Handle::from_rgba(width, height, rgba); + println!("iced"); + + Ok(conv) + } + Approach::Path => { + let img = zune_image::image::Image::open_with_options(path, DecoderOptions::default()) + .unwrap(); + let width = img.dimensions().0 as u32; + let height = img.dimensions().1 as u32; + println!("loaded"); + let flat = img.flatten_to_u8(); + println!("flattened"); + let rgba = convert_rgb_to_rgba(flat); + println!("rgbad"); + let conv = iced::widget::image::Handle::from_rgba(width, height, rgba); + println!("iced"); + + Ok(conv) + } + Approach::Iced => { + let file = File::open(path).map_err(|e| e.to_string())?; + let mmap = unsafe { memmap2::Mmap::map(&file) }.map_err(|e| e.to_string())?; + let img = image::load_from_memory(&mmap).map_err(|e| e.to_string())?; + let width = img.width(); + let height = img.height(); + println!("loaded"); + let rgba = img.into_rgba8().into_raw(); + println!("rgbad"); + let conv = iced::widget::image::Handle::from_rgba(width, height, rgba); + println!("iced"); + + Ok(conv) + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..14995d4 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +pub mod image; diff --git a/src/main.rs b/src/main.rs index 75c678f..4bbd952 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ // use grid::Grid; use std::collections::HashMap; -use std::fs::{self, File}; +use std::fs::{self}; use std::io::Read; use std::path::{Path, PathBuf}; use std::time::{self, Duration}; @@ -15,9 +15,10 @@ use iced::widget::image::FilterMethod; use iced::widget::{ Column, Container, button, center, checkbox, column, container, pick_list, row, slider, text, }; -use iced::{Center, Element, Fill, Length, Size, Subscription, Task, Theme}; +use iced::{Center, Element, Fill, Length, Size, Subscription, Task, Theme, keyboard}; use image::{self, DynamicImage, EncodableLayout, ImageBuffer, ImageReader}; -use itertools::Itertools; + +use imflow::image::{Approach, load_thumbnail}; use zune_image::codecs::qoi::zune_core::options::DecoderOptions; // for general image operations // use image::io::Reader as ImageReader; // specifically for Reader @@ -144,7 +145,7 @@ impl GameOfLife { if !self.loaded_images.contains_key(&path.to_path_buf()) { self.loaded_images.insert( path.to_path_buf(), - load_thumbnail(path.to_str().unwrap()).unwrap(), + load_thumbnail(path.to_str().unwrap(), Approach::Iced).unwrap(), ); } } @@ -154,12 +155,11 @@ impl GameOfLife { } fn subscription(&self) -> Subscription { - if self.is_playing { - iced::time::every(Duration::from_millis(1000 / self.speed as u64)) - .map(|_| Message::Tick) - } else { - Subscription::none() - } + keyboard::on_key_press(|key, _modifiers| match key { + keyboard::Key::Named(keyboard::key::Named::ArrowRight) => Some(Message::Next(1)), + keyboard::Key::Named(keyboard::key::Named::ArrowLeft) => Some(Message::Next(-1)), + _ => None, + }) } fn view(&self) -> Element<'_, Message> { @@ -278,49 +278,3 @@ fn view_controls<'a>( .align_y(Center) .into() } - -fn load_thumbnail(path: &str) -> Result { - // let file = File::open(path).map_err(|e| e.to_string())?; - // let mmap = unsafe { memmap2::Mmap::map(&file) }.map_err(|e| e.to_string())?; - // println!("mapped file"); - // let img = zune_image::image::Image::read(&*mmap, DecoderOptions::default()).unwrap(); - let img = zune_image::image::Image::open_with_options(path, DecoderOptions::default()).unwrap(); - // let img = image::load_from_memory(&mmap) - // .map_err(|e| e.to_string())? - // .decode() - // .map_err(|e| e.to_string())?; - - // let thumbnail = img.thumbnail(128, 128); - let width = img.dimensions().0 as u32; - let height = img.dimensions().1 as u32; - println!("loaded"); - let flat = img.flatten_to_u8(); - println!("flattened"); - let rgba = convert_rgb_to_rgba(flat); - println!("rgbad"); - // let leaked_slice = Box::leak(rgba[0].clone().into_boxed_slice()); - // let slice = rgba[0].as_slice().clone(); - let conv = iced::widget::image::Handle::from_rgba( - width, height, // leaked_slice.as_bytes(), - rgba, - ); - println!("iced"); - - Ok((conv)) -} - -fn convert_rgb_to_rgba(rgb_data: Vec>) -> Vec { - let r_channel = &rgb_data[0]; - - let num_pixels = r_channel.len() / 3; - let mut rgba_data: Vec = Vec::with_capacity(num_pixels * 4); - - for i in 0..num_pixels { - rgba_data.push(r_channel[i * 3]); - rgba_data.push(r_channel[i * 3 + 1]); - rgba_data.push(r_channel[i * 3 + 2]); - rgba_data.push(255); // Fully opaque Alpha value - } - - rgba_data -}