Remove thumbnail letterboxing, fix scroll

This commit is contained in:
Dawid Pietrykowski 2025-04-13 16:06:29 +02:00
parent b00bbbadc0
commit ec879c31b1
4 changed files with 67 additions and 32 deletions

View File

@ -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();
}
}

View File

@ -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 {

View File

@ -348,9 +348,19 @@ pub fn load_thumbnail_exif(path: &ImageData) -> Option<ImflowImageBuffer> {
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,
}
}

View File

@ -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<ImageData, ImflowImageBuffer> = HashMap::new();
// let mut loaded_thumbnails: HashMap<ImageData, ImflowImageBuffer> = 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 {
// 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