Orientation fix, WIP JXL
This commit is contained in:
parent
b2dc693d0e
commit
417a2373c1
@ -1,15 +1,57 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use std::any::Any;
|
||||
use std::fs::{File, read};
|
||||
use std::io::{BufReader, Cursor};
|
||||
use std::iter;
|
||||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
use criterion::{AxisScale, BenchmarkId, PlotConfiguration};
|
||||
use criterion::{Criterion, black_box, criterion_group, criterion_main};
|
||||
use image::codecs::jpeg::JpegDecoder;
|
||||
use image::metadata::Orientation;
|
||||
use image::{DynamicImage, ImageResult, RgbaImage};
|
||||
use imflow::image::{
|
||||
load_available_images, load_image, load_thumbnail_exif, load_thumbnail_full
|
||||
ImflowImageBuffer, get_orientation, get_rating, image_to_rgba_buffer, load_available_images,
|
||||
load_image, load_thumbnail_exif, load_thumbnail_full,
|
||||
};
|
||||
use zune_image::codecs::jpeg::JpegDecoder as ZuneJpegDecoder;
|
||||
use zune_image::codecs::qoi::zune_core::colorspace::ColorSpace;
|
||||
use zune_image::codecs::qoi::zune_core::options::DecoderOptions;
|
||||
const PATH: &str = "test_images";
|
||||
|
||||
/// Create a new decoder that decodes from the stream ```r```
|
||||
// pub fn new(r: R) -> ImageResult<JpegDecoder<R>> {
|
||||
// let mut input = Vec::new();
|
||||
// let mut r = r;
|
||||
// r.read_to_end(&mut input)?;
|
||||
// let options = DecoderOptions::default()
|
||||
// .set_strict_mode(false)
|
||||
// .set_max_width(usize::MAX)
|
||||
// .set_max_height(usize::MAX);
|
||||
// let mut decoder = ZuneJpegDecoder::new_with_options(input.as_slice(), options);
|
||||
// decoder.decode_headers().map_err(ImageError::from_jpeg)?;
|
||||
// // now that we've decoded the headers we can `.unwrap()`
|
||||
// // all these functions that only fail if called before decoding the headers
|
||||
// let (width, height) = decoder.dimensions().unwrap();
|
||||
// // JPEG can only express dimensions up to 65535x65535, so this conversion cannot fail
|
||||
// let width: u16 = width.try_into().unwrap();
|
||||
// let height: u16 = height.try_into().unwrap();
|
||||
// let orig_color_space = decoder.get_output_colorspace().unwrap();
|
||||
// // Limits are disabled by default in the constructor for all decoders
|
||||
// let limits = Limits::no_limits();
|
||||
// Ok(JpegDecoder {
|
||||
// input,
|
||||
// orig_color_space,
|
||||
// width,
|
||||
// height,
|
||||
// limits,
|
||||
// orientation: None,
|
||||
// phantom: PhantomData,
|
||||
// })
|
||||
// }
|
||||
// pub fn full_load_benchmark(c: &mut Criterion) {
|
||||
// let mut group = c.benchmark_group("image_decode");
|
||||
|
||||
@ -33,6 +75,106 @@ const PATH: &str = "test_images";
|
||||
|
||||
// group.finish();
|
||||
// }
|
||||
fn load_a(path: &PathBuf) -> ImflowImageBuffer {
|
||||
let file = read(path.clone()).unwrap();
|
||||
let mut decoder = ZuneJpegDecoder::new(&file);
|
||||
let options = DecoderOptions::new_fast().jpeg_set_out_colorspace(ColorSpace::RGBA);
|
||||
decoder.set_options(options);
|
||||
|
||||
decoder.decode_headers().unwrap();
|
||||
let info = decoder.info().unwrap();
|
||||
let width = info.width as usize;
|
||||
let height = info.height as usize;
|
||||
|
||||
let mut buffer: Vec<u8> = vec![0; width * height * 4];
|
||||
decoder.decode_into(buffer.as_mut_slice()).unwrap();
|
||||
|
||||
// Reinterpret to avoid copying
|
||||
let buffer_u32 = unsafe {
|
||||
Vec::from_raw_parts(
|
||||
buffer.as_mut_ptr() as *mut u32,
|
||||
buffer.len() / 4,
|
||||
buffer.capacity() / 4,
|
||||
)
|
||||
};
|
||||
std::mem::forget(buffer);
|
||||
|
||||
// let total_time = total_start.elapsed();
|
||||
// println!("Total loading time: {:?}", total_time);
|
||||
|
||||
let rating = get_rating(path);
|
||||
|
||||
ImflowImageBuffer {
|
||||
width,
|
||||
height,
|
||||
rgba_buffer: buffer_u32,
|
||||
rating,
|
||||
}
|
||||
}
|
||||
|
||||
fn load_b(path: &PathBuf) -> ImflowImageBuffer {
|
||||
let file = read(path.clone()).unwrap();
|
||||
let mut decoder = ZuneJpegDecoder::new(&file);
|
||||
let options = DecoderOptions::new_fast().jpeg_set_out_colorspace(ColorSpace::RGBA);
|
||||
decoder.set_options(options);
|
||||
|
||||
decoder.decode_headers().unwrap();
|
||||
let info = decoder.info().unwrap();
|
||||
let width = info.width as usize;
|
||||
let height = info.height as usize;
|
||||
|
||||
let mut buffer: Vec<u8> = vec![0; width * height * 4];
|
||||
decoder.decode_into(buffer.as_mut_slice()).unwrap();
|
||||
|
||||
let image = RgbaImage::from_raw(width as u32, height as u32, buffer).unwrap();
|
||||
let orientation = Orientation::from_exif(get_orientation(path)).unwrap();
|
||||
let mut dynamic_image = DynamicImage::from(image);
|
||||
dynamic_image.apply_orientation(orientation);
|
||||
|
||||
let rating = get_rating(path);
|
||||
|
||||
let mut buffer = dynamic_image.to_rgba8();
|
||||
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);
|
||||
|
||||
ImflowImageBuffer {
|
||||
width,
|
||||
height,
|
||||
rgba_buffer: buffer_u32,
|
||||
rating,
|
||||
}
|
||||
}
|
||||
// fn load_b(path: &PathBuf) -> ImflowImageBuffer {
|
||||
// println!("path: {:?}", path);
|
||||
// // let file = read(path.clone()).unwrap();
|
||||
// let file = BufReader::new(File::open(path).unwrap());
|
||||
// let decoder = image::ImageReader::new(file).unwrap();
|
||||
// let options = DecoderOptions::new_fast().jpeg_set_out_colorspace(ColorSpace::RGBA);
|
||||
// decoder.set_options(options);
|
||||
// let image = reader
|
||||
// .with_guessed_format()
|
||||
// .unwrap()
|
||||
// .decode()
|
||||
// .unwrap();
|
||||
// let width = image.width() as usize;
|
||||
// let height = image.height() as usize;
|
||||
// // let buffer = image_to_rgba_buffer(image);
|
||||
// let im = RgbaImage::from_raw(width, height, image.as_rgba8()).unwrap();
|
||||
// let rating = get_rating(path.into());
|
||||
|
||||
// ImflowImageBuffer {
|
||||
// width,
|
||||
// height,
|
||||
// rgba_buffer: buffer,
|
||||
// rating,
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn thumbnail_load_benchmark(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("thumbnail");
|
||||
@ -57,5 +199,29 @@ pub fn thumbnail_load_benchmark(c: &mut Criterion) {
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(benches, thumbnail_load_benchmark);
|
||||
pub fn file_load_benchmark(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("image_load");
|
||||
|
||||
group
|
||||
.sample_size(10)
|
||||
.measurement_time(Duration::from_millis(500))
|
||||
.warm_up_time(Duration::from_millis(200));
|
||||
|
||||
let images = load_available_images(PATH.into());
|
||||
group.bench_function("zune_jpeg", |b| {
|
||||
for image in images.iter().take(10) {
|
||||
b.iter(|| load_a(image));
|
||||
}
|
||||
});
|
||||
group.bench_function("image_rs", |b| {
|
||||
for image in images.iter().take(10) {
|
||||
b.iter(|| load_b(image));
|
||||
}
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// criterion_group!(benches, thumbnail_load_benchmark);
|
||||
criterion_group!(benches, file_load_benchmark);
|
||||
criterion_main!(benches);
|
||||
|
20
src/app.rs
20
src/app.rs
@ -34,14 +34,16 @@ pub(crate) struct TransformData {
|
||||
height: u32,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
fn create_transform_matrix(data: &TransformData, scale_x: f32, scale_y: f32) -> [f32; 16] {
|
||||
const ZOOM_MULTIPLIER: f32 = 3.0;
|
||||
let zoom = data.zoom.powf(ZOOM_MULTIPLIER);
|
||||
|
||||
[
|
||||
zoom * scale_x, 0.0, 0.0, 0.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,
|
||||
zoom * scale_x, 0.0, 0.0, 0.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,
|
||||
]
|
||||
}
|
||||
|
||||
@ -338,8 +340,8 @@ impl App {
|
||||
|
||||
async fn set_window(&mut self, window: Window) {
|
||||
let window = Arc::new(window);
|
||||
let initial_width = 1500;
|
||||
let initial_height = 1000;
|
||||
let initial_height = 1200;
|
||||
let initial_width = (initial_height as f32 * 1.5) as u32;
|
||||
|
||||
let _ = window.request_inner_size(PhysicalSize::new(initial_width, initial_height));
|
||||
|
||||
@ -366,7 +368,6 @@ impl App {
|
||||
}
|
||||
|
||||
fn handle_resized(&mut self, width: u32, height: u32) {
|
||||
println!("Resized {} {}", width, height);
|
||||
if width > 0 && height > 0 {
|
||||
self.state.as_mut().unwrap().resize_surface(width, height);
|
||||
}
|
||||
@ -378,7 +379,6 @@ impl App {
|
||||
|
||||
state.store.check_loaded_images();
|
||||
let imbuf = if let Some(full) = state.store.get_current_image() {
|
||||
// println!("full");
|
||||
full
|
||||
} else {
|
||||
state.store.get_thumbnail()
|
||||
@ -392,7 +392,6 @@ impl App {
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
state.transform_data.width = width;
|
||||
state.transform_data.height = height;
|
||||
|
||||
@ -422,7 +421,8 @@ impl App {
|
||||
pub fn pan_zoom(&mut self, zoom_delta: f32, pan_x: f32, pan_y: f32) {
|
||||
let state = self.state.as_mut().unwrap();
|
||||
|
||||
let image_aspect_ratio = (state.transform_data.width as f32) / (state.transform_data.height as f32);
|
||||
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;
|
||||
|
@ -1,6 +1,6 @@
|
||||
use egui::Context;
|
||||
use egui_wgpu::wgpu::{CommandEncoder, Device, Queue, StoreOp, TextureFormat, TextureView};
|
||||
use egui_wgpu::{wgpu, Renderer, ScreenDescriptor};
|
||||
use egui_wgpu::{Renderer, ScreenDescriptor, wgpu};
|
||||
use egui_winit::State;
|
||||
use winit::event::WindowEvent;
|
||||
use winit::window::Window;
|
||||
|
119
src/image.rs
119
src/image.rs
@ -1,17 +1,22 @@
|
||||
use iced::widget::image::Handle;
|
||||
use image::DynamicImage;
|
||||
use image::RgbaImage;
|
||||
use image::imageops::FilterType;
|
||||
use image::metadata::Orientation;
|
||||
use libheif_rs::{HeifContext, LibHeif, RgbChroma};
|
||||
use rexiv2::Metadata;
|
||||
use zune_image::codecs::jpeg::JpegDecoder;
|
||||
use zune_image::codecs::jpeg_xl::JxlDecoder;
|
||||
use zune_image::codecs::qoi::zune_core::colorspace::ColorSpace;
|
||||
use zune_image::codecs::qoi::zune_core::options::DecoderOptions;
|
||||
use zune_image::traits::DecoderTrait;
|
||||
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::fs::read;
|
||||
use std::io::BufReader;
|
||||
use std::io::Cursor;
|
||||
use std::mem;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Instant;
|
||||
|
||||
@ -26,7 +31,7 @@ pub fn create_iced_handle(width: u32, height: u32, rgba: Vec<u8>) -> Handle {
|
||||
Handle::from_rgba(width, height, rgba)
|
||||
}
|
||||
|
||||
fn get_rating(filename: &PathBuf) -> i32 {
|
||||
pub fn get_rating(filename: &PathBuf) -> i32 {
|
||||
// // Use xmp-toolkit for video files
|
||||
// if is_video(&filename) {
|
||||
// return Ok(read_rating_xmp(filename.clone()).unwrap_or(0));
|
||||
@ -43,6 +48,34 @@ fn get_rating(filename: &PathBuf) -> i32 {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_orientation(filename: &PathBuf) -> u8 {
|
||||
// // Use xmp-toolkit for video files
|
||||
// if is_video(&filename) {
|
||||
// return Ok(read_rating_xmp(filename.clone()).unwrap_or(0));
|
||||
// }
|
||||
|
||||
// Use rexiv2 for image files
|
||||
let meta = Metadata::new_from_path(filename);
|
||||
match meta {
|
||||
Ok(meta) => meta.get_orientation() as u8,
|
||||
Err(e) => panic!("{:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
fn swap_wh<T>(width: T, height: T, orientation: Orientation) -> (T, T) {
|
||||
if [
|
||||
Orientation::Rotate90,
|
||||
Orientation::Rotate270,
|
||||
Orientation::Rotate90FlipH,
|
||||
Orientation::Rotate270FlipH,
|
||||
]
|
||||
.contains(&orientation)
|
||||
{
|
||||
return (height, width);
|
||||
}
|
||||
(width, height)
|
||||
}
|
||||
|
||||
pub fn load_image(path: &PathBuf) -> ImflowImageBuffer {
|
||||
let total_start = Instant::now();
|
||||
|
||||
@ -53,25 +86,50 @@ pub fn load_image(path: &PathBuf) -> ImflowImageBuffer {
|
||||
return img;
|
||||
}
|
||||
|
||||
let file = read(path.clone()).unwrap();
|
||||
let mut decoder = JpegDecoder::new(&file);
|
||||
if is_jxl(path) {}
|
||||
|
||||
let options = DecoderOptions::new_fast().jpeg_set_out_colorspace(ColorSpace::RGBA);
|
||||
decoder.set_options(options);
|
||||
|
||||
decoder.decode_headers().unwrap();
|
||||
let info = decoder.info().unwrap();
|
||||
let width = info.width as usize;
|
||||
let height = info.height as usize;
|
||||
let mut buffer: Vec<u8>;
|
||||
let width: usize;
|
||||
let height: usize;
|
||||
if is_jxl(path) {
|
||||
let file = BufReader::new(File::open(path).unwrap());
|
||||
let mut decoder = JxlDecoder::try_new(file, options).unwrap();
|
||||
let image =
|
||||
<JxlDecoder<std::io::BufReader<File>> as DecoderTrait<&[u8]>>::decode(&mut decoder)
|
||||
.unwrap();
|
||||
(width, height) = image.dimensions();
|
||||
buffer = (*image.flatten_to_u8().get(0).unwrap().clone()).to_vec();
|
||||
println!("buffer len: {} {} {}", buffer.len(), width, height);
|
||||
} else {
|
||||
let file = read(path.clone()).unwrap();
|
||||
let mut decoder = JpegDecoder::new(&file);
|
||||
decoder.set_options(options);
|
||||
|
||||
let mut buffer: Vec<u8> = vec![0; width * height * 4];
|
||||
decoder.decode_into(buffer.as_mut_slice()).unwrap();
|
||||
decoder.decode_headers().unwrap();
|
||||
let info = decoder.info().unwrap();
|
||||
width = info.width as usize;
|
||||
height = info.height as usize;
|
||||
buffer = vec![0; width * height * 4];
|
||||
decoder.decode_into(buffer.as_mut_slice()).unwrap();
|
||||
};
|
||||
|
||||
// TODO: Optimize rotation
|
||||
// let orientation =
|
||||
// Orientation::from_exif(get_orientation(path)).unwrap_or(Orientation::NoTransforms);
|
||||
// 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 mut buffer = dynamic_image.to_rgba8();
|
||||
// let (width, height) = swap_wh(width, height, orientation);
|
||||
|
||||
// Reinterpret to avoid copying
|
||||
let buffer_u32 = unsafe {
|
||||
Vec::from_raw_parts(
|
||||
buffer.as_mut_ptr() as *mut u32,
|
||||
buffer.len() / 4,
|
||||
buffer.capacity() / 4,
|
||||
buffer.len() / 4,
|
||||
)
|
||||
};
|
||||
std::mem::forget(buffer);
|
||||
@ -92,13 +150,15 @@ pub fn load_image(path: &PathBuf) -> ImflowImageBuffer {
|
||||
pub fn image_to_rgba_buffer(img: DynamicImage) -> Vec<u32> {
|
||||
let flat = img.to_rgba8();
|
||||
let mut buffer = flat.to_vec();
|
||||
unsafe {
|
||||
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<PathBuf> {
|
||||
@ -130,7 +190,7 @@ fn is_image(path: &PathBuf) -> bool {
|
||||
if !path.is_file() {
|
||||
return false;
|
||||
}
|
||||
["jpg", "heic", "heif"].contains(
|
||||
["jpg", "jxl", "heic", "heif"].contains(
|
||||
&path
|
||||
.extension()
|
||||
.unwrap()
|
||||
@ -151,6 +211,17 @@ fn is_heif(path: &PathBuf) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
fn is_jxl(path: &PathBuf) -> bool {
|
||||
["jxl", "jpgxl"].contains(
|
||||
&path
|
||||
.extension()
|
||||
.unwrap()
|
||||
.to_ascii_lowercase()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn load_thumbnail(path: &PathBuf) -> ImflowImageBuffer {
|
||||
if is_heif(path) {
|
||||
return load_heif(path, true);
|
||||
@ -268,3 +339,23 @@ pub fn load_heif(path: &PathBuf, resize: bool) -> ImflowImageBuffer {
|
||||
rating,
|
||||
}
|
||||
}
|
||||
|
||||
// fn load_jxl(path: &PathBuf) -> ImflowImageBuffer {
|
||||
// let file = BufReader::new(File::open(path).unwrap());
|
||||
// let decoder = JxlDecoder::try_new(file, DecoderOptions::new_fast()).unwrap();
|
||||
// // let reader = image::ImageReader::new(file);
|
||||
// let image = decoder
|
||||
// .decode()
|
||||
// .unwrap();
|
||||
// let width = image.width() as usize;
|
||||
// let height = image.height() as usize;
|
||||
// let buffer = image_to_rgba_buffer(image);
|
||||
// let rating = get_rating(path.into());
|
||||
|
||||
// ImflowImageBuffer {
|
||||
// width,
|
||||
// height,
|
||||
// rgba_buffer: buffer,
|
||||
// rating,
|
||||
// }
|
||||
// }
|
||||
|
@ -323,7 +323,6 @@ mod egui_tools;
|
||||
use winit::event_loop::{ControlFlow, EventLoop};
|
||||
|
||||
fn main() {
|
||||
|
||||
let args = Args::parse();
|
||||
let path = args.path.unwrap_or("./test_images".into());
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@ -379,7 +378,6 @@ async fn run(path: PathBuf) {
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
struct MyApp {
|
||||
// image: Image,
|
||||
store: ImageStore,
|
||||
@ -388,10 +386,7 @@ struct MyApp {
|
||||
|
||||
impl MyApp {
|
||||
fn new(store: ImageStore, texture: TextureHandle) -> Self {
|
||||
Self {
|
||||
store,
|
||||
texture,
|
||||
}
|
||||
Self { store, texture }
|
||||
}
|
||||
}
|
||||
|
||||
@ -425,7 +420,7 @@ struct Args {
|
||||
}
|
||||
|
||||
// fn init_app() {
|
||||
|
||||
|
||||
// let mut store = ImageStore::new("./test_images".into());
|
||||
|
||||
// let mut imbuf = store.get_current_image().unwrap();
|
||||
|
@ -84,7 +84,10 @@ impl ImageStore {
|
||||
if let Some(full) = self.loaded_images.get_mut(&self.current_image_path.clone()) {
|
||||
full.rating = rating;
|
||||
}
|
||||
if let Some(thumbnail) = self.loaded_images_thumbnails.get_mut(&self.current_image_path.clone()) {
|
||||
if let Some(thumbnail) = self
|
||||
.loaded_images_thumbnails
|
||||
.get_mut(&self.current_image_path.clone())
|
||||
{
|
||||
thumbnail.rating = rating;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user