WIP thumbnails
This commit is contained in:
parent
24d71027f5
commit
21ec2db40f
22
Cargo.lock
generated
22
Cargo.lock
generated
@ -1591,6 +1591,16 @@ dependencies = [
|
||||
"wasi 0.14.2+wasi-0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gexiv2-sys"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4edf7a47be383873c52eb34426723c7c9b040f9e58cf5088f2253cef149daf1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gif"
|
||||
version = "0.13.1"
|
||||
@ -2106,6 +2116,7 @@ dependencies = [
|
||||
"memmap2",
|
||||
"minifb",
|
||||
"pollster",
|
||||
"rexiv2",
|
||||
"threadpool",
|
||||
"tokio",
|
||||
"tracing-subscriber",
|
||||
@ -3792,6 +3803,17 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
|
||||
|
||||
[[package]]
|
||||
name = "rexiv2"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11ecb9dccad43fea1bf58ea7bb8e34614446bea8aa00f78add6624d7ee26fb87"
|
||||
dependencies = [
|
||||
"gexiv2-sys",
|
||||
"libc",
|
||||
"num-rational",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rgb"
|
||||
version = "0.8.50"
|
||||
|
@ -13,6 +13,7 @@ itertools = "0.12"
|
||||
memmap2 = "0.9.5"
|
||||
minifb = "0.28.0"
|
||||
pollster = "0.4.0"
|
||||
rexiv2 = "0.10.0"
|
||||
threadpool = "1.8.1"
|
||||
# rustc-hash.workspace = true
|
||||
tokio = { version = "1.44.1", features = ["sync"] }
|
||||
@ -20,9 +21,6 @@ tracing-subscriber = "0.3"
|
||||
wgpu = "24.0.3"
|
||||
winit = "0.30.9"
|
||||
zune-image = {version = "0.4.15", features = ["all"]}
|
||||
|
||||
[profile.dev.package.zune-jpeg]
|
||||
opt-level = 3
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
debug = false
|
||||
|
114
src/image.rs
114
src/image.rs
@ -1,13 +1,18 @@
|
||||
use iced::widget::image::Handle;
|
||||
use iced::widget::image::Image as IcedImage;
|
||||
// use image::codecs::jpeg::JpegDecoder;
|
||||
// use image::codecs::jpeg::JpegDecoder;
|
||||
use image::DynamicImage;
|
||||
use image::ImageReader;
|
||||
use itertools::Itertools;
|
||||
use memmap2::Mmap;
|
||||
use zune_image::codecs::jpeg::JpegDecoder;
|
||||
use zune_image::codecs::qoi::zune_core::options::DecoderOptions;
|
||||
use zune_image::image::Image as ZuneImage;
|
||||
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::fs::read;
|
||||
use std::io;
|
||||
use std::io::Read;
|
||||
use std::ops::Deref;
|
||||
@ -53,6 +58,12 @@ pub fn read_zune_image(mmap: &[u8]) -> Result<ZuneImage, String> {
|
||||
}
|
||||
|
||||
pub fn read_zune_image_path(path: &str) -> ZuneImage {
|
||||
// let file = File::open(path).unwrap();
|
||||
// let file_contents = read(path).unwrap();
|
||||
// let mmap = map_file_path(path.into()).unwrap();
|
||||
// let decoder = JpegDecoder::new(&file_contents);
|
||||
// decoder.decode_into()
|
||||
// ZuneImage::read(mmap, DecoderOptions::new_fast()).map_err(|e| e.to_string())
|
||||
zune_image::image::Image::open_with_options(path, DecoderOptions::new_fast()).unwrap()
|
||||
}
|
||||
|
||||
@ -134,48 +145,67 @@ pub fn load_thumbnail(
|
||||
|
||||
pub fn load_image_argb(path: PathBuf) -> ImflowImageBuffer {
|
||||
let total_start = Instant::now();
|
||||
|
||||
|
||||
// Stage 1: Memory map the file
|
||||
let stage1_start = Instant::now();
|
||||
let mmap = map_file_path(path).unwrap();
|
||||
let mmap = map_file_path(path.clone()).unwrap();
|
||||
let stage1_time = stage1_start.elapsed();
|
||||
println!("File mapping took: {:?}", stage1_time);
|
||||
|
||||
// println!("File mapping took: {:?}", stage1_time);
|
||||
|
||||
// let file = File::open(path).unwrap();
|
||||
let file_contents = read(path).unwrap();
|
||||
// let mmap = map_file_path(path.into()).unwrap();
|
||||
|
||||
let mut decoder = JpegDecoder::new(&file_contents);
|
||||
let options = DecoderOptions::new_fast()
|
||||
.jpeg_set_max_scans(5)
|
||||
.jpeg_set_out_colorspace(zune_image::codecs::qoi::zune_core::colorspace::ColorSpace::BGRA);
|
||||
decoder.set_options(options);
|
||||
decoder.decode_headers().unwrap();
|
||||
let info = decoder.info().unwrap();
|
||||
let width = info.width as usize;
|
||||
let height = info.height as usize;
|
||||
println!("{} x {}", width, height);
|
||||
let mut buffer2: Vec<u8> = vec![0; width * height * 4];
|
||||
decoder.decode_into(buffer2.as_mut_slice());
|
||||
|
||||
// Stage 2: Read the image
|
||||
let stage2_start = Instant::now();
|
||||
let img = read_zune_image(mmap.deref()).unwrap();
|
||||
let width = img.dimensions().0;
|
||||
let height = img.dimensions().1;
|
||||
let stage2_time = stage2_start.elapsed();
|
||||
println!("Image decoding took: {:?}", stage2_time);
|
||||
|
||||
// let stage2_start = Instant::now();
|
||||
// let img = read_zune_image(mmap.deref()).unwrap();
|
||||
// let width = img.dimensions().0;
|
||||
// let height = img.dimensions().1;
|
||||
// let stage2_time = stage2_start.elapsed();
|
||||
// println!("Image decoding took: {:?}", stage2_time);
|
||||
|
||||
// Stage 3: Flatten the image
|
||||
let stage3_start = Instant::now();
|
||||
let flat = &mut flatten_zune_image(&img)[0];
|
||||
let stage3_time = stage3_start.elapsed();
|
||||
println!("Image flattening took: {:?}", stage3_time);
|
||||
// let stage3_start = Instant::now();
|
||||
// let flat = &mut flatten_zune_image(&img)[0];
|
||||
// let stage3_time = stage3_start.elapsed();
|
||||
// println!("Image flattening took: {:?}", stage3_time);
|
||||
|
||||
// Stage 4: Convert to ARGB format
|
||||
let stage4_start = Instant::now();
|
||||
let mut buffer: Vec<u32> = vec![0; width * height];
|
||||
// let mut buffer: Vec<u32> = vec![0; width * height];
|
||||
|
||||
for (rgba, argb) in flat.chunks_mut(3).zip(buffer.iter_mut()) {
|
||||
let r = rgba[0] as u32;
|
||||
let g = rgba[1] as u32;
|
||||
let b = rgba[2] as u32;
|
||||
*argb = r << 16 | g << 8 | b;
|
||||
}
|
||||
// for (rgba, argb) in buffer2.chunks_mut(4).zip(buffer.iter_mut()) {
|
||||
// let r = rgba[0] as u32;
|
||||
// let g = rgba[1] as u32;
|
||||
// let b = rgba[2] as u32;
|
||||
// *argb = r << 16 | g << 8 | b;
|
||||
// }
|
||||
let buffer: &[u32] =
|
||||
unsafe { std::slice::from_raw_parts(buffer2.as_ptr() as *const u32, buffer2.len() / 4) };
|
||||
let stage4_time = stage4_start.elapsed();
|
||||
println!("RGBA to ARGB conversion took: {:?}", stage4_time);
|
||||
|
||||
|
||||
// Total time
|
||||
let total_time = total_start.elapsed();
|
||||
println!("Total loading time: {:?}", total_time);
|
||||
|
||||
|
||||
ImflowImageBuffer {
|
||||
width,
|
||||
height,
|
||||
argb_buffer: buffer,
|
||||
argb_buffer: buffer.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,26 +217,28 @@ pub struct ImflowImageBuffer {
|
||||
|
||||
pub fn load_image_argb_imagers(path: PathBuf) -> ImflowImageBuffer {
|
||||
let total_start = Instant::now();
|
||||
|
||||
|
||||
// Stage 1: Memory map the file
|
||||
let stage1_start = Instant::now();
|
||||
let mmap = map_file_path(path).unwrap();
|
||||
let stage1_time = stage1_start.elapsed();
|
||||
println!("File mapping took: {:?}", stage1_time);
|
||||
|
||||
// println!("File mapping took: {:?}", stage1_time);
|
||||
|
||||
// Stage 2: Read the image
|
||||
let stage2_start = Instant::now();
|
||||
let img = image::load_from_memory(mmap.deref()).map_err(|e| e.to_string()).unwrap();
|
||||
let img = image::load_from_memory(mmap.deref())
|
||||
.map_err(|e| e.to_string())
|
||||
.unwrap();
|
||||
let width = img.width() as usize;
|
||||
let height = img.height() as usize;
|
||||
let stage2_time = stage2_start.elapsed();
|
||||
println!("Image decoding took: {:?}", stage2_time);
|
||||
|
||||
// println!("Image decoding took: {:?}", stage2_time);
|
||||
|
||||
// Stage 3: Flatten the image
|
||||
let stage3_start = Instant::now();
|
||||
let mut flat = img.into_rgba8().into_raw();
|
||||
let stage3_time = stage3_start.elapsed();
|
||||
println!("Image flattening took: {:?}", stage3_time);
|
||||
// println!("Image flattening took: {:?}", stage3_time);
|
||||
|
||||
// Stage 4: Convert to ARGB format
|
||||
let stage4_start = Instant::now();
|
||||
@ -219,12 +251,12 @@ pub fn load_image_argb_imagers(path: PathBuf) -> ImflowImageBuffer {
|
||||
*argb = r << 16 | g << 8 | b;
|
||||
}
|
||||
let stage4_time = stage4_start.elapsed();
|
||||
println!("RGBA to ARGB conversion took: {:?}", stage4_time);
|
||||
|
||||
// println!("RGBA to ARGB conversion took: {:?}", stage4_time);
|
||||
|
||||
// Total time
|
||||
let total_time = total_start.elapsed();
|
||||
println!("Total loading time: {:?}", total_time);
|
||||
|
||||
|
||||
ImflowImageBuffer {
|
||||
width,
|
||||
height,
|
||||
@ -240,3 +272,15 @@ pub fn load_available_images(dir: PathBuf) -> Vec<PathBuf> {
|
||||
files
|
||||
}
|
||||
|
||||
pub fn get_embedded_thumbnail(path: PathBuf) -> Option<Vec<u8>> {
|
||||
let meta = rexiv2::Metadata::new_from_path(path);
|
||||
match meta {
|
||||
Ok(meta) => {
|
||||
meta.get_thumbnail().map(|v| v.to_vec())
|
||||
}
|
||||
Err(e) => None,
|
||||
}
|
||||
// let file = std::fs::File::open(path).ok()?;
|
||||
// let exif = Reader::new().read_from_container(&mut std::io::BufReader::new(file)).ok()?;
|
||||
// exif.get_thumbnail()
|
||||
}
|
||||
|
128
src/main.rs
128
src/main.rs
@ -5,7 +5,7 @@
|
||||
use std::cmp::min;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs::{self};
|
||||
use std::io::Read;
|
||||
use std::io::{Cursor, Read};
|
||||
use std::ops::Deref;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{self, Duration, Instant};
|
||||
@ -20,15 +20,15 @@ use iced::widget::{
|
||||
use iced::{Center, Element, Fill, Length, Size, Subscription, Task, Theme, keyboard};
|
||||
use image::{self, DynamicImage, EncodableLayout, GenericImageView, ImageBuffer, ImageReader};
|
||||
|
||||
use clap::Parser;
|
||||
use imflow::image::{
|
||||
Approach, ImflowImageBuffer, flatten_image_image, flatten_zune_image, load_available_images,
|
||||
load_image_argb, load_image_argb_imagers, load_thumbnail, map_file, map_file_path,
|
||||
read_zune_image,
|
||||
Approach, ImflowImageBuffer, flatten_image_image, flatten_zune_image, get_embedded_thumbnail,
|
||||
load_available_images, load_image_argb, load_image_argb_imagers, load_thumbnail, map_file,
|
||||
map_file_path, read_zune_image,
|
||||
};
|
||||
use minifb::{Key, Window, WindowOptions};
|
||||
use threadpool::ThreadPool;
|
||||
use zune_image::codecs::qoi::zune_core::options::DecoderOptions; // for general image operations
|
||||
use clap::Parser;
|
||||
// use image::io::Reader as ImageReader; // specifically for Reader
|
||||
|
||||
use std::sync::{Arc, mpsc};
|
||||
@ -336,22 +336,25 @@ use std::sync::{Arc, mpsc};
|
||||
struct State {
|
||||
current_image_id: usize,
|
||||
loaded_images: HashMap<PathBuf, ImflowImageBuffer>,
|
||||
loaded_images_thumbnails: HashMap<PathBuf, ImflowImageBuffer>,
|
||||
available_images: Vec<PathBuf>,
|
||||
current_image_path: PathBuf,
|
||||
pool: ThreadPool,
|
||||
loader_rx: mpsc::Receiver<(PathBuf, ImflowImageBuffer)>,
|
||||
loader_tx: mpsc::Sender<(PathBuf, ImflowImageBuffer)>,
|
||||
currently_loading: HashSet<PathBuf>, // Track what's being loaded
|
||||
currently_loading: HashSet<PathBuf>, // Track what's being loaded
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn new(path: PathBuf) -> Self {
|
||||
let current_image_id: usize = 0;
|
||||
let mut loaded_images: HashMap<PathBuf, ImflowImageBuffer> = HashMap::new();
|
||||
let mut loaded_thumbnails: HashMap<PathBuf, ImflowImageBuffer> = HashMap::new();
|
||||
let available_images = load_available_images(path);
|
||||
let new_path = available_images[0].clone();
|
||||
let current_image = load_image_argb_imagers(new_path.clone());
|
||||
loaded_images.insert(new_path.clone(), current_image);
|
||||
// let current_image = load_image_argb_imagers(new_path.clone());
|
||||
// let current_image = load_image_argb_imagers(new_path.clone());
|
||||
// loaded_images.insert(new_path.clone(), current_image);
|
||||
|
||||
let (loader_tx, loader_rx) = mpsc::channel();
|
||||
|
||||
@ -359,6 +362,35 @@ impl State {
|
||||
|
||||
let currently_loading = HashSet::new();
|
||||
|
||||
for path in &available_images {
|
||||
if let Some(thumbnail) = get_embedded_thumbnail(path.clone()) {
|
||||
let decoder = image::ImageReader::new(Cursor::new(thumbnail))
|
||||
.with_guessed_format()
|
||||
.unwrap();
|
||||
let image = decoder.decode().unwrap();
|
||||
|
||||
let width: usize = image.width() as usize;
|
||||
let height: usize = image.height() as usize;
|
||||
let mut flat = image.into_rgba8().into_raw();
|
||||
let mut buffer: Vec<u32> = vec![0; width * height];
|
||||
|
||||
for (rgba, argb) in flat.chunks_mut(4).zip(buffer.iter_mut()) {
|
||||
let r = rgba[0] as u32;
|
||||
let g = rgba[1] as u32;
|
||||
let b = rgba[2] as u32;
|
||||
*argb = r << 16 | g << 8 | b;
|
||||
}
|
||||
|
||||
let buf = ImflowImageBuffer {
|
||||
width,
|
||||
height,
|
||||
argb_buffer: buffer,
|
||||
};
|
||||
|
||||
loaded_thumbnails.insert(path.clone(), buf);
|
||||
}
|
||||
}
|
||||
|
||||
let mut state = Self {
|
||||
current_image_id,
|
||||
loaded_images,
|
||||
@ -367,10 +399,13 @@ impl State {
|
||||
pool,
|
||||
loader_rx,
|
||||
loader_tx,
|
||||
currently_loading
|
||||
currently_loading,
|
||||
loaded_images_thumbnails: loaded_thumbnails,
|
||||
};
|
||||
|
||||
state.preload_next_images(min(state.available_images.len(), 64));
|
||||
// state.preload_next_images(min(state.available_images.len(), 1));
|
||||
|
||||
// let thumbnail_path = state.available_images[0].clone();
|
||||
|
||||
state
|
||||
}
|
||||
@ -386,10 +421,11 @@ impl State {
|
||||
return;
|
||||
}
|
||||
let tx = self.loader_tx.clone();
|
||||
self.currently_loading.insert(path.clone());
|
||||
self.currently_loading.insert(path.clone());
|
||||
|
||||
self.pool.execute(move || {
|
||||
let image = load_image_argb(path.clone());
|
||||
// let image = load_image_argb_imagers(path.clone());
|
||||
let _ = tx.send((path, image));
|
||||
});
|
||||
}
|
||||
@ -407,7 +443,7 @@ impl State {
|
||||
as usize;
|
||||
let new_path = self.available_images[self.current_image_id].clone();
|
||||
if !self.loaded_images.contains_key(&new_path) {
|
||||
self.request_load(new_path.clone());
|
||||
// self.request_load(new_path.clone());
|
||||
}
|
||||
self.current_image_path = new_path;
|
||||
}
|
||||
@ -415,6 +451,41 @@ impl State {
|
||||
fn get_current_image(&self) -> Option<&ImflowImageBuffer> {
|
||||
self.loaded_images.get(&self.current_image_path)
|
||||
}
|
||||
|
||||
fn get_thumbnail(&mut self) -> &ImflowImageBuffer {
|
||||
if self.loaded_images_thumbnails.contains_key(&self.current_image_path) {
|
||||
return self.loaded_images_thumbnails.get(&self.current_image_path).unwrap();
|
||||
}
|
||||
|
||||
let path = &self.current_image_path;
|
||||
if let Some(thumbnail) = get_embedded_thumbnail(path.clone()) {
|
||||
let decoder = image::ImageReader::new(Cursor::new(thumbnail))
|
||||
.with_guessed_format()
|
||||
.unwrap();
|
||||
let image = decoder.decode().unwrap();
|
||||
|
||||
let width: usize = image.width() as usize;
|
||||
let height: usize = image.height() as usize;
|
||||
let mut flat = image.into_rgba8().into_raw();
|
||||
let mut buffer: Vec<u32> = vec![0; width * height];
|
||||
|
||||
for (rgba, argb) in flat.chunks_mut(4).zip(buffer.iter_mut()) {
|
||||
let r = rgba[0] as u32;
|
||||
let g = rgba[1] as u32;
|
||||
let b = rgba[2] as u32;
|
||||
*argb = r << 16 | g << 8 | b;
|
||||
}
|
||||
|
||||
let buf = ImflowImageBuffer {
|
||||
width,
|
||||
height,
|
||||
argb_buffer: buffer,
|
||||
};
|
||||
|
||||
self.loaded_images_thumbnails.insert(path.clone(), buf);
|
||||
}
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
@ -425,8 +496,8 @@ struct Args {
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
const WIDTH: usize = 1920;
|
||||
const HEIGHT: usize = 1080;
|
||||
const WIDTH: usize = 2000;
|
||||
const HEIGHT: usize = 1000;
|
||||
let mut window = Window::new(
|
||||
"Test - ESC to exit",
|
||||
WIDTH,
|
||||
@ -441,27 +512,32 @@ fn main() {
|
||||
|
||||
let path = args.path.unwrap_or("./test_images".into());
|
||||
let mut state = State::new(path);
|
||||
let mut waiting = true;
|
||||
let mut waiting = false;
|
||||
window.set_key_repeat_delay(0.1);
|
||||
window.set_key_repeat_rate(0.1);
|
||||
|
||||
let thumbs: Vec<&ImflowImageBuffer> = state.loaded_images_thumbnails.values().collect();
|
||||
show_image(&mut window, thumbs[0]);
|
||||
|
||||
while window.is_open() && !window.is_key_down(Key::Escape) {
|
||||
window.update();
|
||||
state.check_loaded_images();
|
||||
if window.is_key_pressed(Key::Right, minifb::KeyRepeat::Yes) {
|
||||
if !waiting && window.is_key_pressed(Key::Right, minifb::KeyRepeat::Yes) {
|
||||
state.next_image(1);
|
||||
waiting = true;
|
||||
} else if window.is_key_pressed(Key::Left, minifb::KeyRepeat::Yes) {
|
||||
show_image(&mut window, state.get_thumbnail());
|
||||
// waiting = true;
|
||||
} else if !waiting && window.is_key_pressed(Key::Left, minifb::KeyRepeat::Yes) {
|
||||
state.next_image(-1);
|
||||
waiting = true;
|
||||
}
|
||||
if waiting {
|
||||
if let Some(image) = state.get_current_image(){
|
||||
waiting = false;
|
||||
|
||||
show_image(&mut window, &image);
|
||||
}
|
||||
show_image(&mut window, state.get_thumbnail());
|
||||
// waiting = true;
|
||||
}
|
||||
// if waiting {
|
||||
// if let Some(image) = state.get_current_image() {
|
||||
// waiting = false;
|
||||
|
||||
// show_image(&mut window, &image);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user