Optimizations, thumbnail strip, store RwLock
This commit is contained in:
parent
da998ccbf1
commit
207878928f
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -644,6 +644,15 @@ dependencies = [
|
|||||||
"itertools 0.10.5",
|
"itertools 0.10.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-channel"
|
||||||
|
version = "0.5.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-deque"
|
name = "crossbeam-deque"
|
||||||
version = "0.8.6"
|
version = "0.8.6"
|
||||||
@ -1433,6 +1442,7 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
"clap 4.5.34",
|
"clap 4.5.34",
|
||||||
"criterion",
|
"criterion",
|
||||||
|
"crossbeam-channel",
|
||||||
"egui",
|
"egui",
|
||||||
"egui-wgpu",
|
"egui-wgpu",
|
||||||
"egui-winit",
|
"egui-winit",
|
||||||
@ -1441,10 +1451,12 @@ dependencies = [
|
|||||||
"jpegxl-rs",
|
"jpegxl-rs",
|
||||||
"libheif-rs",
|
"libheif-rs",
|
||||||
"pollster",
|
"pollster",
|
||||||
|
"rayon",
|
||||||
"rexiv2",
|
"rexiv2",
|
||||||
"sha2",
|
"sha2",
|
||||||
"threadpool",
|
"threadpool",
|
||||||
"winit",
|
"winit",
|
||||||
|
"zerocopy 0.8.24",
|
||||||
"zune-image",
|
"zune-image",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -22,6 +22,9 @@ rexiv2 = "0.10.0"
|
|||||||
threadpool = "1.8.1"
|
threadpool = "1.8.1"
|
||||||
bytemuck = "1.22.0"
|
bytemuck = "1.22.0"
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
|
zerocopy = "0.8.24"
|
||||||
|
crossbeam-channel = "0.5.15"
|
||||||
|
rayon = "1.10.0"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
259
src/app.rs
259
src/app.rs
@ -1,11 +1,16 @@
|
|||||||
use crate::egui_tools::EguiRenderer;
|
use crate::egui_tools::EguiRenderer;
|
||||||
use egui::{Event, Key, PointerButton};
|
use egui::load::{ImageLoadResult, ImageLoader};
|
||||||
|
use egui::{Align2, Color32, ColorImage, Event, ImageSource, Key, PointerButton};
|
||||||
use egui_wgpu::wgpu::SurfaceError;
|
use egui_wgpu::wgpu::SurfaceError;
|
||||||
use egui_wgpu::{ScreenDescriptor, wgpu};
|
use egui_wgpu::{ScreenDescriptor, wgpu};
|
||||||
|
use image::metadata::Orientation;
|
||||||
|
use imflow::image::swap_wh;
|
||||||
use imflow::store::ImageStore;
|
use imflow::store::ImageStore;
|
||||||
|
use std::cmp::{max, min};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, RwLock};
|
||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
use wgpu::{PipelineCompilationOptions, SurfaceConfiguration};
|
use wgpu::{PipelineCompilationOptions, SurfaceConfiguration};
|
||||||
use winit::application::ApplicationHandler;
|
use winit::application::ApplicationHandler;
|
||||||
@ -15,14 +20,13 @@ use winit::event_loop::ActiveEventLoop;
|
|||||||
use winit::platform::x11::WindowAttributesExtX11;
|
use winit::platform::x11::WindowAttributesExtX11;
|
||||||
use winit::window::{Window, WindowId};
|
use winit::window::{Window, WindowId};
|
||||||
|
|
||||||
// Uniforms for transformations
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
struct Transforms {
|
struct Transforms {
|
||||||
transform: [f32; 16], // 4x4 matrix
|
transform: [f32; 16], // 4x4 matrix
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
_padding1: u32,
|
orientation: u32,
|
||||||
_padding2: u32,
|
_padding2: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,6 +36,7 @@ pub(crate) struct TransformData {
|
|||||||
zoom: f32,
|
zoom: f32,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
|
orientation: Orientation,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
@ -212,7 +217,7 @@ pub struct AppState {
|
|||||||
pub surface: wgpu::Surface<'static>,
|
pub surface: wgpu::Surface<'static>,
|
||||||
pub scale_factor: f32,
|
pub scale_factor: f32,
|
||||||
pub egui_renderer: EguiRenderer,
|
pub egui_renderer: EguiRenderer,
|
||||||
pub store: ImageStore,
|
pub store: Arc<RwLock<ImageStore>>,
|
||||||
pub image_texture: wgpu::Texture,
|
pub image_texture: wgpu::Texture,
|
||||||
pub bind_group: wgpu::BindGroup,
|
pub bind_group: wgpu::BindGroup,
|
||||||
pub render_pipeline: wgpu::RenderPipeline,
|
pub render_pipeline: wgpu::RenderPipeline,
|
||||||
@ -276,9 +281,13 @@ impl AppState {
|
|||||||
|
|
||||||
let egui_renderer = EguiRenderer::new(&device, surface_config.format, None, 1, window);
|
let egui_renderer = EguiRenderer::new(&device, surface_config.format, None, 1, window);
|
||||||
|
|
||||||
let scale_factor = 1.0;
|
let store = Arc::new(RwLock::new(ImageStore::new(path)));
|
||||||
|
|
||||||
let store = ImageStore::new(path);
|
let loader = ImflowEguiLoader::new(store.clone());
|
||||||
|
|
||||||
|
egui_renderer.context().add_image_loader(Arc::new(loader));
|
||||||
|
|
||||||
|
let scale_factor = 1.0;
|
||||||
|
|
||||||
let (image_texture, bind_group, render_pipeline, transform_buffer) =
|
let (image_texture, bind_group, render_pipeline, transform_buffer) =
|
||||||
// setup_texture(&device, surface_config.clone(), 6000, 4000);
|
// setup_texture(&device, surface_config.clone(), 6000, 4000);
|
||||||
@ -290,6 +299,7 @@ impl AppState {
|
|||||||
zoom: 1.0,
|
zoom: 1.0,
|
||||||
width: 10000,
|
width: 10000,
|
||||||
height: 10000,
|
height: 10000,
|
||||||
|
orientation: Orientation::NoTransforms,
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@ -313,6 +323,10 @@ impl AppState {
|
|||||||
self.surface_config.height = height;
|
self.surface_config.height = height;
|
||||||
self.surface.configure(&self.device, &self.surface_config);
|
self.surface.configure(&self.device, &self.surface_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fn get_store(&mut self) -> &ImageStore {
|
||||||
|
// &self.store.lock().unwrap()
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
@ -358,7 +372,7 @@ impl App {
|
|||||||
self.window.get_or_insert(window);
|
self.window.get_or_insert(window);
|
||||||
self.state.get_or_insert(state);
|
self.state.get_or_insert(state);
|
||||||
|
|
||||||
self.pan_zoom(0.0, 0.0, 0.0);
|
self.reset_transform();
|
||||||
self.update_texture();
|
self.update_texture();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,11 +386,13 @@ impl App {
|
|||||||
pub fn update_texture(&mut self) {
|
pub fn update_texture(&mut self) {
|
||||||
let state = self.state.as_mut().unwrap();
|
let state = self.state.as_mut().unwrap();
|
||||||
|
|
||||||
state.store.check_loaded_images();
|
{
|
||||||
let imbuf = if let Some(full) = state.store.get_current_image() {
|
let mut store = state.store.write().unwrap();
|
||||||
|
store.check_loaded_images();
|
||||||
|
let imbuf = if let Some(full) = store.get_current_image() {
|
||||||
full
|
full
|
||||||
} else {
|
} else {
|
||||||
state.store.get_thumbnail()
|
store.get_thumbnail()
|
||||||
};
|
};
|
||||||
let width = imbuf.width as u32;
|
let width = imbuf.width as u32;
|
||||||
let height = imbuf.height as u32;
|
let height = imbuf.height as u32;
|
||||||
@ -389,6 +405,7 @@ impl App {
|
|||||||
|
|
||||||
state.transform_data.width = width;
|
state.transform_data.width = width;
|
||||||
state.transform_data.height = height;
|
state.transform_data.height = height;
|
||||||
|
state.transform_data.orientation = imbuf.orientation;
|
||||||
|
|
||||||
state.queue.write_texture(
|
state.queue.write_texture(
|
||||||
wgpu::TexelCopyTextureInfo {
|
wgpu::TexelCopyTextureInfo {
|
||||||
@ -409,15 +426,20 @@ impl App {
|
|||||||
depth_or_array_layers: 1,
|
depth_or_array_layers: 1,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
self.pan_zoom(0.0, 0.0, 0.0);
|
self.update_transform();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_transform(&mut self) {
|
fn update_transform(&mut self) {
|
||||||
let state = self.state.as_mut().unwrap();
|
let state = self.state.as_mut().unwrap();
|
||||||
|
|
||||||
let image_aspect_ratio =
|
let (width, height) = swap_wh(
|
||||||
(state.transform_data.width as f32) / (state.transform_data.height as f32);
|
state.transform_data.width,
|
||||||
|
state.transform_data.height,
|
||||||
|
state.transform_data.orientation,
|
||||||
|
);
|
||||||
|
let image_aspect_ratio = (width as f32) / (height as f32);
|
||||||
let window_size = self.window.as_ref().unwrap().inner_size();
|
let window_size = self.window.as_ref().unwrap().inner_size();
|
||||||
let window_aspect_ratio = window_size.width as f32 / window_size.height as f32;
|
let window_aspect_ratio = window_size.width as f32 / window_size.height as f32;
|
||||||
let mut scale_x = 1.0;
|
let mut scale_x = 1.0;
|
||||||
@ -433,9 +455,9 @@ impl App {
|
|||||||
0,
|
0,
|
||||||
bytemuck::cast_slice(&[Transforms {
|
bytemuck::cast_slice(&[Transforms {
|
||||||
transform,
|
transform,
|
||||||
width: state.transform_data.width,
|
width: width as u32,
|
||||||
height: state.transform_data.height,
|
height: height as u32,
|
||||||
_padding1: 0,
|
orientation: state.transform_data.orientation as u32,
|
||||||
_padding2: 0,
|
_padding2: 0,
|
||||||
}]),
|
}]),
|
||||||
);
|
);
|
||||||
@ -603,10 +625,21 @@ impl App {
|
|||||||
render_pass.draw_indexed(0..6, 0, 0..1);
|
render_pass.draw_indexed(0..6, 0, 0..1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let rating = state.store.get_current_rating();
|
let rating;
|
||||||
let path = state.store.current_image_path.clone();
|
let path;
|
||||||
let filename = path.path.file_name().unwrap();
|
let current_id;
|
||||||
let window = self.window.as_ref().unwrap();
|
let image_count;
|
||||||
|
let filename;
|
||||||
|
let window;
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
filename = path.path.file_name().unwrap();
|
||||||
|
window = self.window.as_ref().unwrap();
|
||||||
|
}
|
||||||
{
|
{
|
||||||
state.egui_renderer.begin_frame(window);
|
state.egui_renderer.begin_frame(window);
|
||||||
|
|
||||||
@ -617,7 +650,7 @@ impl App {
|
|||||||
.show(state.egui_renderer.context(), |ui| {
|
.show(state.egui_renderer.context(), |ui| {
|
||||||
ui.vertical_centered(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
ui.label(
|
ui.label(
|
||||||
egui::RichText::new(format!("{:.1}", rating))
|
egui::RichText::new(format!("{}", rating))
|
||||||
.size(42.0)
|
.size(42.0)
|
||||||
.strong(),
|
.strong(),
|
||||||
);
|
);
|
||||||
@ -628,6 +661,59 @@ impl App {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
egui::Window::new("Id")
|
||||||
|
.collapsible(false)
|
||||||
|
.resizable(false)
|
||||||
|
.default_width(5.0)
|
||||||
|
.anchor(Align2::RIGHT_TOP, [-5.0, 5.0])
|
||||||
|
.pivot(Align2::RIGHT_TOP)
|
||||||
|
.show(state.egui_renderer.context(), |ui| {
|
||||||
|
ui.vertical_centered(|ui| {
|
||||||
|
ui.label(
|
||||||
|
egui::RichText::new(format!("{}/{}", current_id, image_count))
|
||||||
|
.size(22.0)
|
||||||
|
.strong(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::Window::new("Images")
|
||||||
|
.collapsible(false)
|
||||||
|
.resizable(false)
|
||||||
|
.default_width(500.0)
|
||||||
|
.default_height(300.0)
|
||||||
|
.anchor(Align2::CENTER_BOTTOM, [0.0, 10.0])
|
||||||
|
.pivot(Align2::CENTER_BOTTOM)
|
||||||
|
.show(state.egui_renderer.context(), |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
// ui.label(
|
||||||
|
// egui::RichText::new(format!("{}/{}", current_id, image_count))
|
||||||
|
// .size(22.0)
|
||||||
|
// .strong(),
|
||||||
|
// );
|
||||||
|
|
||||||
|
const NUM: i32 = 5;
|
||||||
|
for i in max((current_id as i32) - NUM, 0)
|
||||||
|
..min((current_id as i32) + NUM + 1, image_count as i32)
|
||||||
|
{
|
||||||
|
let source = ImageSource::Bytes {
|
||||||
|
uri: std::borrow::Cow::Owned(i.to_string()),
|
||||||
|
bytes: egui::load::Bytes::Static(&[]),
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.add(
|
||||||
|
egui::Image::new(source)
|
||||||
|
// .sca
|
||||||
|
// .load_for_size(ctx, available_size)
|
||||||
|
// .fit_to_fraction(Vec2::new(10.0, 10.0))
|
||||||
|
// .max_width(200.0)
|
||||||
|
.fit_to_original_size(1.0)
|
||||||
|
.corner_radius(10),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// ui.image(source);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
state.egui_renderer.end_frame_and_draw(
|
state.egui_renderer.end_frame_and_draw(
|
||||||
&state.device,
|
&state.device,
|
||||||
@ -668,14 +754,25 @@ impl ApplicationHandler for App {
|
|||||||
}
|
}
|
||||||
WindowEvent::RedrawRequested => {
|
WindowEvent::RedrawRequested => {
|
||||||
self.handle_redraw();
|
self.handle_redraw();
|
||||||
let (events, _keys_down, pointer) = self
|
let (events, _keys_down, pointer, scroll) = self
|
||||||
.state
|
.state
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.egui_renderer
|
.egui_renderer
|
||||||
.context()
|
.context()
|
||||||
.input(|i| (i.events.clone(), i.keys_down.clone(), i.pointer.clone()));
|
.input(|i| {
|
||||||
|
(
|
||||||
|
i.events.clone(),
|
||||||
|
i.keys_down.clone(),
|
||||||
|
i.pointer.clone(),
|
||||||
|
i.smooth_scroll_delta.clone(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut updated_image = false;
|
||||||
|
let mut reset_transform = false;
|
||||||
|
{
|
||||||
|
let mut store = self.state.as_mut().unwrap().store.write().unwrap();
|
||||||
events.iter().for_each(|e| {
|
events.iter().for_each(|e| {
|
||||||
if let Event::Key { key, pressed, .. } = e {
|
if let Event::Key { key, pressed, .. } = e {
|
||||||
if !*pressed {
|
if !*pressed {
|
||||||
@ -683,49 +780,54 @@ impl ApplicationHandler for App {
|
|||||||
}
|
}
|
||||||
match *key {
|
match *key {
|
||||||
Key::ArrowLeft => {
|
Key::ArrowLeft => {
|
||||||
self.state.as_mut().unwrap().store.next_image(-1);
|
store.next_image(-1);
|
||||||
self.update_texture();
|
updated_image = true;
|
||||||
}
|
}
|
||||||
Key::ArrowRight => {
|
Key::ArrowRight => {
|
||||||
self.state.as_mut().unwrap().store.next_image(1);
|
store.next_image(1);
|
||||||
self.update_texture();
|
updated_image = true;
|
||||||
}
|
}
|
||||||
Key::ArrowUp => {
|
Key::ArrowUp => {
|
||||||
let rating =
|
let rating = store.get_current_rating();
|
||||||
self.state.as_mut().unwrap().store.get_current_rating();
|
store.set_rating(rating + 1);
|
||||||
self.state.as_mut().unwrap().store.set_rating(rating + 1);
|
|
||||||
}
|
}
|
||||||
Key::ArrowDown => {
|
Key::ArrowDown => {
|
||||||
let rating =
|
let rating = store.get_current_rating();
|
||||||
self.state.as_mut().unwrap().store.get_current_rating();
|
store.set_rating(rating - 1);
|
||||||
self.state.as_mut().unwrap().store.set_rating(rating - 1);
|
|
||||||
}
|
}
|
||||||
Key::Backtick => self.state.as_mut().unwrap().store.set_rating(0),
|
Key::Backtick => store.set_rating(0),
|
||||||
Key::Num0 => self.state.as_mut().unwrap().store.set_rating(0),
|
Key::Num0 => store.set_rating(0),
|
||||||
Key::Num1 => self.state.as_mut().unwrap().store.set_rating(1),
|
Key::Num1 => store.set_rating(1),
|
||||||
Key::Num2 => self.state.as_mut().unwrap().store.set_rating(2),
|
Key::Num2 => store.set_rating(2),
|
||||||
Key::Num3 => self.state.as_mut().unwrap().store.set_rating(3),
|
Key::Num3 => store.set_rating(3),
|
||||||
Key::Num4 => self.state.as_mut().unwrap().store.set_rating(4),
|
Key::Num4 => store.set_rating(4),
|
||||||
Key::Num5 => self.state.as_mut().unwrap().store.set_rating(5),
|
Key::Num5 => store.set_rating(5),
|
||||||
Key::Escape => exit(0),
|
Key::Escape => exit(0),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
} else if let Event::MouseWheel { delta, .. } = e {
|
|
||||||
self.pan_zoom(delta.y * 0.2, 0.0, 0.0);
|
|
||||||
} else if let Event::PointerButton {
|
} else if let Event::PointerButton {
|
||||||
button, pressed, ..
|
button, pressed, ..
|
||||||
} = e
|
} = e
|
||||||
{
|
{
|
||||||
if *pressed && *button == PointerButton::Secondary {
|
if *pressed && *button == PointerButton::Secondary {
|
||||||
self.reset_transform();
|
reset_transform = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
if pointer.primary_down() && pointer.is_moving() {
|
if pointer.primary_down() && pointer.is_moving() {
|
||||||
self.pan_zoom(0.0, pointer.delta().x * 0.001, pointer.delta().y * -0.001);
|
self.pan_zoom(0.0, pointer.delta().x * 0.001, pointer.delta().y * -0.001);
|
||||||
}
|
}
|
||||||
|
if scroll.y != 0.0 {
|
||||||
|
self.pan_zoom(scroll.y * 0.01, 0.0, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if updated_image {
|
||||||
|
self.update_texture();
|
||||||
|
}
|
||||||
|
if reset_transform {
|
||||||
|
self.reset_transform();
|
||||||
|
}
|
||||||
self.window.as_ref().unwrap().request_redraw();
|
self.window.as_ref().unwrap().request_redraw();
|
||||||
}
|
}
|
||||||
WindowEvent::Resized(new_size) => {
|
WindowEvent::Resized(new_size) => {
|
||||||
@ -735,3 +837,70 @@ impl ApplicationHandler for App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ImflowEguiLoader {
|
||||||
|
store: Arc<RwLock<ImageStore>>,
|
||||||
|
// stored: Option<ImageLoadResult>,
|
||||||
|
cache: egui::mutex::Mutex<HashMap<usize, ImageLoadResult>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImflowEguiLoader {
|
||||||
|
pub fn new(store: Arc<RwLock<ImageStore>>) -> ImflowEguiLoader {
|
||||||
|
ImflowEguiLoader {
|
||||||
|
store,
|
||||||
|
cache: egui::mutex::Mutex::new(HashMap::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageLoader for ImflowEguiLoader {
|
||||||
|
fn id(&self) -> &str {
|
||||||
|
"ImflowEguiLoader"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(
|
||||||
|
&self,
|
||||||
|
_ctx: &egui::Context,
|
||||||
|
uri: &str,
|
||||||
|
_size_hint: egui::SizeHint,
|
||||||
|
) -> egui::load::ImageLoadResult {
|
||||||
|
let mut cache = self.cache.lock();
|
||||||
|
|
||||||
|
let id = uri.parse::<usize>().unwrap();
|
||||||
|
if let Some(handle) = cache.get(&id) {
|
||||||
|
handle.clone()
|
||||||
|
} else {
|
||||||
|
let imbuf = {
|
||||||
|
let binding = self.store.read().unwrap();
|
||||||
|
binding.get_thumbnail_id(id).clone()
|
||||||
|
};
|
||||||
|
let mut image = ColorImage::new([imbuf.width, imbuf.height], Color32::BLACK);
|
||||||
|
let image_buffer = image.as_raw_mut();
|
||||||
|
for (i, &value) in imbuf.rgba_buffer.iter().enumerate() {
|
||||||
|
let bytes = value.to_le_bytes();
|
||||||
|
let start = i * 4;
|
||||||
|
image_buffer[start..start + 4].copy_from_slice(&bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = ImageLoadResult::Ok(egui::load::ImagePoll::Ready {
|
||||||
|
image: Arc::new(ColorImage {
|
||||||
|
size: [imbuf.width, imbuf.height],
|
||||||
|
pixels: image.pixels,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
cache.insert(id, res.clone());
|
||||||
|
res.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
fn forget(&self, _uri: &str) {}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
fn forget_all(&self) {}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
fn byte_size(&self) -> usize {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
209
src/image.rs
209
src/image.rs
@ -1,5 +1,6 @@
|
|||||||
use image::DynamicImage;
|
use image::DynamicImage;
|
||||||
use image::RgbaImage;
|
use image::ImageBuffer;
|
||||||
|
use image::Rgba;
|
||||||
use image::imageops::FilterType;
|
use image::imageops::FilterType;
|
||||||
use image::metadata::Orientation;
|
use image::metadata::Orientation;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
@ -25,7 +26,6 @@ use std::io::BufReader;
|
|||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::mem;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
@ -79,29 +79,24 @@ pub struct ImflowImageBuffer {
|
|||||||
pub height: usize,
|
pub height: usize,
|
||||||
pub rgba_buffer: Vec<u32>,
|
pub rgba_buffer: Vec<u32>,
|
||||||
pub rating: i32,
|
pub rating: i32,
|
||||||
|
pub orientation: Orientation,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_rating(image: &ImageData) -> i32 {
|
pub fn get_rating(image: &ImageData) -> i32 {
|
||||||
let meta = Metadata::new_from_path(&image.path);
|
if let Ok(meta) = Metadata::new_from_path(&image.path) {
|
||||||
match meta {
|
meta.get_tag_numeric("Xmp.xmp.Rating")
|
||||||
Ok(meta) => {
|
} else {
|
||||||
let rating = meta.get_tag_numeric("Xmp.xmp.Rating");
|
0
|
||||||
rating
|
|
||||||
}
|
|
||||||
Err(e) => panic!("{:?}", e),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_orientation(path: &PathBuf) -> Orientation {
|
pub fn get_orientation(path: &PathBuf) -> Orientation {
|
||||||
let meta = Metadata::new_from_path(path);
|
Metadata::new_from_path(path).map_or(Orientation::NoTransforms, |meta| {
|
||||||
match meta {
|
Orientation::from_exif(meta.get_orientation() as u8).unwrap()
|
||||||
Ok(meta) => Orientation::from_exif(meta.get_orientation() as u8)
|
})
|
||||||
.unwrap_or(Orientation::NoTransforms),
|
|
||||||
Err(_) => Orientation::NoTransforms,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn swap_wh<T>(width: T, height: T, orientation: Orientation) -> (T, T) {
|
pub fn swap_wh<T>(width: T, height: T, orientation: Orientation) -> (T, T) {
|
||||||
if [
|
if [
|
||||||
Orientation::Rotate90,
|
Orientation::Rotate90,
|
||||||
Orientation::Rotate270,
|
Orientation::Rotate270,
|
||||||
@ -161,15 +156,11 @@ pub fn load_image(image: &ImageData) -> ImflowImageBuffer {
|
|||||||
let (metadata, buffer) = decoder.decode_with::<u8>(&file).unwrap();
|
let (metadata, buffer) = decoder.decode_with::<u8>(&file).unwrap();
|
||||||
let width = metadata.width as usize;
|
let width = metadata.width as usize;
|
||||||
let height = metadata.height as usize;
|
let height = metadata.height as usize;
|
||||||
|
// TODO: convert
|
||||||
|
// let orientation = metadata.orientation;
|
||||||
|
let orientation = Orientation::NoTransforms;
|
||||||
|
|
||||||
let rgba_buffer = unsafe {
|
let rgba_buffer = vec_u8_to_u32(buffer);
|
||||||
Vec::from_raw_parts(
|
|
||||||
buffer.as_ptr() as *mut u32,
|
|
||||||
buffer.len() / 4,
|
|
||||||
buffer.len() / 4,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
std::mem::forget(buffer);
|
|
||||||
|
|
||||||
println!("Total JXL loading time: {:?}", total_start.elapsed());
|
println!("Total JXL loading time: {:?}", total_start.elapsed());
|
||||||
|
|
||||||
@ -178,6 +169,7 @@ pub fn load_image(image: &ImageData) -> ImflowImageBuffer {
|
|||||||
height,
|
height,
|
||||||
rgba_buffer,
|
rgba_buffer,
|
||||||
rating,
|
rating,
|
||||||
|
orientation,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImageFormat::Jpg => {
|
ImageFormat::Jpg => {
|
||||||
@ -196,17 +188,21 @@ pub fn load_image(image: &ImageData) -> ImflowImageBuffer {
|
|||||||
buffer = vec![0; width * height * 4];
|
buffer = vec![0; width * height * 4];
|
||||||
decoder.decode_into(buffer.as_mut_slice()).unwrap();
|
decoder.decode_into(buffer.as_mut_slice()).unwrap();
|
||||||
|
|
||||||
let orientation_start = Instant::now();
|
|
||||||
// TODO: Optimize rotation
|
|
||||||
let orientation = image.orientation;
|
let orientation = image.orientation;
|
||||||
let image = RgbaImage::from_raw(width as u32, height as u32, buffer).unwrap();
|
let rgba_buffer = vec_u8_to_u32(buffer);
|
||||||
let mut dynamic_image = DynamicImage::from(image);
|
println!("Total loading time: {:?}", total_start.elapsed());
|
||||||
dynamic_image.apply_orientation(orientation);
|
ImflowImageBuffer {
|
||||||
let buffer = dynamic_image.as_rgba8().unwrap();
|
width,
|
||||||
let (width, height) = swap_wh(width, height, orientation);
|
height,
|
||||||
let orientation_time = orientation_start.elapsed();
|
rgba_buffer,
|
||||||
|
rating,
|
||||||
|
orientation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reinterpret to avoid copying
|
fn vec_u8_to_u32(buffer: Vec<u8>) -> Vec<u32> {
|
||||||
let rgba_buffer = unsafe {
|
let rgba_buffer = unsafe {
|
||||||
Vec::from_raw_parts(
|
Vec::from_raw_parts(
|
||||||
buffer.as_ptr() as *mut u32,
|
buffer.as_ptr() as *mut u32,
|
||||||
@ -214,32 +210,27 @@ pub fn load_image(image: &ImageData) -> ImflowImageBuffer {
|
|||||||
buffer.len() / 4,
|
buffer.len() / 4,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
std::mem::forget(dynamic_image);
|
std::mem::forget(buffer);
|
||||||
let total_time = total_start.elapsed();
|
rgba_buffer
|
||||||
println!("Orientation time: {:?}", orientation_time);
|
// bytemuck::cast_vec(buffer)
|
||||||
println!("Total loading time: {:?}", total_time);
|
}
|
||||||
ImflowImageBuffer {
|
|
||||||
width,
|
fn vec_u32_to_u8(buffer: Vec<u32>) -> Vec<u8> {
|
||||||
height,
|
let rgba_buffer = unsafe {
|
||||||
rgba_buffer,
|
Vec::from_raw_parts(
|
||||||
rating,
|
buffer.as_ptr() as *mut u8,
|
||||||
}
|
buffer.len() * 4,
|
||||||
}
|
buffer.len() * 4,
|
||||||
}
|
)
|
||||||
|
};
|
||||||
|
std::mem::forget(buffer);
|
||||||
|
rgba_buffer
|
||||||
|
// bytemuck::cast_vec(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn image_to_rgba_buffer(img: DynamicImage) -> Vec<u32> {
|
pub fn image_to_rgba_buffer(img: DynamicImage) -> Vec<u32> {
|
||||||
let flat = img.to_rgba8();
|
let flat: ImageBuffer<Rgba<u8>, Vec<u8>> = img.to_rgba8();
|
||||||
let mut buffer = flat.to_vec();
|
vec_u8_to_u32(flat.into_vec())
|
||||||
let vec = unsafe {
|
|
||||||
Vec::from_raw_parts(
|
|
||||||
buffer.as_mut_ptr() as *mut u32,
|
|
||||||
buffer.len() / 4,
|
|
||||||
buffer.len() / 4,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
mem::forget(buffer);
|
|
||||||
vec
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_available_images(dir: PathBuf) -> Vec<ImageData> {
|
pub fn load_available_images(dir: PathBuf) -> Vec<ImageData> {
|
||||||
@ -277,44 +268,41 @@ pub fn load_available_images(dir: PathBuf) -> Vec<ImageData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_embedded_thumbnail(path: &PathBuf) -> bool {
|
pub fn check_embedded_thumbnail(path: &PathBuf) -> bool {
|
||||||
if let Ok(meta) = Metadata::new_from_path(&path) {
|
Metadata::new_from_path(path).map_or(false, |meta| meta.get_preview_images().is_some())
|
||||||
meta.get_preview_images().is_some()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_embedded_thumbnail(image: &ImageData) -> Option<Vec<u8>> {
|
pub fn get_embedded_thumbnail(image: &ImageData) -> Option<Vec<u8>> {
|
||||||
let meta = Metadata::new_from_path(&image.path);
|
Metadata::new_from_path(&image.path)
|
||||||
match meta {
|
.ok()?
|
||||||
Ok(meta) => {
|
.get_preview_images()?
|
||||||
if let Some(previews) = meta.get_preview_images() {
|
.first()
|
||||||
for preview in previews {
|
.and_then(|preview| preview.get_data().ok())
|
||||||
return Some(preview.get_data().unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_thumbnail(path: &ImageData) -> ImflowImageBuffer {
|
pub fn load_thumbnail(path: &ImageData) -> ImflowImageBuffer {
|
||||||
let cache_path = path.get_cache_path();
|
let cache_path = path.get_cache_path();
|
||||||
|
let mut buffer: Option<Vec<u8>> = None;
|
||||||
if cache_path.exists() {
|
if cache_path.exists() {
|
||||||
let bytes = fs::read(cache_path).unwrap();
|
let read_bytes = fs::read(&cache_path).unwrap();
|
||||||
|
if read_bytes.len() != 0 {
|
||||||
|
buffer = Some(read_bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(bytes) = buffer {
|
||||||
let width = u32::from_le_bytes(bytes[..4].try_into().unwrap()) as usize;
|
let width = u32::from_le_bytes(bytes[..4].try_into().unwrap()) as usize;
|
||||||
let height = u32::from_le_bytes(bytes[4..8].try_into().unwrap()) as usize;
|
let height = u32::from_le_bytes(bytes[4..8].try_into().unwrap()) as usize;
|
||||||
let buffer: &[u8] = &bytes[8..];
|
let orientation =
|
||||||
let mut buffer_u8 = buffer.to_vec();
|
Orientation::from_exif(u32::from_le_bytes(bytes[8..12].try_into().unwrap()) as u8)
|
||||||
|
.unwrap_or(Orientation::NoTransforms);
|
||||||
|
let (ptr, len, cap) = bytes.into_raw_parts();
|
||||||
|
assert!(ptr.align_offset(4) == 0);
|
||||||
let buffer_u32 = unsafe {
|
let buffer_u32 = unsafe {
|
||||||
Vec::from_raw_parts(
|
Vec::from_raw_parts(
|
||||||
buffer_u8.as_mut_ptr() as *mut u32,
|
(ptr as usize + 12) as *mut u32,
|
||||||
buffer_u8.len() / 4,
|
(len - 12) / 4,
|
||||||
buffer_u8.len() / 4,
|
(cap - 12) / 4,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
std::mem::forget(buffer_u8);
|
|
||||||
|
|
||||||
assert_eq!(width * height, buffer_u32.len());
|
assert_eq!(width * height, buffer_u32.len());
|
||||||
|
|
||||||
@ -323,12 +311,13 @@ pub fn load_thumbnail(path: &ImageData) -> ImflowImageBuffer {
|
|||||||
height,
|
height,
|
||||||
rgba_buffer: buffer_u32,
|
rgba_buffer: buffer_u32,
|
||||||
rating: 0,
|
rating: 0,
|
||||||
|
orientation,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let thumbnail = if path.format == ImageFormat::Heif {
|
let thumbnail = if path.format == ImageFormat::Heif {
|
||||||
load_heif(path, true)
|
load_heif(path, true)
|
||||||
} else {
|
} else {
|
||||||
load_thumbnail_exif(path).unwrap_or(load_thumbnail_full(path))
|
load_thumbnail_exif(path).unwrap_or_else(|| load_thumbnail_full(path))
|
||||||
};
|
};
|
||||||
|
|
||||||
save_thumbnail(&cache_path, thumbnail.clone());
|
save_thumbnail(&cache_path, thumbnail.clone());
|
||||||
@ -336,37 +325,27 @@ pub fn load_thumbnail(path: &ImageData) -> ImflowImageBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_thumbnail_exif(path: &ImageData) -> Option<ImflowImageBuffer> {
|
pub fn load_thumbnail_exif(path: &ImageData) -> Option<ImflowImageBuffer> {
|
||||||
match get_embedded_thumbnail(path) {
|
if let Some(thumbnail) = get_embedded_thumbnail(path) {
|
||||||
Some(thumbnail) => {
|
|
||||||
let decoder = image::ImageReader::new(Cursor::new(thumbnail))
|
let decoder = image::ImageReader::new(Cursor::new(thumbnail))
|
||||||
.with_guessed_format()
|
.with_guessed_format()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut image = decoder.decode().unwrap();
|
let image = decoder.decode().unwrap();
|
||||||
|
|
||||||
image.apply_orientation(path.orientation);
|
let orientation = path.orientation;
|
||||||
let width: usize = image.width() as usize;
|
let width: usize = image.width() as usize;
|
||||||
let height: usize = image.height() as usize;
|
let height: usize = image.height() as usize;
|
||||||
let flat = image.into_rgba8().into_raw();
|
let rgba_buffer = image_to_rgba_buffer(image);
|
||||||
let mut buffer = flat.to_vec();
|
|
||||||
let buffer_u32 = unsafe {
|
|
||||||
Vec::from_raw_parts(
|
|
||||||
buffer.as_mut_ptr() as *mut u32,
|
|
||||||
buffer.len() / 4,
|
|
||||||
buffer.len() / 4,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
std::mem::forget(buffer);
|
|
||||||
|
|
||||||
let rating = get_rating(path.into());
|
let rating = get_rating(path.into());
|
||||||
|
|
||||||
Some(ImflowImageBuffer {
|
Some(ImflowImageBuffer {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
rgba_buffer: buffer_u32,
|
rgba_buffer,
|
||||||
rating,
|
rating,
|
||||||
|
orientation,
|
||||||
})
|
})
|
||||||
}
|
} else {
|
||||||
_ => None,
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,17 +357,21 @@ pub fn load_thumbnail_full(path: &ImageData) -> ImflowImageBuffer {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.decode()
|
.decode()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.resize_to_fill(1920, 1920, FilterType::Lanczos3);
|
.resize_to_fill(720, 720, FilterType::Nearest);
|
||||||
let width = image.width() as usize;
|
let width = image.width() as usize;
|
||||||
let height = image.height() as usize;
|
let height = image.height() as usize;
|
||||||
|
let start = std::time::Instant::now();
|
||||||
let buffer = image_to_rgba_buffer(image);
|
let buffer = image_to_rgba_buffer(image);
|
||||||
|
println!("Elapsed: {:?}", start.elapsed());
|
||||||
let rating = get_rating(path.into());
|
let rating = get_rating(path.into());
|
||||||
|
let orientation = path.orientation;
|
||||||
|
|
||||||
ImflowImageBuffer {
|
ImflowImageBuffer {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
rgba_buffer: buffer,
|
rgba_buffer: buffer,
|
||||||
rating,
|
rating,
|
||||||
|
orientation,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -446,18 +429,25 @@ pub fn load_heif(path: &ImageData, resize: bool) -> ImflowImageBuffer {
|
|||||||
assert_eq!(interleaved_plane.storage_bits_per_pixel, 32);
|
assert_eq!(interleaved_plane.storage_bits_per_pixel, 32);
|
||||||
|
|
||||||
let rgba_buffer = interleaved_plane.data;
|
let rgba_buffer = interleaved_plane.data;
|
||||||
let u32_slice = unsafe {
|
let u32_slice = slice_u8_to_u32(rgba_buffer);
|
||||||
std::slice::from_raw_parts(rgba_buffer.as_ptr() as *const u32, rgba_buffer.len() / 4)
|
|
||||||
};
|
|
||||||
|
|
||||||
ImflowImageBuffer {
|
ImflowImageBuffer {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
rgba_buffer: u32_slice.to_vec(),
|
rgba_buffer: u32_slice.to_vec(),
|
||||||
rating,
|
rating,
|
||||||
|
// TODO: verify
|
||||||
|
orientation: path.orientation,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn slice_u8_to_u32(rgba_buffer: &[u8]) -> &[u32] {
|
||||||
|
let u32_slice = unsafe {
|
||||||
|
std::slice::from_raw_parts(rgba_buffer.as_ptr() as *const u32, rgba_buffer.len() / 4)
|
||||||
|
};
|
||||||
|
u32_slice
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_file_hash(path: &PathBuf) -> GenericArray<u8, U32> {
|
pub fn get_file_hash(path: &PathBuf) -> GenericArray<u8, U32> {
|
||||||
let mut file = File::open(path).unwrap();
|
let mut file = File::open(path).unwrap();
|
||||||
let mut buf = [0u8; 16 * 1024];
|
let mut buf = [0u8; 16 * 1024];
|
||||||
@ -474,18 +464,11 @@ pub fn save_thumbnail(path: &PathBuf, image: ImflowImageBuffer) {
|
|||||||
if !cache_dir.exists() {
|
if !cache_dir.exists() {
|
||||||
fs::create_dir(cache_dir).unwrap();
|
fs::create_dir(cache_dir).unwrap();
|
||||||
}
|
}
|
||||||
println!("path: {:?}", path);
|
|
||||||
let mut file = File::create(path).unwrap();
|
let mut file = File::create(path).unwrap();
|
||||||
let buffer = image.rgba_buffer;
|
let u8_buffer = vec_u32_to_u8(image.rgba_buffer);
|
||||||
let u8_buffer = unsafe {
|
|
||||||
Vec::from_raw_parts(
|
|
||||||
buffer.as_ptr() as *mut u8,
|
|
||||||
buffer.len() * 4,
|
|
||||||
buffer.len() * 4,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
std::mem::forget(buffer);
|
|
||||||
file.write(&(image.width as u32).to_le_bytes()).unwrap();
|
file.write(&(image.width as u32).to_le_bytes()).unwrap();
|
||||||
file.write(&(image.height as u32).to_le_bytes()).unwrap();
|
file.write(&(image.height as u32).to_le_bytes()).unwrap();
|
||||||
|
file.write(&(image.orientation.to_exif() as u32).to_le_bytes())
|
||||||
|
.unwrap();
|
||||||
file.write(&u8_buffer).unwrap();
|
file.write(&u8_buffer).unwrap();
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
|
#![feature(vec_into_raw_parts)]
|
||||||
pub mod image;
|
pub mod image;
|
||||||
pub mod store;
|
pub mod store;
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
struct Transforms {
|
struct Transforms {
|
||||||
transform: mat4x4<f32>,
|
transform: mat4x4<f32>,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32
|
height: u32,
|
||||||
|
orientation: u32
|
||||||
};
|
};
|
||||||
@group(0) @binding(2) var<uniform> transforms: Transforms;
|
@group(0) @binding(2) var<uniform> transforms: Transforms;
|
||||||
|
|
||||||
@ -26,11 +27,24 @@ fn vs_main(in: VertexInput) -> VertexOutput {
|
|||||||
@group(0) @binding(0) var texture: texture_2d<f32>;
|
@group(0) @binding(0) var texture: texture_2d<f32>;
|
||||||
@group(0) @binding(1) var texture_sampler: sampler;
|
@group(0) @binding(1) var texture_sampler: sampler;
|
||||||
|
|
||||||
|
fn reverse(in: vec2<f32>) -> vec2<f32> {
|
||||||
|
return vec2<f32>(in.y, in.x);
|
||||||
|
}
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
|
fn fs_main(@location(0) in: vec2<f32>) -> @location(0) vec4<f32> {
|
||||||
let texture_size = vec2<f32>(f32(transforms.width), f32(transforms.height));
|
var texture_size = vec2<f32>(f32(transforms.width), f32(transforms.height));
|
||||||
let out_dim = vec2<f32>(textureDimensions(texture));
|
let out_dim = vec2<f32>(textureDimensions(texture));
|
||||||
|
var uv = in;
|
||||||
|
if transforms.orientation == 2 {
|
||||||
|
uv.x = 1.0-uv.x;
|
||||||
|
} else if transforms.orientation == 3 {
|
||||||
|
uv.y = 1.0-uv.y;
|
||||||
|
}
|
||||||
let scale = texture_size / out_dim;
|
let scale = texture_size / out_dim;
|
||||||
let pixel = uv * scale;
|
var pixel = uv * scale;
|
||||||
|
if transforms.orientation == 3 {
|
||||||
|
pixel = reverse(pixel);
|
||||||
|
}
|
||||||
return textureSample(texture, texture_sampler, pixel);
|
return textureSample(texture, texture_sampler, pixel);
|
||||||
}
|
}
|
||||||
|
47
src/store.rs
47
src/store.rs
@ -1,24 +1,25 @@
|
|||||||
use crate::image::{ImageData, load_thumbnail};
|
use crate::image::{ImageData, load_thumbnail};
|
||||||
use crate::image::{ImflowImageBuffer, load_available_images, load_image};
|
use crate::image::{ImflowImageBuffer, load_available_images, load_image};
|
||||||
|
use crossbeam_channel::{Receiver, Sender, unbounded};
|
||||||
|
use rayon::prelude::*;
|
||||||
use rexiv2::Metadata;
|
use rexiv2::Metadata;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::mpsc;
|
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use threadpool::ThreadPool;
|
use threadpool::ThreadPool;
|
||||||
|
|
||||||
const PRELOAD_NEXT_IMAGE_N: usize = 16;
|
const PRELOAD_NEXT_IMAGE_N: usize = 16;
|
||||||
|
|
||||||
pub struct ImageStore {
|
pub struct ImageStore {
|
||||||
pub(crate) current_image_id: usize,
|
pub current_image_id: usize,
|
||||||
pub(crate) loaded_images: HashMap<ImageData, ImflowImageBuffer>,
|
pub(crate) loaded_images: HashMap<ImageData, ImflowImageBuffer>,
|
||||||
pub(crate) loaded_images_thumbnails: HashMap<ImageData, ImflowImageBuffer>,
|
pub(crate) loaded_images_thumbnails: HashMap<ImageData, ImflowImageBuffer>,
|
||||||
pub(crate) available_images: Vec<ImageData>,
|
pub available_images: Vec<ImageData>,
|
||||||
pub current_image_path: ImageData,
|
pub current_image_path: ImageData,
|
||||||
pub(crate) pool: ThreadPool,
|
pub(crate) pool: ThreadPool,
|
||||||
pub(crate) loader_rx: mpsc::Receiver<(ImageData, ImflowImageBuffer)>,
|
pub(crate) loader_rx: Receiver<(ImageData, ImflowImageBuffer)>,
|
||||||
pub(crate) loader_tx: mpsc::Sender<(ImageData, ImflowImageBuffer)>,
|
pub(crate) loader_tx: Sender<(ImageData, ImflowImageBuffer)>,
|
||||||
pub(crate) currently_loading: HashSet<ImageData>,
|
pub(crate) currently_loading: HashSet<ImageData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,27 +27,33 @@ impl ImageStore {
|
|||||||
pub fn new(path: PathBuf) -> Self {
|
pub fn new(path: PathBuf) -> Self {
|
||||||
let current_image_id: usize = 0;
|
let current_image_id: usize = 0;
|
||||||
let mut loaded_images: HashMap<ImageData, ImflowImageBuffer> = HashMap::new();
|
let mut loaded_images: HashMap<ImageData, ImflowImageBuffer> = HashMap::new();
|
||||||
let mut loaded_thumbnails: HashMap<ImageData, ImflowImageBuffer> = HashMap::new();
|
// let mut loaded_thumbnails: HashMap<ImageData, ImflowImageBuffer> = HashMap::new();
|
||||||
let available_images = load_available_images(path);
|
let available_images = load_available_images(path);
|
||||||
let new_path = available_images[0].clone();
|
let new_path = available_images[0].clone();
|
||||||
|
|
||||||
let (loader_tx, loader_rx) = mpsc::channel();
|
let (loader_tx, loader_rx) = unbounded();
|
||||||
|
|
||||||
let pool = ThreadPool::new(32);
|
let pool = ThreadPool::new(32);
|
||||||
|
|
||||||
let currently_loading = HashSet::new();
|
let currently_loading = HashSet::new();
|
||||||
|
|
||||||
|
let first_image_path = available_images[0].clone();
|
||||||
|
let first_image_thread = std::thread::spawn(move || {
|
||||||
|
let image = load_image(&first_image_path);
|
||||||
|
(first_image_path, image)
|
||||||
|
});
|
||||||
|
|
||||||
let total_start = Instant::now();
|
let total_start = Instant::now();
|
||||||
let mut loaded = 0;
|
let (sender, receiver) = unbounded();
|
||||||
let to_load = available_images.len();
|
available_images
|
||||||
for path in &available_images {
|
.par_iter()
|
||||||
|
.for_each_with(sender, |s, path| {
|
||||||
if path.embedded_thumbnail {
|
if path.embedded_thumbnail {
|
||||||
let buf = load_thumbnail(path);
|
let buf = load_thumbnail(path);
|
||||||
loaded_thumbnails.insert(path.clone(), buf);
|
s.send((path.clone(), buf)).unwrap();
|
||||||
println!("Loaded embedded thumbnail for: {}/{}", loaded, to_load);
|
|
||||||
}
|
|
||||||
loaded += 1;
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
let loaded_thumbnails: HashMap<_, _> = receiver.iter().collect();
|
||||||
let total_time = total_start.elapsed();
|
let total_time = total_start.elapsed();
|
||||||
println!(
|
println!(
|
||||||
"all thumbnails load time: {:?} for {}",
|
"all thumbnails load time: {:?} for {}",
|
||||||
@ -54,8 +61,10 @@ impl ImageStore {
|
|||||||
loaded_thumbnails.len()
|
loaded_thumbnails.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
let path = available_images[0].clone();
|
// let path = available_images[0].clone();
|
||||||
let image = load_image(&path.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);
|
loaded_images.insert(path, image);
|
||||||
let mut state = Self {
|
let mut state = Self {
|
||||||
current_image_id,
|
current_image_id,
|
||||||
@ -121,6 +130,7 @@ impl ImageStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_load(&mut self, path: ImageData) {
|
pub fn request_load(&mut self, path: ImageData) {
|
||||||
|
// return;
|
||||||
if self.loaded_images.contains_key(&path) || self.currently_loading.contains(&path) {
|
if self.loaded_images.contains_key(&path) || self.currently_loading.contains(&path) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -161,6 +171,11 @@ impl ImageStore {
|
|||||||
self.loaded_images.get(path)
|
self.loaded_images.get(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_thumbnail_id(&self, id: usize) -> &ImflowImageBuffer {
|
||||||
|
let path = self.available_images.get(id).unwrap();
|
||||||
|
self.loaded_images_thumbnails.get(path).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_thumbnail(&mut self) -> &ImflowImageBuffer {
|
pub fn get_thumbnail(&mut self) -> &ImflowImageBuffer {
|
||||||
if self
|
if self
|
||||||
.loaded_images_thumbnails
|
.loaded_images_thumbnails
|
||||||
|
Loading…
x
Reference in New Issue
Block a user