From ec879c31b11d34e8fa303c17b58c41247f85ad4f Mon Sep 17 00:00:00 2001 From: Dawid Pietrykowski Date: Sun, 13 Apr 2025 16:06:29 +0200 Subject: [PATCH] Remove thumbnail letterboxing, fix scroll --- src/app.rs | 37 ++++++++++++++++++++++++++++++------- src/egui_tools.rs | 3 ++- src/image.rs | 16 +++++++++++++--- src/store.rs | 43 ++++++++++++++++++++++--------------------- 4 files changed, 67 insertions(+), 32 deletions(-) diff --git a/src/app.rs b/src/app.rs index 0325f6c..b611426 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,10 +1,12 @@ use crate::egui_tools::EguiRenderer; use egui::load::{ImageLoadResult, ImageLoader}; -use egui::{Align2, Color32, ColorImage, Event, Image, ImageSource, Key, PointerButton, Sense}; +use egui::{ + Align, Align2, Color32, ColorImage, Event, Image, ImageSource, Key, PointerButton, Sense, +}; use egui_wgpu::wgpu::SurfaceError; use egui_wgpu::{ScreenDescriptor, wgpu}; use image::metadata::Orientation; -use imflow::image::{ImageFormat, swap_wh}; +use imflow::image::{ImageData, ImageFormat, swap_wh}; use imflow::store::{FileFilters, ImageStore}; use std::cmp::{max, min}; use std::collections::HashMap; @@ -224,6 +226,7 @@ pub struct AppState { pub transform_buffer: wgpu::Buffer, pub transform_data: TransformData, pub filters: FileFilters, + pub selected_image: ImageData, } impl AppState { @@ -282,7 +285,11 @@ impl AppState { let egui_renderer = EguiRenderer::new(&device, surface_config.format, None, 1, window); - let store = Arc::new(RwLock::new(ImageStore::new(path))); + let image_store = ImageStore::new(path); + + // TODO: verify + let selected_image = image_store.current_image_path.clone(); + let store = Arc::new(RwLock::new(image_store)); let loader = ImflowEguiLoader::new(store.clone()); @@ -317,6 +324,7 @@ impl AppState { transform_buffer, transform_data, filters: FileFilters::default(), + selected_image, } } @@ -387,7 +395,12 @@ impl App { pub fn update_texture(&mut self) { let state = self.state.as_mut().unwrap(); - + { + let store = state.store.read().unwrap(); + if state.selected_image == store.current_image_path { + return; + } + } { let mut store = state.store.write().unwrap(); store.check_loaded_images(); @@ -641,12 +654,15 @@ impl App { let filename; let window; let filtered_images; + let current_image; + let mut selected_image = None; { let store = state.store.read().unwrap(); rating = store.get_current_rating(); path = store.current_image_path.clone(); current_id = store.current_image_id; image_count = store.available_images.len(); + current_image = store.current_image_path.clone(); filtered_images = store.get_filtered_images(&state.filters); filename = path.path.file_name().unwrap(); window = self.window.as_ref().unwrap(); @@ -690,10 +706,11 @@ impl App { .corner_radius(10) .sense(Sense::click()), ); + if current_image == image { + image_widget.scroll_to_me(Some(Align::Center)); + } if image_widget.clicked() { - image_widget.scroll_to_me(None); - // ui.scroll_to_rect(image_widget.rect, None); - println!("{}", image.get_hash_str()); + selected_image = Some(image); } } }); @@ -712,6 +729,10 @@ impl App { } }); + if let Some(selected_image) = selected_image { + state.store.write().unwrap().select_image(selected_image); + } + state.egui_renderer.end_frame_and_draw( &state.device, &state.queue, @@ -724,6 +745,8 @@ impl App { state.queue.submit(Some(encoder.finish())); surface_texture.present(); + + self.update_texture(); } } diff --git a/src/egui_tools.rs b/src/egui_tools.rs index 06dbea0..b836233 100644 --- a/src/egui_tools.rs +++ b/src/egui_tools.rs @@ -1,4 +1,4 @@ -use egui::{Color32, Context, Visuals}; +use egui::{vec2, Color32, Context, Rangef, Style, Visuals}; use egui_wgpu::wgpu::{CommandEncoder, Device, Queue, StoreOp, TextureFormat, TextureView}; use egui_wgpu::{Renderer, ScreenDescriptor, wgpu}; use egui_winit::State; @@ -25,6 +25,7 @@ impl EguiRenderer { ) -> EguiRenderer { let egui_context = Context::default(); egui_context.options_mut(|o| o.line_scroll_speed = 200.0); + egui_context.style_mut(|s| s.scroll_animation.duration = Rangef::new(0.1, 5.0)); egui_context.set_visuals_of( egui::Theme::Dark, Visuals { diff --git a/src/image.rs b/src/image.rs index 1d4813a..f1be53b 100644 --- a/src/image.rs +++ b/src/image.rs @@ -348,9 +348,19 @@ pub fn load_thumbnail_exif(path: &ImageData) -> Option { let decoder = image::ImageReader::new(Cursor::new(thumbnail)) .with_guessed_format() .unwrap(); - let mut image = decoder.decode().unwrap(); - + let image = decoder.decode().unwrap(); let orientation = path.orientation; + + let width = image.width(); + let height = image.height(); + // TODO: extract from image + let ratio_image = 1.5; + let ratio_thumbnail = width as f32 / height as f32; + let crop = ratio_thumbnail / ratio_image; + let start = ((0.5 - (crop / 2.0)) * height as f32).round(); + let cropped_height = (height as f32 * crop) as u32; + let mut image = image.crop_imm(0, start as u32, width, cropped_height); + image.apply_orientation(orientation); let width: usize = image.width() as usize; let height: usize = image.height() as usize; @@ -457,7 +467,7 @@ pub fn load_heif(path: &ImageData, resize: bool) -> ImflowImageBuffer { rgba_buffer: u32_slice.to_vec(), rating, // TODO: verify - orientation: path.orientation, + orientation: Orientation::NoTransforms, } } diff --git a/src/store.rs b/src/store.rs index ec57adf..d9301ca 100644 --- a/src/store.rs +++ b/src/store.rs @@ -9,7 +9,7 @@ use std::path::PathBuf; use std::time::Instant; use threadpool::ThreadPool; -const PRELOAD_NEXT_IMAGE_N: usize = 16; +const PRELOAD_NEXT_IMAGE_N: usize = 0; pub struct FileFilters { pub rating: [bool; 6], @@ -47,7 +47,6 @@ impl ImageStore { pub fn new(path: PathBuf) -> Self { let current_image_id: usize = 0; let mut loaded_images: HashMap = HashMap::new(); - // let mut loaded_thumbnails: HashMap = HashMap::new(); let available_images = load_available_images(path); let new_path = available_images[0].clone(); @@ -68,10 +67,10 @@ impl ImageStore { available_images .par_iter() .for_each_with(sender, |s, path| { - if path.embedded_thumbnail { - let buf = load_thumbnail(path); - s.send((path.clone(), buf)).unwrap(); - } + // if path.embedded_thumbnail { + let buf = load_thumbnail(path); + s.send((path.clone(), buf)).unwrap(); + // } }); let loaded_thumbnails: HashMap<_, _> = receiver.iter().collect(); let total_time = total_start.elapsed(); @@ -81,9 +80,6 @@ impl ImageStore { loaded_thumbnails.len() ); - // let path = available_images[0].clone(); - // let image = load_image(&path.clone()); - // loaded_images.insert(path, image); let (path, image) = first_image_thread.join().unwrap(); loaded_images.insert(path, image); let mut state = Self { @@ -125,16 +121,15 @@ impl ImageStore { } pub fn get_current_rating(&self) -> i32 { - let imbuf = if let Some(full) = self.get_current_image() { - // println!("full"); - full - } else { - // TODO: this assumes loaded thumbnail - self.loaded_images_thumbnails - .get(&self.current_image_path) - .unwrap() - }; - imbuf.rating + self.current_image_path.rating + // let imbuf = if let Some(full) = self.get_current_image() { + // // println!("full"); + // full + // } else { + // // TODO: this assumes loaded thumbnail + + // }; + // imbuf.rating } pub fn preload_next_images(&mut self, n: usize) { @@ -150,7 +145,6 @@ impl ImageStore { } pub fn request_load(&mut self, path: ImageData) { - // return; if self.loaded_images.contains_key(&path) || self.currently_loading.contains(&path) { return; } @@ -183,6 +177,14 @@ impl ImageStore { self.preload_next_images(PRELOAD_NEXT_IMAGE_N); } + pub fn select_image(&mut self, selected_image: ImageData) { + if !self.loaded_images.contains_key(&selected_image) { + self.request_load(selected_image.clone()); + } + self.current_image_path = selected_image; + self.preload_next_images(PRELOAD_NEXT_IMAGE_N); + } + pub fn get_current_image(&self) -> Option<&ImflowImageBuffer> { self.loaded_images.get(&self.current_image_path) } @@ -214,7 +216,6 @@ impl ImageStore { .get(&self.current_image_path) .unwrap(); } - // panic!(); let buf = load_thumbnail(&self.current_image_path); self.loaded_images_thumbnails