Working HEIF, working aspect ratio, resize
This commit is contained in:
parent
ed66f17139
commit
b2dc693d0e
48
Cargo.lock
generated
48
Cargo.lock
generated
@ -1553,6 +1553,17 @@ dependencies = [
|
|||||||
"syn 2.0.100",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enumn"
|
||||||
|
version = "0.1.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.100",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_filter"
|
name = "env_filter"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
@ -1788,6 +1799,12 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "four-cc"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "795cbfc56d419a7ce47ccbb7504dd9a5b7c484c083c356e797de08bd988d9629"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
@ -2666,6 +2683,7 @@ dependencies = [
|
|||||||
"iced",
|
"iced",
|
||||||
"image 0.25.6",
|
"image 0.25.6",
|
||||||
"itertools 0.12.1",
|
"itertools 0.12.1",
|
||||||
|
"libheif-rs",
|
||||||
"memmap2",
|
"memmap2",
|
||||||
"minifb",
|
"minifb",
|
||||||
"pollster",
|
"pollster",
|
||||||
@ -3019,6 +3037,30 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libheif-rs"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e4a26370abb4723a3ce73083e479b98017604206cadb0e35da5eac4813600d85"
|
||||||
|
dependencies = [
|
||||||
|
"enumn",
|
||||||
|
"four-cc",
|
||||||
|
"libc",
|
||||||
|
"libheif-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libheif-sys"
|
||||||
|
version = "3.1.0+1.18.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e663db80d4272b60c066c5a9d17370ffa0433a31d424152f95f1e1effb9b3860"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libloading"
|
name = "libloading"
|
||||||
version = "0.7.4"
|
version = "0.7.4"
|
||||||
@ -5363,6 +5405,12 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version-compare"
|
name = "version-compare"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -31,6 +31,7 @@ wgpu = "24.0.3"
|
|||||||
# winit = "0.30.9"
|
# winit = "0.30.9"
|
||||||
zune-image = {version = "0.4.15", features = ["all"]}
|
zune-image = {version = "0.4.15", features = ["all"]}
|
||||||
bytemuck = "1.22.0"
|
bytemuck = "1.22.0"
|
||||||
|
libheif-rs = "1.1.0"
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
debug = false
|
debug = false
|
||||||
|
84
src/app.rs
84
src/app.rs
@ -1,14 +1,11 @@
|
|||||||
use crate::egui_tools::EguiRenderer;
|
use crate::egui_tools::EguiRenderer;
|
||||||
use eframe::WindowAttributes;
|
|
||||||
use egui::{Event, Key};
|
use egui::{Event, Key};
|
||||||
use egui_wgpu::wgpu::SurfaceError;
|
use egui_wgpu::wgpu::SurfaceError;
|
||||||
use egui_wgpu::{ScreenDescriptor, wgpu};
|
use egui_wgpu::{ScreenDescriptor, wgpu};
|
||||||
use imflow::store::ImageStore;
|
use imflow::store::ImageStore;
|
||||||
use std::any::Any;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time;
|
|
||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
use wgpu::{PipelineCompilationOptions, SurfaceConfiguration};
|
use wgpu::{PipelineCompilationOptions, SurfaceConfiguration};
|
||||||
use winit::application::ApplicationHandler;
|
use winit::application::ApplicationHandler;
|
||||||
@ -23,20 +20,28 @@ use winit::window::{Window, WindowId};
|
|||||||
#[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,
|
||||||
|
height: u32,
|
||||||
|
_padding1: u32,
|
||||||
|
_padding2: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TransformData {
|
pub(crate) struct TransformData {
|
||||||
pan_x: f32,
|
pan_x: f32,
|
||||||
pan_y: f32,
|
pan_y: f32,
|
||||||
zoom: f32,
|
zoom: f32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_transform_matrix(data: &TransformData) -> [f32; 16] {
|
fn create_transform_matrix(data: &TransformData, scale_x: f32, scale_y: f32) -> [f32; 16] {
|
||||||
const ZOOM_MULTIPLIER: f32 = 3.0;
|
const ZOOM_MULTIPLIER: f32 = 3.0;
|
||||||
let zoom = data.zoom.powf(ZOOM_MULTIPLIER);
|
let zoom = data.zoom.powf(ZOOM_MULTIPLIER);
|
||||||
[
|
[
|
||||||
zoom, 0.0, 0.0, 0.0, 0.0, zoom, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, data.pan_x, data.pan_y, 0.0,
|
zoom * scale_x, 0.0, 0.0, 0.0,
|
||||||
1.0,
|
0.0, zoom * scale_y, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 1.0, 0.0,
|
||||||
|
data.pan_x, data.pan_y, 0.0, 1.0,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +67,7 @@ fn setup_texture(
|
|||||||
mip_level_count: 1,
|
mip_level_count: 1,
|
||||||
sample_count: 1,
|
sample_count: 1,
|
||||||
dimension: wgpu::TextureDimension::D2,
|
dimension: wgpu::TextureDimension::D2,
|
||||||
format: wgpu::TextureFormat::Bgra8UnormSrgb,
|
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||||
view_formats: &[],
|
view_formats: &[],
|
||||||
});
|
});
|
||||||
@ -101,7 +106,7 @@ fn setup_texture(
|
|||||||
},
|
},
|
||||||
wgpu::BindGroupLayoutEntry {
|
wgpu::BindGroupLayoutEntry {
|
||||||
binding: 2,
|
binding: 2,
|
||||||
visibility: wgpu::ShaderStages::VERTEX,
|
visibility: wgpu::ShaderStages::all(),
|
||||||
ty: wgpu::BindingType::Buffer {
|
ty: wgpu::BindingType::Buffer {
|
||||||
ty: wgpu::BufferBindingType::Uniform,
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
has_dynamic_offset: false,
|
has_dynamic_offset: false,
|
||||||
@ -225,7 +230,7 @@ impl AppState {
|
|||||||
window: &Window,
|
window: &Window,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
path: PathBuf
|
path: PathBuf,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let power_pref = wgpu::PowerPreference::default();
|
let power_pref = wgpu::PowerPreference::default();
|
||||||
let adapter = instance
|
let adapter = instance
|
||||||
@ -279,12 +284,15 @@ impl AppState {
|
|||||||
let store = ImageStore::new(path);
|
let store = ImageStore::new(path);
|
||||||
|
|
||||||
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);
|
||||||
|
setup_texture(&device, surface_config.clone(), 8192, 8192);
|
||||||
|
|
||||||
let transform_data = TransformData {
|
let transform_data = TransformData {
|
||||||
pan_x: 0.0,
|
pan_x: 0.0,
|
||||||
pan_y: 0.0,
|
pan_y: 0.0,
|
||||||
zoom: 1.0,
|
zoom: 1.0,
|
||||||
|
width: 10000,
|
||||||
|
height: 10000,
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@ -314,7 +322,7 @@ pub struct App {
|
|||||||
instance: wgpu::Instance,
|
instance: wgpu::Instance,
|
||||||
state: Option<AppState>,
|
state: Option<AppState>,
|
||||||
window: Option<Arc<Window>>,
|
window: Option<Arc<Window>>,
|
||||||
path: PathBuf
|
path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
@ -324,7 +332,7 @@ impl App {
|
|||||||
instance,
|
instance,
|
||||||
state: None,
|
state: None,
|
||||||
window: None,
|
window: None,
|
||||||
path
|
path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,7 +354,7 @@ impl App {
|
|||||||
&window,
|
&window,
|
||||||
initial_width,
|
initial_width,
|
||||||
initial_width,
|
initial_width,
|
||||||
self.path.clone()
|
self.path.clone(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@ -362,6 +370,7 @@ impl App {
|
|||||||
if width > 0 && height > 0 {
|
if width > 0 && height > 0 {
|
||||||
self.state.as_mut().unwrap().resize_surface(width, height);
|
self.state.as_mut().unwrap().resize_surface(width, height);
|
||||||
}
|
}
|
||||||
|
self.pan_zoom(0.0, 0.0, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_texture(&mut self) {
|
pub fn update_texture(&mut self) {
|
||||||
@ -369,7 +378,7 @@ impl App {
|
|||||||
|
|
||||||
state.store.check_loaded_images();
|
state.store.check_loaded_images();
|
||||||
let imbuf = if let Some(full) = state.store.get_current_image() {
|
let imbuf = if let Some(full) = state.store.get_current_image() {
|
||||||
println!("full");
|
// println!("full");
|
||||||
full
|
full
|
||||||
} else {
|
} else {
|
||||||
state.store.get_thumbnail()
|
state.store.get_thumbnail()
|
||||||
@ -378,11 +387,15 @@ impl App {
|
|||||||
let height = imbuf.height as u32;
|
let height = imbuf.height as u32;
|
||||||
let buffer_u8 = unsafe {
|
let buffer_u8 = unsafe {
|
||||||
std::slice::from_raw_parts(
|
std::slice::from_raw_parts(
|
||||||
imbuf.argb_buffer.as_ptr() as *const u8,
|
imbuf.rgba_buffer.as_ptr() as *const u8,
|
||||||
imbuf.argb_buffer.len() * 4,
|
imbuf.rgba_buffer.len() * 4,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
state.transform_data.width = width;
|
||||||
|
state.transform_data.height = height;
|
||||||
|
|
||||||
state.queue.write_texture(
|
state.queue.write_texture(
|
||||||
wgpu::TexelCopyTextureInfo {
|
wgpu::TexelCopyTextureInfo {
|
||||||
texture: &state.image_texture,
|
texture: &state.image_texture,
|
||||||
@ -402,18 +415,38 @@ impl App {
|
|||||||
depth_or_array_layers: 1,
|
depth_or_array_layers: 1,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
self.pan_zoom(0.0, 0.0, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pan_zoom(&mut self, zoom_delta: f32, pan_x: f32, pan_y: f32) {
|
pub fn pan_zoom(&mut self, zoom_delta: f32, pan_x: f32, pan_y: f32) {
|
||||||
let state = self.state.as_mut().unwrap();
|
let state = self.state.as_mut().unwrap();
|
||||||
|
|
||||||
|
let image_aspect_ratio = (state.transform_data.width as f32) / (state.transform_data.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;
|
||||||
|
let mut scale_y = 1.0;
|
||||||
|
if window_aspect_ratio > image_aspect_ratio {
|
||||||
|
scale_x = image_aspect_ratio / window_aspect_ratio;
|
||||||
|
} else {
|
||||||
|
scale_y = window_aspect_ratio / image_aspect_ratio;
|
||||||
|
}
|
||||||
|
|
||||||
state.transform_data.zoom = (state.transform_data.zoom + zoom_delta).clamp(1.0, 20.0);
|
state.transform_data.zoom = (state.transform_data.zoom + zoom_delta).clamp(1.0, 20.0);
|
||||||
state.transform_data.pan_x += pan_x;
|
state.transform_data.pan_x += pan_x;
|
||||||
state.transform_data.pan_y += pan_y;
|
state.transform_data.pan_y += pan_y;
|
||||||
let transform = create_transform_matrix(&state.transform_data);
|
let transform = create_transform_matrix(&state.transform_data, scale_x, scale_y);
|
||||||
state.queue.write_buffer(
|
state.queue.write_buffer(
|
||||||
&state.transform_buffer,
|
&state.transform_buffer,
|
||||||
0,
|
0,
|
||||||
bytemuck::cast_slice(&[Transforms { transform }]),
|
bytemuck::cast_slice(&[Transforms {
|
||||||
|
transform,
|
||||||
|
width: state.transform_data.width,
|
||||||
|
height: state.transform_data.height,
|
||||||
|
_padding1: 0,
|
||||||
|
_padding2: 0,
|
||||||
|
}]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -473,7 +506,7 @@ impl App {
|
|||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||||
r: 0.0,
|
r: 0.0,
|
||||||
g: 1.0, // Green
|
g: 0.0, // Green
|
||||||
b: 0.0,
|
b: 0.0,
|
||||||
a: 1.0,
|
a: 1.0,
|
||||||
}),
|
}),
|
||||||
@ -611,7 +644,7 @@ impl ApplicationHandler for App {
|
|||||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||||
let attributes = Window::default_attributes()
|
let attributes = Window::default_attributes()
|
||||||
.with_base_size(LogicalSize::new(2000, 4000))
|
.with_base_size(LogicalSize::new(2000, 4000))
|
||||||
.with_resizable(false);
|
.with_resizable(true);
|
||||||
let window = event_loop.create_window(attributes).unwrap();
|
let window = event_loop.create_window(attributes).unwrap();
|
||||||
pollster::block_on(self.set_window(window));
|
pollster::block_on(self.set_window(window));
|
||||||
}
|
}
|
||||||
@ -630,7 +663,7 @@ impl ApplicationHandler for App {
|
|||||||
event_loop.exit();
|
event_loop.exit();
|
||||||
}
|
}
|
||||||
WindowEvent::RedrawRequested => {
|
WindowEvent::RedrawRequested => {
|
||||||
let start = time::Instant::now();
|
// let start = time::Instant::now();
|
||||||
self.handle_redraw();
|
self.handle_redraw();
|
||||||
// println!("Updated in: {}ms", start.elapsed().as_millis());
|
// println!("Updated in: {}ms", start.elapsed().as_millis());
|
||||||
// Extract the events by cloning them from the input context
|
// Extract the events by cloning them from the input context
|
||||||
@ -677,12 +710,7 @@ impl ApplicationHandler for App {
|
|||||||
Key::Escape => exit(0),
|
Key::Escape => exit(0),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
} else if let Event::MouseWheel {
|
} else if let Event::MouseWheel { delta, .. } = e {
|
||||||
unit,
|
|
||||||
delta,
|
|
||||||
modifiers,
|
|
||||||
} = e
|
|
||||||
{
|
|
||||||
self.pan_zoom(delta.y * 0.2, 0.0, 0.0);
|
self.pan_zoom(delta.y * 0.2, 0.0, 0.0);
|
||||||
} else if let Event::PointerMoved(pos) = e {
|
} else if let Event::PointerMoved(pos) = e {
|
||||||
if keys_down.contains(&Key::Tab) {
|
if keys_down.contains(&Key::Tab) {
|
||||||
|
151
src/image.rs
151
src/image.rs
@ -1,6 +1,7 @@
|
|||||||
use iced::widget::image::Handle;
|
use iced::widget::image::Handle;
|
||||||
use image::DynamicImage;
|
use image::DynamicImage;
|
||||||
use image::imageops::FilterType;
|
use image::imageops::FilterType;
|
||||||
|
use libheif_rs::{HeifContext, LibHeif, RgbChroma};
|
||||||
use rexiv2::Metadata;
|
use rexiv2::Metadata;
|
||||||
use zune_image::codecs::jpeg::JpegDecoder;
|
use zune_image::codecs::jpeg::JpegDecoder;
|
||||||
use zune_image::codecs::qoi::zune_core::colorspace::ColorSpace;
|
use zune_image::codecs::qoi::zune_core::colorspace::ColorSpace;
|
||||||
@ -17,7 +18,7 @@ use std::time::Instant;
|
|||||||
pub struct ImflowImageBuffer {
|
pub struct ImflowImageBuffer {
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
pub height: usize,
|
pub height: usize,
|
||||||
pub argb_buffer: Vec<u32>,
|
pub rgba_buffer: Vec<u32>,
|
||||||
pub rating: i32,
|
pub rating: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,11 +26,7 @@ pub fn create_iced_handle(width: u32, height: u32, rgba: Vec<u8>) -> Handle {
|
|||||||
Handle::from_rgba(width, height, rgba)
|
Handle::from_rgba(width, height, rgba)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_rating(filename: PathBuf) -> i32 {
|
fn get_rating(filename: &PathBuf) -> i32 {
|
||||||
// if !path_exists(filename.clone()) {
|
|
||||||
// anyhow::bail!("File doesn't exist");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Use xmp-toolkit for video files
|
// // Use xmp-toolkit for video files
|
||||||
// if is_video(&filename) {
|
// if is_video(&filename) {
|
||||||
// return Ok(read_rating_xmp(filename.clone()).unwrap_or(0));
|
// return Ok(read_rating_xmp(filename.clone()).unwrap_or(0));
|
||||||
@ -46,12 +43,19 @@ fn get_rating(filename: PathBuf) -> i32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_image(path: PathBuf) -> ImflowImageBuffer {
|
pub fn load_image(path: &PathBuf) -> ImflowImageBuffer {
|
||||||
let total_start = Instant::now();
|
let total_start = Instant::now();
|
||||||
|
|
||||||
|
if is_heif(path) {
|
||||||
|
let img = load_heif(path, false);
|
||||||
|
let total_time = total_start.elapsed();
|
||||||
|
println!("Total HEIF loading time: {:?}", total_time);
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
|
||||||
let file = read(path.clone()).unwrap();
|
let file = read(path.clone()).unwrap();
|
||||||
let mut decoder = JpegDecoder::new(&file);
|
let mut decoder = JpegDecoder::new(&file);
|
||||||
let options = DecoderOptions::new_fast().jpeg_set_out_colorspace(ColorSpace::BGRA);
|
let options = DecoderOptions::new_fast().jpeg_set_out_colorspace(ColorSpace::RGBA);
|
||||||
decoder.set_options(options);
|
decoder.set_options(options);
|
||||||
|
|
||||||
decoder.decode_headers().unwrap();
|
decoder.decode_headers().unwrap();
|
||||||
@ -80,30 +84,28 @@ pub fn load_image(path: PathBuf) -> ImflowImageBuffer {
|
|||||||
ImflowImageBuffer {
|
ImflowImageBuffer {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
argb_buffer: buffer_u32,
|
rgba_buffer: buffer_u32,
|
||||||
rating,
|
rating,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn image_to_argb_buffer(img: DynamicImage) -> Vec<u32> {
|
pub fn image_to_rgba_buffer(img: DynamicImage) -> Vec<u32> {
|
||||||
let flat = img.into_rgba8();
|
let flat = img.to_rgba8();
|
||||||
let buf = flat.as_raw();
|
let mut buffer = flat.to_vec();
|
||||||
|
unsafe {
|
||||||
buf.chunks_exact(4)
|
Vec::from_raw_parts(
|
||||||
.map(|rgba| {
|
buffer.as_mut_ptr() as *mut u32,
|
||||||
let r = rgba[0] as u32;
|
buffer.len() / 4,
|
||||||
let g = rgba[1] as u32;
|
buffer.len() / 4,
|
||||||
let b = rgba[2] as u32;
|
)
|
||||||
r << 16 | g << 8 | b
|
}
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_available_images(dir: PathBuf) -> Vec<PathBuf> {
|
pub fn load_available_images(dir: PathBuf) -> Vec<PathBuf> {
|
||||||
let mut files: Vec<PathBuf> = fs::read_dir(dir)
|
let mut files: Vec<PathBuf> = fs::read_dir(dir)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.map(|f| f.unwrap().path())
|
.map(|f| f.unwrap().path())
|
||||||
.filter(|f| ["jpg", "heic"].contains(&f.extension().unwrap().to_ascii_lowercase().to_str().unwrap()))
|
.filter(is_image)
|
||||||
.collect();
|
.collect();
|
||||||
files.sort();
|
files.sort();
|
||||||
files
|
files
|
||||||
@ -124,7 +126,35 @@ pub fn get_embedded_thumbnail(path: PathBuf) -> Option<Vec<u8>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_image(path: &PathBuf) -> bool {
|
||||||
|
if !path.is_file() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
["jpg", "heic", "heif"].contains(
|
||||||
|
&path
|
||||||
|
.extension()
|
||||||
|
.unwrap()
|
||||||
|
.to_ascii_lowercase()
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_heif(path: &PathBuf) -> bool {
|
||||||
|
["heif", "heic"].contains(
|
||||||
|
&path
|
||||||
|
.extension()
|
||||||
|
.unwrap()
|
||||||
|
.to_ascii_lowercase()
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load_thumbnail(path: &PathBuf) -> ImflowImageBuffer {
|
pub fn load_thumbnail(path: &PathBuf) -> ImflowImageBuffer {
|
||||||
|
if is_heif(path) {
|
||||||
|
return load_heif(path, true);
|
||||||
|
}
|
||||||
match load_thumbnail_exif(path) {
|
match load_thumbnail_exif(path) {
|
||||||
Some(thumbnail) => return thumbnail,
|
Some(thumbnail) => return thumbnail,
|
||||||
None => load_thumbnail_full(path),
|
None => load_thumbnail_full(path),
|
||||||
@ -141,22 +171,22 @@ pub fn load_thumbnail_exif(path: &PathBuf) -> Option<ImflowImageBuffer> {
|
|||||||
|
|
||||||
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 mut flat = image.into_rgba8().into_raw();
|
let flat = image.into_rgba8().into_raw();
|
||||||
let mut buffer: Vec<u32> = vec![0; width * height];
|
let mut buffer = flat.to_vec();
|
||||||
|
let buffer_u32 = unsafe {
|
||||||
for (rgba, argb) in flat.chunks_mut(4).zip(buffer.iter_mut()) {
|
Vec::from_raw_parts(
|
||||||
let r = rgba[0] as u32;
|
buffer.as_mut_ptr() as *mut u32,
|
||||||
let g = rgba[1] as u32;
|
buffer.len() / 4,
|
||||||
let b = rgba[2] as u32;
|
buffer.len() / 4,
|
||||||
*argb = r << 16 | g << 8 | b;
|
)
|
||||||
}
|
};
|
||||||
|
|
||||||
let rating = get_rating(path.into());
|
let rating = get_rating(path.into());
|
||||||
|
|
||||||
Some(ImflowImageBuffer {
|
Some(ImflowImageBuffer {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
argb_buffer: buffer,
|
rgba_buffer: buffer_u32,
|
||||||
rating,
|
rating,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -175,13 +205,66 @@ pub fn load_thumbnail_full(path: &PathBuf) -> ImflowImageBuffer {
|
|||||||
.resize(640, 480, FilterType::Nearest);
|
.resize(640, 480, 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 buffer = image_to_argb_buffer(image);
|
let buffer = image_to_rgba_buffer(image);
|
||||||
let rating = get_rating(path.into());
|
let rating = get_rating(path.into());
|
||||||
|
|
||||||
ImflowImageBuffer {
|
ImflowImageBuffer {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
argb_buffer: buffer,
|
rgba_buffer: buffer,
|
||||||
|
rating,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_heif(path: &PathBuf, resize: bool) -> ImflowImageBuffer {
|
||||||
|
let lib_heif = LibHeif::new();
|
||||||
|
let ctx = HeifContext::read_from_file(path.to_str().unwrap()).unwrap();
|
||||||
|
let handle = ctx.primary_image_handle().unwrap();
|
||||||
|
// assert_eq!(handle.width(), 1652);
|
||||||
|
// assert_eq!(handle.height(), 1791);
|
||||||
|
|
||||||
|
// Get Exif
|
||||||
|
// let mut meta_ids: Vec<ItemId> = vec![0; 1];
|
||||||
|
// let count = handle.metadata_block_ids(&mut meta_ids, b"Exif");
|
||||||
|
// assert_eq!(count, 1);
|
||||||
|
// let exif: Vec<u8> = handle.metadata(meta_ids[0]).unwrap();
|
||||||
|
|
||||||
|
// Decode the image
|
||||||
|
let mut image = lib_heif
|
||||||
|
.decode(&handle, libheif_rs::ColorSpace::Rgb(RgbChroma::Rgba), None)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
image.color_space(),
|
||||||
|
Some(libheif_rs::ColorSpace::Rgb(RgbChroma::Rgba)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Scale the image
|
||||||
|
if resize {
|
||||||
|
image = image.scale(640, 480, None).unwrap();
|
||||||
|
assert_eq!(image.width(), 640);
|
||||||
|
assert_eq!(image.height(), 480);
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = image.width() as usize;
|
||||||
|
let height = image.height() as usize;
|
||||||
|
let rating = get_rating(path);
|
||||||
|
|
||||||
|
// Get "pixels"
|
||||||
|
let planes = image.planes();
|
||||||
|
let interleaved_plane = planes.interleaved.unwrap();
|
||||||
|
assert!(!interleaved_plane.data.is_empty());
|
||||||
|
assert!(interleaved_plane.stride > 0);
|
||||||
|
|
||||||
|
let rgba_buffer = interleaved_plane.data;
|
||||||
|
// Create a slice of u32 from the u8 slice
|
||||||
|
let u32_slice = unsafe {
|
||||||
|
std::slice::from_raw_parts(rgba_buffer.as_ptr() as *const u32, rgba_buffer.len() / 4)
|
||||||
|
};
|
||||||
|
|
||||||
|
ImflowImageBuffer {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
rgba_buffer: u32_slice.to_vec(),
|
||||||
rating,
|
rating,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
struct Transforms {
|
struct Transforms {
|
||||||
transform: mat4x4<f32>,
|
transform: mat4x4<f32>,
|
||||||
|
width: u32,
|
||||||
|
height: u32
|
||||||
};
|
};
|
||||||
@group(0) @binding(2) var<uniform> transforms: Transforms;
|
@group(0) @binding(2) var<uniform> transforms: Transforms;
|
||||||
|
|
||||||
@ -16,7 +18,6 @@ struct VertexOutput {
|
|||||||
@vertex
|
@vertex
|
||||||
fn vs_main(in: VertexInput) -> VertexOutput {
|
fn vs_main(in: VertexInput) -> VertexOutput {
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
// Apply zoom/transformation matrix
|
|
||||||
out.position = transforms.transform * vec4<f32>(in.position, 1.0);
|
out.position = transforms.transform * vec4<f32>(in.position, 1.0);
|
||||||
out.uv = in.uv;
|
out.uv = in.uv;
|
||||||
return out;
|
return out;
|
||||||
@ -27,5 +28,9 @@ fn vs_main(in: VertexInput) -> VertexOutput {
|
|||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
|
fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
|
||||||
return textureSample(texture, texture_sampler, uv);
|
let texture_size = vec2<f32>(f32(transforms.width), f32(transforms.height));
|
||||||
|
let out_dim = vec2<f32>(textureDimensions(texture));
|
||||||
|
let scale = texture_size / out_dim;
|
||||||
|
let pixel = uv * scale;
|
||||||
|
return textureSample(texture, texture_sampler, pixel);
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ impl ImageStore {
|
|||||||
);
|
);
|
||||||
|
|
||||||
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);
|
loaded_images.insert(path, image);
|
||||||
let mut state = Self {
|
let mut state = Self {
|
||||||
current_image_id,
|
current_image_id,
|
||||||
@ -91,7 +91,7 @@ impl ImageStore {
|
|||||||
|
|
||||||
pub fn get_current_rating(&self) -> i32 {
|
pub fn get_current_rating(&self) -> i32 {
|
||||||
let imbuf = if let Some(full) = self.get_current_image() {
|
let imbuf = if let Some(full) = self.get_current_image() {
|
||||||
println!("full");
|
// println!("full");
|
||||||
full
|
full
|
||||||
} else {
|
} else {
|
||||||
// TODO: this assumes loaded thumbnail
|
// TODO: this assumes loaded thumbnail
|
||||||
@ -122,7 +122,7 @@ impl ImageStore {
|
|||||||
self.currently_loading.insert(path.clone());
|
self.currently_loading.insert(path.clone());
|
||||||
|
|
||||||
self.pool.execute(move || {
|
self.pool.execute(move || {
|
||||||
let image = load_image(path.clone());
|
let image = load_image(&path.clone());
|
||||||
let _ = tx.send((path, image));
|
let _ = tx.send((path, image));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user