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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
@ -1433,6 +1442,7 @@ dependencies = [
|
||||
"bytemuck",
|
||||
"clap 4.5.34",
|
||||
"criterion",
|
||||
"crossbeam-channel",
|
||||
"egui",
|
||||
"egui-wgpu",
|
||||
"egui-winit",
|
||||
@ -1441,10 +1451,12 @@ dependencies = [
|
||||
"jpegxl-rs",
|
||||
"libheif-rs",
|
||||
"pollster",
|
||||
"rayon",
|
||||
"rexiv2",
|
||||
"sha2",
|
||||
"threadpool",
|
||||
"winit",
|
||||
"zerocopy 0.8.24",
|
||||
"zune-image",
|
||||
]
|
||||
|
||||
|
@ -22,6 +22,9 @@ rexiv2 = "0.10.0"
|
||||
threadpool = "1.8.1"
|
||||
bytemuck = "1.22.0"
|
||||
sha2 = "0.10.8"
|
||||
zerocopy = "0.8.24"
|
||||
crossbeam-channel = "0.5.15"
|
||||
rayon = "1.10.0"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
|
259
src/app.rs
259
src/app.rs
@ -1,11 +1,16 @@
|
||||
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::{ScreenDescriptor, wgpu};
|
||||
use image::metadata::Orientation;
|
||||
use imflow::image::swap_wh;
|
||||
use imflow::store::ImageStore;
|
||||
use std::cmp::{max, min};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::process::exit;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use wgpu::util::DeviceExt;
|
||||
use wgpu::{PipelineCompilationOptions, SurfaceConfiguration};
|
||||
use winit::application::ApplicationHandler;
|
||||
@ -15,14 +20,13 @@ use winit::event_loop::ActiveEventLoop;
|
||||
use winit::platform::x11::WindowAttributesExtX11;
|
||||
use winit::window::{Window, WindowId};
|
||||
|
||||
// Uniforms for transformations
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Transforms {
|
||||
transform: [f32; 16], // 4x4 matrix
|
||||
width: u32,
|
||||
height: u32,
|
||||
_padding1: u32,
|
||||
orientation: u32,
|
||||
_padding2: u32,
|
||||
}
|
||||
|
||||
@ -32,6 +36,7 @@ pub(crate) struct TransformData {
|
||||
zoom: f32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
orientation: Orientation,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
@ -212,7 +217,7 @@ pub struct AppState {
|
||||
pub surface: wgpu::Surface<'static>,
|
||||
pub scale_factor: f32,
|
||||
pub egui_renderer: EguiRenderer,
|
||||
pub store: ImageStore,
|
||||
pub store: Arc<RwLock<ImageStore>>,
|
||||
pub image_texture: wgpu::Texture,
|
||||
pub bind_group: wgpu::BindGroup,
|
||||
pub render_pipeline: wgpu::RenderPipeline,
|
||||
@ -276,9 +281,13 @@ impl AppState {
|
||||
|
||||
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) =
|
||||
// setup_texture(&device, surface_config.clone(), 6000, 4000);
|
||||
@ -290,6 +299,7 @@ impl AppState {
|
||||
zoom: 1.0,
|
||||
width: 10000,
|
||||
height: 10000,
|
||||
orientation: Orientation::NoTransforms,
|
||||
};
|
||||
|
||||
Self {
|
||||
@ -313,6 +323,10 @@ impl AppState {
|
||||
self.surface_config.height = height;
|
||||
self.surface.configure(&self.device, &self.surface_config);
|
||||
}
|
||||
|
||||
// fn get_store(&mut self) -> &ImageStore {
|
||||
// &self.store.lock().unwrap()
|
||||
// }
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
@ -358,7 +372,7 @@ impl App {
|
||||
self.window.get_or_insert(window);
|
||||
self.state.get_or_insert(state);
|
||||
|
||||
self.pan_zoom(0.0, 0.0, 0.0);
|
||||
self.reset_transform();
|
||||
self.update_texture();
|
||||
}
|
||||
|
||||
@ -372,11 +386,13 @@ impl App {
|
||||
pub fn update_texture(&mut self) {
|
||||
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
|
||||
} else {
|
||||
state.store.get_thumbnail()
|
||||
store.get_thumbnail()
|
||||
};
|
||||
let width = imbuf.width as u32;
|
||||
let height = imbuf.height as u32;
|
||||
@ -389,6 +405,7 @@ impl App {
|
||||
|
||||
state.transform_data.width = width;
|
||||
state.transform_data.height = height;
|
||||
state.transform_data.orientation = imbuf.orientation;
|
||||
|
||||
state.queue.write_texture(
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
@ -409,15 +426,20 @@ impl App {
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
self.pan_zoom(0.0, 0.0, 0.0);
|
||||
self.update_transform();
|
||||
}
|
||||
|
||||
fn update_transform(&mut self) {
|
||||
let state = self.state.as_mut().unwrap();
|
||||
|
||||
let image_aspect_ratio =
|
||||
(state.transform_data.width as f32) / (state.transform_data.height as f32);
|
||||
let (width, height) = swap_wh(
|
||||
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_aspect_ratio = window_size.width as f32 / window_size.height as f32;
|
||||
let mut scale_x = 1.0;
|
||||
@ -433,9 +455,9 @@ impl App {
|
||||
0,
|
||||
bytemuck::cast_slice(&[Transforms {
|
||||
transform,
|
||||
width: state.transform_data.width,
|
||||
height: state.transform_data.height,
|
||||
_padding1: 0,
|
||||
width: width as u32,
|
||||
height: height as u32,
|
||||
orientation: state.transform_data.orientation as u32,
|
||||
_padding2: 0,
|
||||
}]),
|
||||
);
|
||||
@ -603,10 +625,21 @@ impl App {
|
||||
render_pass.draw_indexed(0..6, 0, 0..1);
|
||||
}
|
||||
|
||||
let rating = state.store.get_current_rating();
|
||||
let path = state.store.current_image_path.clone();
|
||||
let filename = path.path.file_name().unwrap();
|
||||
let window = self.window.as_ref().unwrap();
|
||||
let rating;
|
||||
let path;
|
||||
let current_id;
|
||||
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);
|
||||
|
||||
@ -617,7 +650,7 @@ impl App {
|
||||
.show(state.egui_renderer.context(), |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(
|
||||
egui::RichText::new(format!("{:.1}", rating))
|
||||
egui::RichText::new(format!("{}", rating))
|
||||
.size(42.0)
|
||||
.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.device,
|
||||
@ -668,14 +754,25 @@ impl ApplicationHandler for App {
|
||||
}
|
||||
WindowEvent::RedrawRequested => {
|
||||
self.handle_redraw();
|
||||
let (events, _keys_down, pointer) = self
|
||||
let (events, _keys_down, pointer, scroll) = self
|
||||
.state
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.egui_renderer
|
||||
.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| {
|
||||
if let Event::Key { key, pressed, .. } = e {
|
||||
if !*pressed {
|
||||
@ -683,49 +780,54 @@ impl ApplicationHandler for App {
|
||||
}
|
||||
match *key {
|
||||
Key::ArrowLeft => {
|
||||
self.state.as_mut().unwrap().store.next_image(-1);
|
||||
self.update_texture();
|
||||
store.next_image(-1);
|
||||
updated_image = true;
|
||||
}
|
||||
Key::ArrowRight => {
|
||||
self.state.as_mut().unwrap().store.next_image(1);
|
||||
self.update_texture();
|
||||
store.next_image(1);
|
||||
updated_image = true;
|
||||
}
|
||||
Key::ArrowUp => {
|
||||
let rating =
|
||||
self.state.as_mut().unwrap().store.get_current_rating();
|
||||
self.state.as_mut().unwrap().store.set_rating(rating + 1);
|
||||
let rating = store.get_current_rating();
|
||||
store.set_rating(rating + 1);
|
||||
}
|
||||
Key::ArrowDown => {
|
||||
let rating =
|
||||
self.state.as_mut().unwrap().store.get_current_rating();
|
||||
self.state.as_mut().unwrap().store.set_rating(rating - 1);
|
||||
let rating = store.get_current_rating();
|
||||
store.set_rating(rating - 1);
|
||||
}
|
||||
Key::Backtick => self.state.as_mut().unwrap().store.set_rating(0),
|
||||
Key::Num0 => self.state.as_mut().unwrap().store.set_rating(0),
|
||||
Key::Num1 => self.state.as_mut().unwrap().store.set_rating(1),
|
||||
Key::Num2 => self.state.as_mut().unwrap().store.set_rating(2),
|
||||
Key::Num3 => self.state.as_mut().unwrap().store.set_rating(3),
|
||||
Key::Num4 => self.state.as_mut().unwrap().store.set_rating(4),
|
||||
Key::Num5 => self.state.as_mut().unwrap().store.set_rating(5),
|
||||
Key::Backtick => store.set_rating(0),
|
||||
Key::Num0 => store.set_rating(0),
|
||||
Key::Num1 => store.set_rating(1),
|
||||
Key::Num2 => store.set_rating(2),
|
||||
Key::Num3 => store.set_rating(3),
|
||||
Key::Num4 => store.set_rating(4),
|
||||
Key::Num5 => store.set_rating(5),
|
||||
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 {
|
||||
button, pressed, ..
|
||||
} = e
|
||||
{
|
||||
if *pressed && *button == PointerButton::Secondary {
|
||||
self.reset_transform();
|
||||
reset_transform = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
if pointer.primary_down() && pointer.is_moving() {
|
||||
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();
|
||||
}
|
||||
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!()
|
||||
}
|
||||
}
|
||||
|
207
src/image.rs
207
src/image.rs
@ -1,5 +1,6 @@
|
||||
use image::DynamicImage;
|
||||
use image::RgbaImage;
|
||||
use image::ImageBuffer;
|
||||
use image::Rgba;
|
||||
use image::imageops::FilterType;
|
||||
use image::metadata::Orientation;
|
||||
use itertools::Itertools;
|
||||
@ -25,7 +26,6 @@ use std::io::BufReader;
|
||||
use std::io::Cursor;
|
||||
use std::io::Read;
|
||||
use std::io::Write;
|
||||
use std::mem;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::time::Instant;
|
||||
@ -79,29 +79,24 @@ pub struct ImflowImageBuffer {
|
||||
pub height: usize,
|
||||
pub rgba_buffer: Vec<u32>,
|
||||
pub rating: i32,
|
||||
pub orientation: Orientation,
|
||||
}
|
||||
|
||||
pub fn get_rating(image: &ImageData) -> i32 {
|
||||
let meta = Metadata::new_from_path(&image.path);
|
||||
match meta {
|
||||
Ok(meta) => {
|
||||
let rating = meta.get_tag_numeric("Xmp.xmp.Rating");
|
||||
rating
|
||||
}
|
||||
Err(e) => panic!("{:?}", e),
|
||||
if let Ok(meta) = Metadata::new_from_path(&image.path) {
|
||||
meta.get_tag_numeric("Xmp.xmp.Rating")
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_orientation(path: &PathBuf) -> Orientation {
|
||||
let meta = Metadata::new_from_path(path);
|
||||
match meta {
|
||||
Ok(meta) => Orientation::from_exif(meta.get_orientation() as u8)
|
||||
.unwrap_or(Orientation::NoTransforms),
|
||||
Err(_) => Orientation::NoTransforms,
|
||||
}
|
||||
Metadata::new_from_path(path).map_or(Orientation::NoTransforms, |meta| {
|
||||
Orientation::from_exif(meta.get_orientation() as u8).unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
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 [
|
||||
Orientation::Rotate90,
|
||||
Orientation::Rotate270,
|
||||
@ -161,15 +156,11 @@ pub fn load_image(image: &ImageData) -> ImflowImageBuffer {
|
||||
let (metadata, buffer) = decoder.decode_with::<u8>(&file).unwrap();
|
||||
let width = metadata.width as usize;
|
||||
let height = metadata.height as usize;
|
||||
// TODO: convert
|
||||
// let orientation = metadata.orientation;
|
||||
let orientation = Orientation::NoTransforms;
|
||||
|
||||
let rgba_buffer = unsafe {
|
||||
Vec::from_raw_parts(
|
||||
buffer.as_ptr() as *mut u32,
|
||||
buffer.len() / 4,
|
||||
buffer.len() / 4,
|
||||
)
|
||||
};
|
||||
std::mem::forget(buffer);
|
||||
let rgba_buffer = vec_u8_to_u32(buffer);
|
||||
|
||||
println!("Total JXL loading time: {:?}", total_start.elapsed());
|
||||
|
||||
@ -178,6 +169,7 @@ pub fn load_image(image: &ImageData) -> ImflowImageBuffer {
|
||||
height,
|
||||
rgba_buffer,
|
||||
rating,
|
||||
orientation,
|
||||
}
|
||||
}
|
||||
ImageFormat::Jpg => {
|
||||
@ -196,17 +188,21 @@ pub fn load_image(image: &ImageData) -> ImflowImageBuffer {
|
||||
buffer = vec![0; width * height * 4];
|
||||
decoder.decode_into(buffer.as_mut_slice()).unwrap();
|
||||
|
||||
let orientation_start = Instant::now();
|
||||
// TODO: Optimize rotation
|
||||
let orientation = image.orientation;
|
||||
let image = RgbaImage::from_raw(width as u32, height as u32, buffer).unwrap();
|
||||
let mut dynamic_image = DynamicImage::from(image);
|
||||
dynamic_image.apply_orientation(orientation);
|
||||
let buffer = dynamic_image.as_rgba8().unwrap();
|
||||
let (width, height) = swap_wh(width, height, orientation);
|
||||
let orientation_time = orientation_start.elapsed();
|
||||
let rgba_buffer = vec_u8_to_u32(buffer);
|
||||
println!("Total loading time: {:?}", total_start.elapsed());
|
||||
ImflowImageBuffer {
|
||||
width,
|
||||
height,
|
||||
rgba_buffer,
|
||||
rating,
|
||||
orientation,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reinterpret to avoid copying
|
||||
fn vec_u8_to_u32(buffer: Vec<u8>) -> Vec<u32> {
|
||||
let rgba_buffer = unsafe {
|
||||
Vec::from_raw_parts(
|
||||
buffer.as_ptr() as *mut u32,
|
||||
@ -214,32 +210,27 @@ pub fn load_image(image: &ImageData) -> ImflowImageBuffer {
|
||||
buffer.len() / 4,
|
||||
)
|
||||
};
|
||||
std::mem::forget(dynamic_image);
|
||||
let total_time = total_start.elapsed();
|
||||
println!("Orientation time: {:?}", orientation_time);
|
||||
println!("Total loading time: {:?}", total_time);
|
||||
ImflowImageBuffer {
|
||||
width,
|
||||
height,
|
||||
rgba_buffer,
|
||||
rating,
|
||||
}
|
||||
}
|
||||
std::mem::forget(buffer);
|
||||
rgba_buffer
|
||||
// bytemuck::cast_vec(buffer)
|
||||
}
|
||||
|
||||
fn vec_u32_to_u8(buffer: Vec<u32>) -> Vec<u8> {
|
||||
let rgba_buffer = unsafe {
|
||||
Vec::from_raw_parts(
|
||||
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> {
|
||||
let flat = img.to_rgba8();
|
||||
let mut buffer = flat.to_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
|
||||
let flat: ImageBuffer<Rgba<u8>, Vec<u8>> = img.to_rgba8();
|
||||
vec_u8_to_u32(flat.into_vec())
|
||||
}
|
||||
|
||||
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 {
|
||||
if let Ok(meta) = Metadata::new_from_path(&path) {
|
||||
meta.get_preview_images().is_some()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
Metadata::new_from_path(path).map_or(false, |meta| meta.get_preview_images().is_some())
|
||||
}
|
||||
|
||||
pub fn get_embedded_thumbnail(image: &ImageData) -> Option<Vec<u8>> {
|
||||
let meta = Metadata::new_from_path(&image.path);
|
||||
match meta {
|
||||
Ok(meta) => {
|
||||
if let Some(previews) = meta.get_preview_images() {
|
||||
for preview in previews {
|
||||
return Some(preview.get_data().unwrap());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
Metadata::new_from_path(&image.path)
|
||||
.ok()?
|
||||
.get_preview_images()?
|
||||
.first()
|
||||
.and_then(|preview| preview.get_data().ok())
|
||||
}
|
||||
|
||||
pub fn load_thumbnail(path: &ImageData) -> ImflowImageBuffer {
|
||||
let cache_path = path.get_cache_path();
|
||||
let mut buffer: Option<Vec<u8>> = None;
|
||||
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 height = u32::from_le_bytes(bytes[4..8].try_into().unwrap()) as usize;
|
||||
let buffer: &[u8] = &bytes[8..];
|
||||
let mut buffer_u8 = buffer.to_vec();
|
||||
let orientation =
|
||||
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 {
|
||||
Vec::from_raw_parts(
|
||||
buffer_u8.as_mut_ptr() as *mut u32,
|
||||
buffer_u8.len() / 4,
|
||||
buffer_u8.len() / 4,
|
||||
(ptr as usize + 12) as *mut u32,
|
||||
(len - 12) / 4,
|
||||
(cap - 12) / 4,
|
||||
)
|
||||
};
|
||||
std::mem::forget(buffer_u8);
|
||||
|
||||
assert_eq!(width * height, buffer_u32.len());
|
||||
|
||||
@ -323,12 +311,13 @@ pub fn load_thumbnail(path: &ImageData) -> ImflowImageBuffer {
|
||||
height,
|
||||
rgba_buffer: buffer_u32,
|
||||
rating: 0,
|
||||
orientation,
|
||||
};
|
||||
}
|
||||
let thumbnail = if path.format == ImageFormat::Heif {
|
||||
load_heif(path, true)
|
||||
} 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());
|
||||
@ -336,37 +325,27 @@ pub fn load_thumbnail(path: &ImageData) -> ImflowImageBuffer {
|
||||
}
|
||||
|
||||
pub fn load_thumbnail_exif(path: &ImageData) -> Option<ImflowImageBuffer> {
|
||||
match get_embedded_thumbnail(path) {
|
||||
Some(thumbnail) => {
|
||||
if let Some(thumbnail) = get_embedded_thumbnail(path) {
|
||||
let decoder = image::ImageReader::new(Cursor::new(thumbnail))
|
||||
.with_guessed_format()
|
||||
.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 height: usize = image.height() as usize;
|
||||
let flat = image.into_rgba8().into_raw();
|
||||
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 rgba_buffer = image_to_rgba_buffer(image);
|
||||
let rating = get_rating(path.into());
|
||||
|
||||
Some(ImflowImageBuffer {
|
||||
width,
|
||||
height,
|
||||
rgba_buffer: buffer_u32,
|
||||
rgba_buffer,
|
||||
rating,
|
||||
orientation,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@ -378,17 +357,21 @@ pub fn load_thumbnail_full(path: &ImageData) -> ImflowImageBuffer {
|
||||
.unwrap()
|
||||
.decode()
|
||||
.unwrap()
|
||||
.resize_to_fill(1920, 1920, FilterType::Lanczos3);
|
||||
.resize_to_fill(720, 720, FilterType::Nearest);
|
||||
let width = image.width() as usize;
|
||||
let height = image.height() as usize;
|
||||
let start = std::time::Instant::now();
|
||||
let buffer = image_to_rgba_buffer(image);
|
||||
println!("Elapsed: {:?}", start.elapsed());
|
||||
let rating = get_rating(path.into());
|
||||
let orientation = path.orientation;
|
||||
|
||||
ImflowImageBuffer {
|
||||
width,
|
||||
height,
|
||||
rgba_buffer: buffer,
|
||||
rating,
|
||||
orientation,
|
||||
}
|
||||
}
|
||||
|
||||
@ -446,18 +429,25 @@ pub fn load_heif(path: &ImageData, resize: bool) -> ImflowImageBuffer {
|
||||
assert_eq!(interleaved_plane.storage_bits_per_pixel, 32);
|
||||
|
||||
let rgba_buffer = interleaved_plane.data;
|
||||
let u32_slice = unsafe {
|
||||
std::slice::from_raw_parts(rgba_buffer.as_ptr() as *const u32, rgba_buffer.len() / 4)
|
||||
};
|
||||
let u32_slice = slice_u8_to_u32(rgba_buffer);
|
||||
|
||||
ImflowImageBuffer {
|
||||
width,
|
||||
height,
|
||||
rgba_buffer: u32_slice.to_vec(),
|
||||
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> {
|
||||
let mut file = File::open(path).unwrap();
|
||||
let mut buf = [0u8; 16 * 1024];
|
||||
@ -474,18 +464,11 @@ pub fn save_thumbnail(path: &PathBuf, image: ImflowImageBuffer) {
|
||||
if !cache_dir.exists() {
|
||||
fs::create_dir(cache_dir).unwrap();
|
||||
}
|
||||
println!("path: {:?}", path);
|
||||
let mut file = File::create(path).unwrap();
|
||||
let buffer = 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);
|
||||
let u8_buffer = vec_u32_to_u8(image.rgba_buffer);
|
||||
file.write(&(image.width 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();
|
||||
}
|
||||
|
@ -1,2 +1,3 @@
|
||||
#![feature(vec_into_raw_parts)]
|
||||
pub mod image;
|
||||
pub mod store;
|
||||
|
@ -1,7 +1,8 @@
|
||||
struct Transforms {
|
||||
transform: mat4x4<f32>,
|
||||
width: u32,
|
||||
height: u32
|
||||
height: u32,
|
||||
orientation: u32
|
||||
};
|
||||
@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(1) var texture_sampler: sampler;
|
||||
|
||||
fn reverse(in: vec2<f32>) -> vec2<f32> {
|
||||
return vec2<f32>(in.y, in.x);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
|
||||
let texture_size = vec2<f32>(f32(transforms.width), f32(transforms.height));
|
||||
fn fs_main(@location(0) in: vec2<f32>) -> @location(0) vec4<f32> {
|
||||
var texture_size = vec2<f32>(f32(transforms.width), f32(transforms.height));
|
||||
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 pixel = uv * scale;
|
||||
var pixel = uv * scale;
|
||||
if transforms.orientation == 3 {
|
||||
pixel = reverse(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::{ImflowImageBuffer, load_available_images, load_image};
|
||||
use crossbeam_channel::{Receiver, Sender, unbounded};
|
||||
use rayon::prelude::*;
|
||||
use rexiv2::Metadata;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc;
|
||||
use std::time::Instant;
|
||||
use threadpool::ThreadPool;
|
||||
|
||||
const PRELOAD_NEXT_IMAGE_N: usize = 16;
|
||||
|
||||
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_thumbnails: HashMap<ImageData, ImflowImageBuffer>,
|
||||
pub(crate) available_images: Vec<ImageData>,
|
||||
pub available_images: Vec<ImageData>,
|
||||
pub current_image_path: ImageData,
|
||||
pub(crate) pool: ThreadPool,
|
||||
pub(crate) loader_rx: mpsc::Receiver<(ImageData, ImflowImageBuffer)>,
|
||||
pub(crate) loader_tx: mpsc::Sender<(ImageData, ImflowImageBuffer)>,
|
||||
pub(crate) loader_rx: Receiver<(ImageData, ImflowImageBuffer)>,
|
||||
pub(crate) loader_tx: Sender<(ImageData, ImflowImageBuffer)>,
|
||||
pub(crate) currently_loading: HashSet<ImageData>,
|
||||
}
|
||||
|
||||
@ -26,27 +27,33 @@ 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 mut loaded_thumbnails: HashMap<ImageData, ImflowImageBuffer> = HashMap::new();
|
||||
let available_images = load_available_images(path);
|
||||
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 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 mut loaded = 0;
|
||||
let to_load = available_images.len();
|
||||
for path in &available_images {
|
||||
let (sender, receiver) = unbounded();
|
||||
available_images
|
||||
.par_iter()
|
||||
.for_each_with(sender, |s, path| {
|
||||
if path.embedded_thumbnail {
|
||||
let buf = load_thumbnail(path);
|
||||
loaded_thumbnails.insert(path.clone(), buf);
|
||||
println!("Loaded embedded thumbnail for: {}/{}", loaded, to_load);
|
||||
}
|
||||
loaded += 1;
|
||||
s.send((path.clone(), buf)).unwrap();
|
||||
}
|
||||
});
|
||||
let loaded_thumbnails: HashMap<_, _> = receiver.iter().collect();
|
||||
let total_time = total_start.elapsed();
|
||||
println!(
|
||||
"all thumbnails load time: {:?} for {}",
|
||||
@ -54,8 +61,10 @@ impl ImageStore {
|
||||
loaded_thumbnails.len()
|
||||
);
|
||||
|
||||
let path = available_images[0].clone();
|
||||
let image = load_image(&path.clone());
|
||||
// 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 {
|
||||
current_image_id,
|
||||
@ -121,6 +130,7 @@ impl ImageStore {
|
||||
}
|
||||
|
||||
pub fn request_load(&mut self, path: ImageData) {
|
||||
// return;
|
||||
if self.loaded_images.contains_key(&path) || self.currently_loading.contains(&path) {
|
||||
return;
|
||||
}
|
||||
@ -161,6 +171,11 @@ impl ImageStore {
|
||||
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 {
|
||||
if self
|
||||
.loaded_images_thumbnails
|
||||
|
Loading…
x
Reference in New Issue
Block a user