Refactor, optimizations
This commit is contained in:
parent
aed4212043
commit
4e42cfdc05
@ -3,44 +3,59 @@
|
||||
use std::iter;
|
||||
use std::time::Duration;
|
||||
|
||||
use criterion::BenchmarkId;
|
||||
use criterion::{AxisScale, BenchmarkId, PlotConfiguration};
|
||||
use criterion::{Criterion, black_box, criterion_group, criterion_main};
|
||||
use imflow::image::{load_available_images, load_image_argb, load_image_argb_imagers, load_thumbnail, Approach};
|
||||
use imflow::image::{
|
||||
load_available_images, load_image, load_thumbnail_exif, load_thumbnail_full
|
||||
};
|
||||
const PATH: &str = "test_images";
|
||||
|
||||
pub fn criterion_benchmark(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("image_decode");
|
||||
// pub fn full_load_benchmark(c: &mut Criterion) {
|
||||
// let mut group = c.benchmark_group("image_decode");
|
||||
|
||||
// group
|
||||
// .sample_size(10)
|
||||
// .measurement_time(Duration::from_millis(500))
|
||||
// .warm_up_time(Duration::from_millis(200));
|
||||
|
||||
// let images = load_available_images(PATH.into());
|
||||
// for image in images.iter() {
|
||||
// let image_name = image.to_str().unwrap();
|
||||
|
||||
// group.bench_with_input(format!("{}/zune", image_name), image, |b, image| {
|
||||
// b.iter(|| load_image_argb(image.clone().into()));
|
||||
// });
|
||||
|
||||
// group.bench_with_input(format!("{}/image-rs", image_name), image, |b, image| {
|
||||
// b.iter(|| load_image_argb_imagers(image.clone().into()));
|
||||
// });
|
||||
// }
|
||||
|
||||
// group.finish();
|
||||
// }
|
||||
|
||||
pub fn thumbnail_load_benchmark(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("thumbnail");
|
||||
|
||||
group
|
||||
.sample_size(10) // Reduce number of samples (default is 100)
|
||||
.measurement_time(Duration::from_millis(500)) // Reduce measurement time (default is 5 seconds)
|
||||
.warm_up_time(Duration::from_millis(200)); // Reduce warm-up time (default is 3 seconds)
|
||||
.sample_size(10)
|
||||
.measurement_time(Duration::from_millis(500))
|
||||
.warm_up_time(Duration::from_millis(200));
|
||||
|
||||
let images = load_available_images(PATH.into());
|
||||
for image in images.iter() {
|
||||
let image_name = image.to_str().unwrap();
|
||||
|
||||
// Benchmark zune for this image
|
||||
group.bench_with_input(
|
||||
format!("{}/zune", image_name),
|
||||
image,
|
||||
|b, image| {
|
||||
b.iter(|| load_image_argb(image.clone().into()));
|
||||
},
|
||||
);
|
||||
|
||||
// Benchmark image-rs for the same image
|
||||
group.bench_with_input(
|
||||
format!("{}/image-rs", image_name),
|
||||
image,
|
||||
|b, image| {
|
||||
b.iter(|| load_image_argb_imagers(image.clone().into()));
|
||||
},
|
||||
);
|
||||
}
|
||||
group.bench_function("exif", |b| {
|
||||
for image in images.iter().take(10) {
|
||||
b.iter(|| load_thumbnail_exif(image));
|
||||
}
|
||||
});
|
||||
group.bench_function("full", |b| {
|
||||
for image in images.iter().take(10) {
|
||||
b.iter(|| load_thumbnail_full(image));
|
||||
}
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_group!(benches, thumbnail_load_benchmark);
|
||||
criterion_main!(benches);
|
||||
|
371
src/image.rs
371
src/image.rs
@ -1,273 +1,76 @@
|
||||
use iced::widget::image::Handle;
|
||||
use iced::widget::image::Image as IcedImage;
|
||||
use image::imageops::FilterType;
|
||||
// use image::codecs::jpeg::JpegDecoder;
|
||||
// use image::codecs::jpeg::JpegDecoder;
|
||||
use image::DynamicImage;
|
||||
use image::ImageReader;
|
||||
use itertools::Itertools;
|
||||
use memmap2::Mmap;
|
||||
use image::imageops::FilterType;
|
||||
use zune_image::codecs::jpeg::JpegDecoder;
|
||||
use zune_image::codecs::qoi::zune_core::colorspace::ColorSpace;
|
||||
use zune_image::codecs::qoi::zune_core::options::DecoderOptions;
|
||||
use zune_image::image::Image as ZuneImage;
|
||||
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::fs::read;
|
||||
use std::io;
|
||||
use std::io::BufReader;
|
||||
use std::io::Cursor;
|
||||
use std::io::Read;
|
||||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Instant;
|
||||
|
||||
pub enum Approach {
|
||||
Mmap,
|
||||
Path,
|
||||
ImageRs,
|
||||
Iced,
|
||||
ImageRsPath,
|
||||
}
|
||||
|
||||
pub fn convert_zune_rgb_to_rgba(rgb_data: Vec<Vec<u8>>) -> Vec<u8> {
|
||||
let r_channel = &rgb_data[0];
|
||||
|
||||
let num_pixels = r_channel.len() / 3;
|
||||
let mut rgba_data: Vec<u8> = Vec::with_capacity(num_pixels * 4);
|
||||
|
||||
for i in 0..num_pixels {
|
||||
rgba_data.push(r_channel[i * 3]);
|
||||
rgba_data.push(r_channel[i * 3 + 1]);
|
||||
rgba_data.push(r_channel[i * 3 + 2]);
|
||||
rgba_data.push(255); // Fully opaque Alpha value
|
||||
}
|
||||
|
||||
rgba_data
|
||||
}
|
||||
|
||||
pub fn map_file(path: &str) -> io::Result<Mmap> {
|
||||
let file = File::open(path)?;
|
||||
unsafe { Mmap::map(&file) }
|
||||
}
|
||||
|
||||
pub fn map_file_path(path: PathBuf) -> io::Result<Mmap> {
|
||||
let file = File::open(path)?;
|
||||
unsafe { Mmap::map(&file) }
|
||||
}
|
||||
|
||||
pub fn read_zune_image(mmap: &[u8]) -> Result<ZuneImage, String> {
|
||||
ZuneImage::read(mmap, DecoderOptions::new_fast()).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
pub fn read_zune_image_path(path: &str) -> ZuneImage {
|
||||
// let file = File::open(path).unwrap();
|
||||
// let file_contents = read(path).unwrap();
|
||||
// let mmap = map_file_path(path.into()).unwrap();
|
||||
// let decoder = JpegDecoder::new(&file_contents);
|
||||
// decoder.decode_into()
|
||||
// ZuneImage::read(mmap, DecoderOptions::new_fast()).map_err(|e| e.to_string())
|
||||
zune_image::image::Image::open_with_options(path, DecoderOptions::new_fast()).unwrap()
|
||||
}
|
||||
|
||||
pub fn flatten_zune_image(img: &ZuneImage) -> Vec<Vec<u8>> {
|
||||
img.flatten_to_u8()
|
||||
}
|
||||
|
||||
pub fn flatten_image_image(img: DynamicImage) -> Vec<u8> {
|
||||
img.into_rgba8().into_raw()
|
||||
}
|
||||
|
||||
pub fn create_iced_handle(width: u32, height: u32, rgba: Vec<u8>) -> Handle {
|
||||
Handle::from_rgba(width, height, rgba)
|
||||
}
|
||||
|
||||
// pub fn load_thumbnail(
|
||||
// path: &str,
|
||||
// approach: Approach,
|
||||
// ) -> Result<iced::widget::image::Handle, String> {
|
||||
// match approach {
|
||||
// Approach::Mmap => {
|
||||
// let mmap = map_file(path).unwrap();
|
||||
// println!("mapped file");
|
||||
// let img = read_zune_image(mmap.deref())?;
|
||||
// let width = img.dimensions().0 as u32;
|
||||
// let height = img.dimensions().1 as u32;
|
||||
// println!("loaded");
|
||||
// let flat = flatten_zune_image(&img);
|
||||
// println!("flattened");
|
||||
// let rgba = convert_zune_rgb_to_rgba(flat);
|
||||
// println!("rgbad");
|
||||
// let conv = create_iced_handle(width, height, rgba);
|
||||
// println!("iced");
|
||||
|
||||
// Ok(conv)
|
||||
// }
|
||||
// Approach::Path => {
|
||||
// let img = read_zune_image_path(path);
|
||||
// let width = img.dimensions().0 as u32;
|
||||
// let height = img.dimensions().1 as u32;
|
||||
// println!("loaded");
|
||||
// let flat = flatten_zune_image(&img);
|
||||
// println!("flattened");
|
||||
// let rgba = convert_zune_rgb_to_rgba(flat);
|
||||
// println!("rgbad");
|
||||
// let conv = create_iced_handle(width, height, rgba);
|
||||
// println!("iced");
|
||||
|
||||
// Ok(conv)
|
||||
// }
|
||||
// Approach::ImageRs => {
|
||||
// let mmap = map_file(path).unwrap();
|
||||
// let img = image::load_from_memory(mmap.deref()).map_err(|e| e.to_string())?;
|
||||
// let width = img.width();
|
||||
// let height = img.height();
|
||||
// println!("loaded");
|
||||
// let rgba = flatten_image_image(img);
|
||||
// println!("rgbad");
|
||||
// let conv = create_iced_handle(width, height, rgba);
|
||||
// println!("iced");
|
||||
|
||||
// Ok(conv)
|
||||
// }
|
||||
// Approach::ImageRsPath => {
|
||||
// let img = ImageReader::open(path).unwrap().decode().unwrap();
|
||||
// let width = img.width();
|
||||
// let height = img.height();
|
||||
// println!("loaded");
|
||||
// let rgba = flatten_image_image(img);
|
||||
// println!("rgbad");
|
||||
// let conv = create_iced_handle(width, height, rgba);
|
||||
// println!("iced");
|
||||
|
||||
// Ok(conv)
|
||||
// }
|
||||
// Approach::Iced => Ok(Handle::from_path(path)),
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn load_image_argb(path: PathBuf) -> ImflowImageBuffer {
|
||||
let total_start = Instant::now();
|
||||
|
||||
let file_contents = read(path).unwrap();
|
||||
|
||||
let mut decoder = JpegDecoder::new(&file_contents);
|
||||
let options = DecoderOptions::new_fast().jpeg_set_out_colorspace(ColorSpace::BGRA);
|
||||
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 buffer2: Vec<u8> = vec![0; width * height * 4];
|
||||
decoder.decode_into(buffer2.as_mut_slice());
|
||||
|
||||
// Stage 2: Read the image
|
||||
// let stage2_start = Instant::now();
|
||||
// let img = read_zune_image(mmap.deref()).unwrap();
|
||||
// let width = img.dimensions().0;
|
||||
// let height = img.dimensions().1;
|
||||
// let stage2_time = stage2_start.elapsed();
|
||||
// println!("Image decoding took: {:?}", stage2_time);
|
||||
|
||||
// Stage 3: Flatten the image
|
||||
// let stage3_start = Instant::now();
|
||||
// let flat = &mut flatten_zune_image(&img)[0];
|
||||
// let stage3_time = stage3_start.elapsed();
|
||||
// println!("Image flattening took: {:?}", stage3_time);
|
||||
|
||||
// Stage 4: Convert to ARGB format
|
||||
let stage4_start = Instant::now();
|
||||
// let mut buffer: Vec<u32> = vec![0; width * height];
|
||||
|
||||
// for (rgba, argb) in buffer2.chunks_mut(4).zip(buffer.iter_mut()) {
|
||||
// let r = rgba[0] as u32;
|
||||
// let g = rgba[1] as u32;
|
||||
// let b = rgba[2] as u32;
|
||||
// *argb = r << 16 | g << 8 | b;
|
||||
// }
|
||||
let buffer: &[u32] =
|
||||
unsafe { std::slice::from_raw_parts(buffer2.as_ptr() as *const u32, buffer2.len() / 4) };
|
||||
let stage4_time = stage4_start.elapsed();
|
||||
println!("RGBA to ARGB conversion took: {:?}", stage4_time);
|
||||
|
||||
// Total time
|
||||
let total_time = total_start.elapsed();
|
||||
println!("Total loading time: {:?}", total_time);
|
||||
|
||||
ImflowImageBuffer {
|
||||
width,
|
||||
height,
|
||||
argb_buffer: buffer.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ImflowImageBuffer {
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub argb_buffer: Vec<u32>,
|
||||
}
|
||||
|
||||
pub fn image_to_argb_buffer(img: DynamicImage) -> Vec<u32> {
|
||||
let mut buffer: Vec<u32> = vec![0; (img.width() * img.height()) as usize];
|
||||
let mut flat = img.into_rgba8().into_raw();
|
||||
|
||||
for (rgba, argb) in flat.chunks_mut(4).zip(buffer.iter_mut()) {
|
||||
let r = rgba[0] as u32;
|
||||
let g = rgba[1] as u32;
|
||||
let b = rgba[2] as u32;
|
||||
*argb = r << 16 | g << 8 | b;
|
||||
}
|
||||
buffer
|
||||
pub fn create_iced_handle(width: u32, height: u32, rgba: Vec<u8>) -> Handle {
|
||||
Handle::from_rgba(width, height, rgba)
|
||||
}
|
||||
|
||||
pub fn load_image_argb_imagers(path: PathBuf) -> ImflowImageBuffer {
|
||||
pub fn load_image(path: PathBuf) -> ImflowImageBuffer {
|
||||
let total_start = Instant::now();
|
||||
|
||||
// Stage 1: Memory map the file
|
||||
let stage1_start = Instant::now();
|
||||
let mmap = map_file_path(path).unwrap();
|
||||
let stage1_time = stage1_start.elapsed();
|
||||
// println!("File mapping took: {:?}", stage1_time);
|
||||
let file = read(path).unwrap();
|
||||
let mut decoder = JpegDecoder::new(&file);
|
||||
let options = DecoderOptions::new_fast().jpeg_set_out_colorspace(ColorSpace::BGRA);
|
||||
decoder.set_options(options);
|
||||
|
||||
// Stage 2: Read the image
|
||||
let stage2_start = Instant::now();
|
||||
let img = image::load_from_memory(mmap.deref())
|
||||
.map_err(|e| e.to_string())
|
||||
.unwrap();
|
||||
let width = img.width() as usize;
|
||||
let height = img.height() as usize;
|
||||
let stage2_time = stage2_start.elapsed();
|
||||
// println!("Image decoding took: {:?}", stage2_time);
|
||||
decoder.decode_headers().unwrap();
|
||||
let info = decoder.info().unwrap();
|
||||
let width = info.width as usize;
|
||||
let height = info.height as usize;
|
||||
|
||||
// Stage 3: Flatten the image
|
||||
let stage3_start = Instant::now();
|
||||
let mut flat = img.into_rgba8().into_raw();
|
||||
let stage3_time = stage3_start.elapsed();
|
||||
// println!("Image flattening took: {:?}", stage3_time);
|
||||
let mut buffer: Vec<u8> = vec![0; width * height * 4];
|
||||
decoder.decode_into(buffer.as_mut_slice()).unwrap();
|
||||
|
||||
// Stage 4: Convert to ARGB format
|
||||
let stage4_start = Instant::now();
|
||||
let mut buffer: Vec<u32> = vec![0; width * height];
|
||||
// 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);
|
||||
|
||||
for (rgba, argb) in flat.chunks_mut(4).zip(buffer.iter_mut()) {
|
||||
let r = rgba[0] as u32;
|
||||
let g = rgba[1] as u32;
|
||||
let b = rgba[2] as u32;
|
||||
*argb = r << 16 | g << 8 | b;
|
||||
}
|
||||
let stage4_time = stage4_start.elapsed();
|
||||
// println!("RGBA to ARGB conversion took: {:?}", stage4_time);
|
||||
|
||||
// Total time
|
||||
let total_time = total_start.elapsed();
|
||||
println!("Total loading time: {:?}", total_time);
|
||||
|
||||
ImflowImageBuffer {
|
||||
width,
|
||||
height,
|
||||
argb_buffer: buffer,
|
||||
argb_buffer: buffer_u32,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn image_to_argb_buffer(img: DynamicImage) -> Vec<u32> {
|
||||
let flat = img.into_rgba8();
|
||||
let buf = flat.as_raw();
|
||||
|
||||
buf.chunks_exact(4).map(|rgba| {
|
||||
let r = rgba[0] as u32;
|
||||
let g = rgba[1] as u32;
|
||||
let b = rgba[2] as u32;
|
||||
r << 16 | g << 8 | b
|
||||
}).collect()
|
||||
}
|
||||
|
||||
pub fn load_available_images(dir: PathBuf) -> Vec<PathBuf> {
|
||||
let mut files: Vec<PathBuf> = fs::read_dir(dir)
|
||||
.unwrap()
|
||||
@ -282,48 +85,68 @@ pub fn get_embedded_thumbnail(path: PathBuf) -> Option<Vec<u8>> {
|
||||
let meta = rexiv2::Metadata::new_from_path(path);
|
||||
match meta {
|
||||
Ok(meta) => {
|
||||
// println!("{:?}", meta.get_preview_images());
|
||||
meta.get_thumbnail().map(|v| v.to_vec())
|
||||
},
|
||||
for preview in meta.get_preview_images().unwrap() {
|
||||
return Some(preview.get_data().unwrap());
|
||||
}
|
||||
None
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_thumbnail(path: PathBuf) -> ImflowImageBuffer {
|
||||
if let Some(thumbnail) = get_embedded_thumbnail(path.clone()) {
|
||||
let decoder = image::ImageReader::new(Cursor::new(thumbnail))
|
||||
.with_guessed_format()
|
||||
.unwrap();
|
||||
let image = decoder.decode().unwrap();
|
||||
|
||||
let width: usize = image.width() as usize;
|
||||
let height: usize = image.height() as usize;
|
||||
let mut flat = image.into_rgba8().into_raw();
|
||||
let mut buffer: Vec<u32> = vec![0; width * height];
|
||||
|
||||
for (rgba, argb) in flat.chunks_mut(4).zip(buffer.iter_mut()) {
|
||||
let r = rgba[0] as u32;
|
||||
let g = rgba[1] as u32;
|
||||
let b = rgba[2] as u32;
|
||||
*argb = r << 16 | g << 8 | b;
|
||||
}
|
||||
|
||||
ImflowImageBuffer {
|
||||
width,
|
||||
height,
|
||||
argb_buffer: buffer,
|
||||
}
|
||||
} else {
|
||||
let reader = image::ImageReader::new(Cursor::new(read(path).unwrap()));
|
||||
let image = reader.decode().unwrap().resize(640, 480, FilterType::Nearest);
|
||||
let width = image.width() as usize;
|
||||
let height = image.height() as usize;
|
||||
let buffer = image_to_argb_buffer(image);
|
||||
|
||||
ImflowImageBuffer {
|
||||
width,
|
||||
height,
|
||||
argb_buffer: buffer,
|
||||
}
|
||||
pub fn load_thumbnail(path: &PathBuf) -> ImflowImageBuffer {
|
||||
match load_thumbnail_exif(path) {
|
||||
Some(thumbnail) => return thumbnail,
|
||||
None => load_thumbnail_full(path),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_thumbnail_exif(path: &PathBuf) -> Option<ImflowImageBuffer> {
|
||||
match get_embedded_thumbnail(path.clone()) {
|
||||
Some(thumbnail) => {
|
||||
let decoder = image::ImageReader::new(Cursor::new(thumbnail))
|
||||
.with_guessed_format()
|
||||
.unwrap();
|
||||
let image = decoder.decode().unwrap();
|
||||
|
||||
let width: usize = image.width() as usize;
|
||||
let height: usize = image.height() as usize;
|
||||
let mut flat = image.into_rgba8().into_raw();
|
||||
let mut buffer: Vec<u32> = vec![0; width * height];
|
||||
|
||||
for (rgba, argb) in flat.chunks_mut(4).zip(buffer.iter_mut()) {
|
||||
let r = rgba[0] as u32;
|
||||
let g = rgba[1] as u32;
|
||||
let b = rgba[2] as u32;
|
||||
*argb = r << 16 | g << 8 | b;
|
||||
}
|
||||
|
||||
Some(ImflowImageBuffer {
|
||||
width,
|
||||
height,
|
||||
argb_buffer: buffer,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_thumbnail_full(path: &PathBuf) -> ImflowImageBuffer {
|
||||
let file = BufReader::new(File::open(path).unwrap());
|
||||
let reader = image::ImageReader::new(file);
|
||||
let image = reader
|
||||
.with_guessed_format()
|
||||
.unwrap()
|
||||
.decode()
|
||||
.unwrap()
|
||||
.resize(640, 480, FilterType::Nearest);
|
||||
let width = image.width() as usize;
|
||||
let height = image.height() as usize;
|
||||
let buffer = image_to_argb_buffer(image);
|
||||
|
||||
ImflowImageBuffer {
|
||||
width,
|
||||
height,
|
||||
argb_buffer: buffer,
|
||||
}
|
||||
}
|
||||
|
@ -1 +1,2 @@
|
||||
pub mod image;
|
||||
pub mod store;
|
||||
|
718
src/main.rs
718
src/main.rs
@ -1,37 +1,19 @@
|
||||
//! This example showcases an interactive version of the Game of Life, invented
|
||||
//! by John Conway. It leverages a `Canvas` together with other widgets.
|
||||
// use grid::Grid;
|
||||
// use std::fs::{self};
|
||||
// use std::path::{Path, PathBuf};
|
||||
// use std::collections::HashMap;
|
||||
// use iced::widget::image::FilterMethod;
|
||||
// use iced::widget::{
|
||||
// Column, Container, button, center, checkbox, column, container, row, slider, text,
|
||||
// };
|
||||
// use iced::{Center, Element, Fill, Length, Subscription, Task, keyboard};
|
||||
|
||||
use std::cmp::min;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs::{self};
|
||||
use std::io::{Cursor, Read};
|
||||
use std::ops::Deref;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{self, Duration, Instant};
|
||||
|
||||
use iced::futures::AsyncReadExt;
|
||||
use iced::widget::shader::wgpu::core::command::LoadOp;
|
||||
// use iced::time::milliseconds;
|
||||
use iced::widget::image::FilterMethod;
|
||||
use iced::widget::{
|
||||
Column, Container, button, center, checkbox, column, container, pick_list, row, slider, text,
|
||||
};
|
||||
use iced::{Center, Element, Fill, Length, Size, Subscription, Task, Theme, keyboard};
|
||||
use image::{self, DynamicImage, EncodableLayout, GenericImageView, ImageBuffer, ImageReader};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
use imflow::image::{
|
||||
Approach, ImflowImageBuffer, flatten_image_image, flatten_zune_image, get_embedded_thumbnail,
|
||||
load_available_images, load_image_argb, load_image_argb_imagers, load_thumbnail, map_file,
|
||||
map_file_path, read_zune_image,
|
||||
};
|
||||
use minifb::{Key, Window, WindowOptions};
|
||||
use threadpool::ThreadPool;
|
||||
use zune_image::codecs::qoi::zune_core::options::DecoderOptions; // for general image operations
|
||||
// use image::io::Reader as ImageReader; // specifically for Reader
|
||||
|
||||
use std::sync::{Arc, mpsc};
|
||||
use imflow::image::ImflowImageBuffer;
|
||||
use imflow::store::ImageStore;
|
||||
|
||||
// use winit::{
|
||||
// application::ApplicationHandler,
|
||||
@ -333,200 +315,6 @@ use std::sync::{Arc, mpsc};
|
||||
// }
|
||||
//
|
||||
|
||||
struct State {
|
||||
current_image_id: usize,
|
||||
loaded_images: HashMap<PathBuf, ImflowImageBuffer>,
|
||||
loaded_images_thumbnails: HashMap<PathBuf, ImflowImageBuffer>,
|
||||
available_images: Vec<PathBuf>,
|
||||
current_image_path: PathBuf,
|
||||
pool: ThreadPool,
|
||||
loader_rx: mpsc::Receiver<(PathBuf, ImflowImageBuffer)>,
|
||||
loader_tx: mpsc::Sender<(PathBuf, ImflowImageBuffer)>,
|
||||
currently_loading: HashSet<PathBuf>, // Track what's being loaded
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn new(path: PathBuf) -> Self {
|
||||
let current_image_id: usize = 0;
|
||||
let mut loaded_images: HashMap<PathBuf, ImflowImageBuffer> = HashMap::new();
|
||||
let mut loaded_thumbnails: HashMap<PathBuf, ImflowImageBuffer> = HashMap::new();
|
||||
let available_images = load_available_images(path);
|
||||
let new_path = available_images[0].clone();
|
||||
// let current_image = load_image_argb_imagers(new_path.clone());
|
||||
// let current_image = load_image_argb_imagers(new_path.clone());
|
||||
// loaded_images.insert(new_path.clone(), current_image);
|
||||
|
||||
let (loader_tx, loader_rx) = mpsc::channel();
|
||||
|
||||
let pool = ThreadPool::new(32);
|
||||
|
||||
let currently_loading = HashSet::new();
|
||||
|
||||
let total_start = Instant::now();
|
||||
let mut loaded = 0;
|
||||
let to_load = available_images.len();
|
||||
for path in &available_images {
|
||||
if let Some(thumbnail) = get_embedded_thumbnail(path.clone()) {
|
||||
let decoder = image::ImageReader::new(Cursor::new(thumbnail))
|
||||
.with_guessed_format()
|
||||
.unwrap();
|
||||
let image = decoder.decode().unwrap();
|
||||
|
||||
let width: usize = image.width() as usize;
|
||||
let height: usize = image.height() as usize;
|
||||
let mut flat = image.into_rgba8().into_raw();
|
||||
let mut buffer: Vec<u32> = vec![0; width * height];
|
||||
|
||||
for (rgba, argb) in flat.chunks_mut(4).zip(buffer.iter_mut()) {
|
||||
let r = rgba[0] as u32;
|
||||
let g = rgba[1] as u32;
|
||||
let b = rgba[2] as u32;
|
||||
*argb = r << 16 | g << 8 | b;
|
||||
}
|
||||
|
||||
let buf = ImflowImageBuffer {
|
||||
width,
|
||||
height,
|
||||
argb_buffer: buffer,
|
||||
};
|
||||
|
||||
loaded_thumbnails.insert(path.clone(), buf);
|
||||
loaded += 1;
|
||||
println!("{}/{}", loaded, to_load);
|
||||
} else {
|
||||
loaded += 1;
|
||||
if !loaded_images.contains_key(&path.clone()) {
|
||||
let loaded = load_image_argb(path.clone());
|
||||
loaded_images.insert(path.clone(), loaded);
|
||||
// self.request_load(new_path.clone());
|
||||
}
|
||||
// let loaded = load_image_argb(path.clone());
|
||||
// loaded_images.get(&path.clone()).unwrap()
|
||||
println!("none for {:?}", path);
|
||||
}
|
||||
}
|
||||
let total_time = total_start.elapsed();
|
||||
println!(
|
||||
"all thumbnails load time: {:?} for {}",
|
||||
total_time,
|
||||
loaded_thumbnails.len()
|
||||
);
|
||||
|
||||
let mut state = Self {
|
||||
current_image_id,
|
||||
loaded_images,
|
||||
available_images,
|
||||
current_image_path: new_path,
|
||||
pool,
|
||||
loader_rx,
|
||||
loader_tx,
|
||||
currently_loading,
|
||||
loaded_images_thumbnails: loaded_thumbnails,
|
||||
};
|
||||
|
||||
// state.preload_next_images(min(state.available_images.len(), 1));
|
||||
|
||||
// let thumbnail_path = state.available_images[0].clone();
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
fn preload_next_images(&mut self, n: usize) {
|
||||
for image in self.available_images.clone().iter().take(n) {
|
||||
self.request_load(image.clone());
|
||||
}
|
||||
}
|
||||
|
||||
fn request_load(&mut self, path: PathBuf) {
|
||||
if self.loaded_images.contains_key(&path) || self.currently_loading.contains(&path) {
|
||||
return;
|
||||
}
|
||||
let tx = self.loader_tx.clone();
|
||||
self.currently_loading.insert(path.clone());
|
||||
|
||||
self.pool.execute(move || {
|
||||
let image = load_image_argb(path.clone());
|
||||
// let image = load_image_argb_imagers(path.clone());
|
||||
let _ = tx.send((path, image));
|
||||
});
|
||||
}
|
||||
|
||||
fn check_loaded_images(&mut self) {
|
||||
while let Ok((path, image)) = self.loader_rx.try_recv() {
|
||||
self.loaded_images.insert(path.clone(), image);
|
||||
self.currently_loading.remove(&path);
|
||||
}
|
||||
}
|
||||
|
||||
fn next_image(&mut self, change: i32) {
|
||||
self.current_image_id = (self.current_image_id as i32 + change)
|
||||
.clamp(0, self.available_images.len() as i32 - 1)
|
||||
as usize;
|
||||
let new_path = self.available_images[self.current_image_id].clone();
|
||||
if !self.loaded_images.contains_key(&new_path) {
|
||||
// self.request_load(new_path.clone());
|
||||
}
|
||||
self.current_image_path = new_path;
|
||||
}
|
||||
|
||||
fn get_current_image(&self) -> Option<&ImflowImageBuffer> {
|
||||
self.loaded_images.get(&self.current_image_path)
|
||||
}
|
||||
|
||||
fn get_thumbnail(&mut self) -> &ImflowImageBuffer {
|
||||
if self
|
||||
.loaded_images_thumbnails
|
||||
.contains_key(&self.current_image_path)
|
||||
{
|
||||
return self
|
||||
.loaded_images_thumbnails
|
||||
.get(&self.current_image_path)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let path = &self.current_image_path;
|
||||
if let Some(thumbnail) = get_embedded_thumbnail(path.clone()) {
|
||||
let total_start = Instant::now();
|
||||
let decoder = image::ImageReader::new(Cursor::new(thumbnail))
|
||||
.with_guessed_format()
|
||||
.unwrap();
|
||||
let image = decoder.decode().unwrap();
|
||||
|
||||
let width: usize = image.width() as usize;
|
||||
let height: usize = image.height() as usize;
|
||||
let mut flat = image.into_rgba8().into_raw();
|
||||
let mut buffer: Vec<u32> = vec![0; width * height];
|
||||
|
||||
for (rgba, argb) in flat.chunks_mut(4).zip(buffer.iter_mut()) {
|
||||
let r = rgba[0] as u32;
|
||||
let g = rgba[1] as u32;
|
||||
let b = rgba[2] as u32;
|
||||
*argb = r << 16 | g << 8 | b;
|
||||
}
|
||||
|
||||
let buf = ImflowImageBuffer {
|
||||
width,
|
||||
height,
|
||||
argb_buffer: buffer,
|
||||
};
|
||||
let total_time = total_start.elapsed();
|
||||
println!("thumbnail load time: {:?}", total_time);
|
||||
|
||||
self.loaded_images_thumbnails.insert(path.clone(), buf);
|
||||
|
||||
return self.loaded_images_thumbnails.get(&path.clone()).unwrap();
|
||||
}
|
||||
println!("skipping {:?}", path);
|
||||
if !self.loaded_images.contains_key(&path.clone()) {
|
||||
let loaded = load_image_argb(path.clone());
|
||||
self.loaded_images.insert(path.clone(), loaded);
|
||||
// self.request_load(new_path.clone());
|
||||
}
|
||||
// let loaded = load_image_argb(path.clone());
|
||||
self.loaded_images.get(&path.clone()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Args {
|
||||
@ -550,8 +338,8 @@ fn main() {
|
||||
window.set_target_fps(120);
|
||||
|
||||
let path = args.path.unwrap_or("./test_images".into());
|
||||
let mut state = State::new(path);
|
||||
let mut waiting = false;
|
||||
let mut state = ImageStore::new(path);
|
||||
let mut waiting = true;
|
||||
window.set_key_repeat_delay(0.1);
|
||||
window.set_key_repeat_rate(0.1);
|
||||
|
||||
@ -560,22 +348,30 @@ fn main() {
|
||||
while window.is_open() && !window.is_key_down(Key::Escape) {
|
||||
window.update();
|
||||
state.check_loaded_images();
|
||||
if !waiting && window.is_key_pressed(Key::Right, minifb::KeyRepeat::Yes) {
|
||||
if window.is_key_pressed(Key::Right, minifb::KeyRepeat::Yes) {
|
||||
state.next_image(1);
|
||||
show_image(&mut window, state.get_thumbnail());
|
||||
// waiting = true;
|
||||
} else if !waiting && window.is_key_pressed(Key::Left, minifb::KeyRepeat::Yes) {
|
||||
if let Some(full) = state.get_current_image() {
|
||||
show_image(&mut window, full);
|
||||
} else {
|
||||
show_image(&mut window, state.get_thumbnail());
|
||||
waiting = true;
|
||||
}
|
||||
} else if window.is_key_pressed(Key::Left, minifb::KeyRepeat::Yes) {
|
||||
state.next_image(-1);
|
||||
show_image(&mut window, state.get_thumbnail());
|
||||
// waiting = true;
|
||||
if let Some(full) = state.get_current_image() {
|
||||
show_image(&mut window, full);
|
||||
} else {
|
||||
show_image(&mut window, state.get_thumbnail());
|
||||
waiting = true;
|
||||
}
|
||||
}
|
||||
// if waiting {
|
||||
// if let Some(image) = state.get_current_image() {
|
||||
// waiting = false;
|
||||
if waiting {
|
||||
if let Some(image) = state.get_current_image() {
|
||||
waiting = false;
|
||||
|
||||
// show_image(&mut window, &image);
|
||||
// }
|
||||
// }
|
||||
show_image(&mut window, &image);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -585,247 +381,247 @@ fn show_image(window: &mut Window, image: &ImflowImageBuffer) {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
struct MainApp {
|
||||
is_playing: bool,
|
||||
queued_ticks: usize,
|
||||
speed: usize,
|
||||
next_speed: Option<usize>,
|
||||
version: usize,
|
||||
image_filter_method: FilterMethod,
|
||||
current_image: Option<PathBuf>,
|
||||
width: u32,
|
||||
available_images: Vec<PathBuf>,
|
||||
current_image_id: usize,
|
||||
loaded_images: HashMap<PathBuf, iced::widget::image::Handle>,
|
||||
}
|
||||
// struct MainApp {
|
||||
// is_playing: bool,
|
||||
// queued_ticks: usize,
|
||||
// speed: usize,
|
||||
// next_speed: Option<usize>,
|
||||
// version: usize,
|
||||
// image_filter_method: FilterMethod,
|
||||
// current_image: Option<PathBuf>,
|
||||
// width: u32,
|
||||
// available_images: Vec<PathBuf>,
|
||||
// current_image_id: usize,
|
||||
// loaded_images: HashMap<PathBuf, iced::widget::image::Handle>,
|
||||
// }
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
TogglePlayback,
|
||||
ToggleGrid(bool),
|
||||
Clear,
|
||||
SpeedChanged(f32),
|
||||
Tick,
|
||||
Next(i32),
|
||||
ImageWidthChanged(u32),
|
||||
ImageUseNearestToggled(bool),
|
||||
}
|
||||
// #[derive(Debug, Clone)]
|
||||
// enum Message {
|
||||
// TogglePlayback,
|
||||
// ToggleGrid(bool),
|
||||
// Clear,
|
||||
// SpeedChanged(f32),
|
||||
// Tick,
|
||||
// Next(i32),
|
||||
// ImageWidthChanged(u32),
|
||||
// ImageUseNearestToggled(bool),
|
||||
// }
|
||||
|
||||
impl MainApp {
|
||||
fn new() -> Self {
|
||||
let mut dir: Vec<PathBuf> = fs::read_dir(Path::new("./test_images"))
|
||||
.unwrap()
|
||||
.map(|f| f.unwrap().path())
|
||||
.collect();
|
||||
dir.sort();
|
||||
let mut res = Self {
|
||||
is_playing: false,
|
||||
queued_ticks: 0,
|
||||
speed: 5,
|
||||
next_speed: None,
|
||||
version: 0,
|
||||
image_filter_method: FilterMethod::Nearest,
|
||||
width: 1400,
|
||||
current_image: Some(dir.first().unwrap().clone()),
|
||||
available_images: dir,
|
||||
current_image_id: 0,
|
||||
loaded_images: HashMap::new(),
|
||||
};
|
||||
let _ = res.update(Message::Next(0));
|
||||
res
|
||||
}
|
||||
// impl MainApp {
|
||||
// fn new() -> Self {
|
||||
// let mut dir: Vec<PathBuf> = fs::read_dir(Path::new("./test_images"))
|
||||
// .unwrap()
|
||||
// .map(|f| f.unwrap().path())
|
||||
// .collect();
|
||||
// dir.sort();
|
||||
// let mut res = Self {
|
||||
// is_playing: false,
|
||||
// queued_ticks: 0,
|
||||
// speed: 5,
|
||||
// next_speed: None,
|
||||
// version: 0,
|
||||
// image_filter_method: FilterMethod::Nearest,
|
||||
// width: 1400,
|
||||
// current_image: Some(dir.first().unwrap().clone()),
|
||||
// available_images: dir,
|
||||
// current_image_id: 0,
|
||||
// loaded_images: HashMap::new(),
|
||||
// };
|
||||
// let _ = res.update(Message::Next(0));
|
||||
// res
|
||||
// }
|
||||
|
||||
fn update(&mut self, message: Message) -> Task<Message> {
|
||||
match message {
|
||||
Message::Tick => {
|
||||
self.queued_ticks = (self.queued_ticks + 1).min(self.speed);
|
||||
// fn update(&mut self, message: Message) -> Task<Message> {
|
||||
// match message {
|
||||
// Message::Tick => {
|
||||
// self.queued_ticks = (self.queued_ticks + 1).min(self.speed);
|
||||
|
||||
// if let Some(task) = self.grid.tick(self.queued_ticks) {
|
||||
// if let Some(speed) = self.next_speed.take() {
|
||||
// self.speed = speed;
|
||||
// }
|
||||
// // if let Some(task) = self.grid.tick(self.queued_ticks) {
|
||||
// // if let Some(speed) = self.next_speed.take() {
|
||||
// // self.speed = speed;
|
||||
// // }
|
||||
|
||||
// self.queued_ticks = 0;
|
||||
// // self.queued_ticks = 0;
|
||||
|
||||
// let version = self.version;
|
||||
// // let version = self.version;
|
||||
|
||||
// // return Task::perform(task, Message::Grid.with(version));
|
||||
// }
|
||||
}
|
||||
Message::TogglePlayback => {
|
||||
self.is_playing = !self.is_playing;
|
||||
}
|
||||
Message::ToggleGrid(show_grid_lines) => {
|
||||
// self.grid.toggle_lines(show_grid_lines);
|
||||
}
|
||||
Message::Clear => {
|
||||
// self.grid.clear();
|
||||
self.version += 1;
|
||||
}
|
||||
Message::SpeedChanged(speed) => {
|
||||
if self.is_playing {
|
||||
self.next_speed = Some(speed.round() as usize);
|
||||
} else {
|
||||
self.speed = speed.round() as usize;
|
||||
}
|
||||
}
|
||||
Message::ImageWidthChanged(image_width) => {
|
||||
self.width = image_width;
|
||||
}
|
||||
Message::ImageUseNearestToggled(use_nearest) => {
|
||||
self.image_filter_method = if use_nearest {
|
||||
FilterMethod::Nearest
|
||||
} else {
|
||||
FilterMethod::Linear
|
||||
};
|
||||
}
|
||||
Message::Next(change) => {
|
||||
let elements = self.available_images.len() as i32;
|
||||
let new_id = (self.current_image_id as i32 + change).clamp(0, elements - 1);
|
||||
println!(
|
||||
"updated id: {} from {} total {}",
|
||||
new_id, self.current_image_id, elements
|
||||
);
|
||||
self.current_image_id = new_id as usize;
|
||||
let path = self
|
||||
.available_images
|
||||
.get(self.current_image_id)
|
||||
.unwrap()
|
||||
.clone();
|
||||
self.current_image = Some(path.clone());
|
||||
if !self.loaded_images.contains_key(&path.to_path_buf()) {
|
||||
// self.loaded_images.insert(
|
||||
// path.to_path_buf(),
|
||||
// load_thumbnail(path.to_str().unwrap(), Approach::ImageRs).unwrap(),
|
||||
// );
|
||||
}
|
||||
}
|
||||
}
|
||||
// // // return Task::perform(task, Message::Grid.with(version));
|
||||
// // }
|
||||
// }
|
||||
// Message::TogglePlayback => {
|
||||
// self.is_playing = !self.is_playing;
|
||||
// }
|
||||
// Message::ToggleGrid(show_grid_lines) => {
|
||||
// // self.grid.toggle_lines(show_grid_lines);
|
||||
// }
|
||||
// Message::Clear => {
|
||||
// // self.grid.clear();
|
||||
// self.version += 1;
|
||||
// }
|
||||
// Message::SpeedChanged(speed) => {
|
||||
// if self.is_playing {
|
||||
// self.next_speed = Some(speed.round() as usize);
|
||||
// } else {
|
||||
// self.speed = speed.round() as usize;
|
||||
// }
|
||||
// }
|
||||
// Message::ImageWidthChanged(image_width) => {
|
||||
// self.width = image_width;
|
||||
// }
|
||||
// Message::ImageUseNearestToggled(use_nearest) => {
|
||||
// self.image_filter_method = if use_nearest {
|
||||
// FilterMethod::Nearest
|
||||
// } else {
|
||||
// FilterMethod::Linear
|
||||
// };
|
||||
// }
|
||||
// Message::Next(change) => {
|
||||
// let elements = self.available_images.len() as i32;
|
||||
// let new_id = (self.current_image_id as i32 + change).clamp(0, elements - 1);
|
||||
// println!(
|
||||
// "updated id: {} from {} total {}",
|
||||
// new_id, self.current_image_id, elements
|
||||
// );
|
||||
// self.current_image_id = new_id as usize;
|
||||
// let path = self
|
||||
// .available_images
|
||||
// .get(self.current_image_id)
|
||||
// .unwrap()
|
||||
// .clone();
|
||||
// self.current_image = Some(path.clone());
|
||||
// if !self.loaded_images.contains_key(&path.to_path_buf()) {
|
||||
// // self.loaded_images.insert(
|
||||
// // path.to_path_buf(),
|
||||
// // load_thumbnail(path.to_str().unwrap(), Approach::ImageRs).unwrap(),
|
||||
// // );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
Task::none()
|
||||
}
|
||||
// Task::none()
|
||||
// }
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
keyboard::on_key_press(|key, _modifiers| match key {
|
||||
keyboard::Key::Named(keyboard::key::Named::ArrowRight) => Some(Message::Next(1)),
|
||||
keyboard::Key::Named(keyboard::key::Named::ArrowLeft) => Some(Message::Next(-1)),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
// fn subscription(&self) -> Subscription<Message> {
|
||||
// keyboard::on_key_press(|key, _modifiers| match key {
|
||||
// keyboard::Key::Named(keyboard::key::Named::ArrowRight) => Some(Message::Next(1)),
|
||||
// keyboard::Key::Named(keyboard::key::Named::ArrowLeft) => Some(Message::Next(-1)),
|
||||
// _ => None,
|
||||
// })
|
||||
// }
|
||||
|
||||
fn view(&self) -> Element<'_, Message> {
|
||||
let version = self.version;
|
||||
let selected_speed = self.next_speed.unwrap_or(self.speed);
|
||||
let controls = view_controls(
|
||||
self.is_playing,
|
||||
true,
|
||||
// self.grid.are_lines_visible(),
|
||||
selected_speed,
|
||||
// self.grid.preset(),
|
||||
);
|
||||
// fn view(&self) -> Element<'_, Message> {
|
||||
// let version = self.version;
|
||||
// let selected_speed = self.next_speed.unwrap_or(self.speed);
|
||||
// let controls = view_controls(
|
||||
// self.is_playing,
|
||||
// true,
|
||||
// // self.grid.are_lines_visible(),
|
||||
// selected_speed,
|
||||
// // self.grid.preset(),
|
||||
// );
|
||||
|
||||
let content = column![
|
||||
// image("/media/nfs/sphotos/Images/24-08-11-Copenhagen/24-08-12/20240812-175614_DSC03844.JPG").into(),
|
||||
// self.grid.view().map(Message::Grid.with(version)),
|
||||
self.image(),
|
||||
controls,
|
||||
]
|
||||
.height(Fill);
|
||||
// let content = column![
|
||||
// // image("/media/nfs/sphotos/Images/24-08-11-Copenhagen/24-08-12/20240812-175614_DSC03844.JPG").into(),
|
||||
// // self.grid.view().map(Message::Grid.with(version)),
|
||||
// self.image(),
|
||||
// controls,
|
||||
// ]
|
||||
// .height(Fill);
|
||||
|
||||
container(content).width(Fill).height(Fill).into()
|
||||
// image("/media/nfs/sphotos/Images/24-08-11-Copenhagen/24-08-12/20240812-175614_DSC03844.JPG").into()
|
||||
}
|
||||
// container(content).width(Fill).height(Fill).into()
|
||||
// // image("/media/nfs/sphotos/Images/24-08-11-Copenhagen/24-08-12/20240812-175614_DSC03844.JPG").into()
|
||||
// }
|
||||
|
||||
fn image(&self) -> Column<Message> {
|
||||
let width = self.width;
|
||||
let filter_method = self.image_filter_method;
|
||||
// fn image(&self) -> Column<Message> {
|
||||
// let width = self.width;
|
||||
// let filter_method = self.image_filter_method;
|
||||
|
||||
Self::container("Image")
|
||||
.push("An image that tries to keep its aspect ratio.")
|
||||
.push(self.ferris(
|
||||
width,
|
||||
filter_method,
|
||||
self.current_image.as_ref().unwrap().as_ref(),
|
||||
))
|
||||
.push(slider(100..=1500, width, Message::ImageWidthChanged))
|
||||
.push(text!("Width: {width} px").width(Fill).align_x(Center))
|
||||
.push(
|
||||
checkbox(
|
||||
"Use nearest interpolation",
|
||||
filter_method == FilterMethod::Nearest,
|
||||
)
|
||||
.on_toggle(Message::ImageUseNearestToggled),
|
||||
)
|
||||
.align_x(Center)
|
||||
}
|
||||
// Self::container("Image")
|
||||
// .push("An image that tries to keep its aspect ratio.")
|
||||
// .push(self.ferris(
|
||||
// width,
|
||||
// filter_method,
|
||||
// self.current_image.as_ref().unwrap().as_ref(),
|
||||
// ))
|
||||
// .push(slider(100..=1500, width, Message::ImageWidthChanged))
|
||||
// .push(text!("Width: {width} px").width(Fill).align_x(Center))
|
||||
// .push(
|
||||
// checkbox(
|
||||
// "Use nearest interpolation",
|
||||
// filter_method == FilterMethod::Nearest,
|
||||
// )
|
||||
// .on_toggle(Message::ImageUseNearestToggled),
|
||||
// )
|
||||
// .align_x(Center)
|
||||
// }
|
||||
|
||||
fn container(title: &str) -> Column<'_, Message> {
|
||||
column![text(title).size(50)].spacing(20)
|
||||
}
|
||||
// fn container(title: &str) -> Column<'_, Message> {
|
||||
// column![text(title).size(50)].spacing(20)
|
||||
// }
|
||||
|
||||
fn ferris<'a>(
|
||||
&self,
|
||||
width: u32,
|
||||
filter_method: iced::widget::image::FilterMethod,
|
||||
path: &Path,
|
||||
) -> Container<'a, Message> {
|
||||
if self.loaded_images.get(path).is_none() {
|
||||
return center(text("loading"));
|
||||
}
|
||||
let img = iced::widget::image::Image::new(self.loaded_images.get(path).unwrap());
|
||||
center(
|
||||
// This should go away once we unify resource loading on native
|
||||
// platforms
|
||||
img.filter_method(filter_method)
|
||||
.width(Length::Fixed(width as f32)),
|
||||
)
|
||||
}
|
||||
}
|
||||
// fn ferris<'a>(
|
||||
// &self,
|
||||
// width: u32,
|
||||
// filter_method: iced::widget::image::FilterMethod,
|
||||
// path: &Path,
|
||||
// ) -> Container<'a, Message> {
|
||||
// if self.loaded_images.get(path).is_none() {
|
||||
// return center(text("loading"));
|
||||
// }
|
||||
// let img = iced::widget::image::Image::new(self.loaded_images.get(path).unwrap());
|
||||
// center(
|
||||
// // This should go away once we unify resource loading on native
|
||||
// // platforms
|
||||
// img.filter_method(filter_method)
|
||||
// .width(Length::Fixed(width as f32)),
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
impl Default for MainApp {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
// impl Default for MainApp {
|
||||
// fn default() -> Self {
|
||||
// Self::new()
|
||||
// }
|
||||
// }
|
||||
|
||||
fn view_controls<'a>(
|
||||
is_playing: bool,
|
||||
is_grid_enabled: bool,
|
||||
speed: usize,
|
||||
// preset: Preset,
|
||||
) -> Element<'a, Message> {
|
||||
let playback_controls = row![
|
||||
button(if is_playing { "Pause" } else { "Play" }).on_press(Message::TogglePlayback),
|
||||
button("Previous")
|
||||
.on_press(Message::Next(-1))
|
||||
.style(button::secondary),
|
||||
button("Next")
|
||||
.on_press(Message::Next(1))
|
||||
.style(button::secondary),
|
||||
]
|
||||
.spacing(10);
|
||||
// fn view_controls<'a>(
|
||||
// is_playing: bool,
|
||||
// is_grid_enabled: bool,
|
||||
// speed: usize,
|
||||
// // preset: Preset,
|
||||
// ) -> Element<'a, Message> {
|
||||
// let playback_controls = row![
|
||||
// button(if is_playing { "Pause" } else { "Play" }).on_press(Message::TogglePlayback),
|
||||
// button("Previous")
|
||||
// .on_press(Message::Next(-1))
|
||||
// .style(button::secondary),
|
||||
// button("Next")
|
||||
// .on_press(Message::Next(1))
|
||||
// .style(button::secondary),
|
||||
// ]
|
||||
// .spacing(10);
|
||||
|
||||
let speed_controls = row![
|
||||
slider(1.0..=1000.0, speed as f32, Message::SpeedChanged),
|
||||
text!("x{speed}").size(16),
|
||||
]
|
||||
.align_y(Center)
|
||||
.spacing(10);
|
||||
// let speed_controls = row![
|
||||
// slider(1.0..=1000.0, speed as f32, Message::SpeedChanged),
|
||||
// text!("x{speed}").size(16),
|
||||
// ]
|
||||
// .align_y(Center)
|
||||
// .spacing(10);
|
||||
|
||||
row![
|
||||
playback_controls,
|
||||
speed_controls,
|
||||
// checkbox("Grid", is_grid_enabled).on_toggle(Message::ToggleGrid),
|
||||
// row![
|
||||
// pick_list(preset::ALL, Some(preset), Message::PresetPicked),
|
||||
// button("Clear")
|
||||
// .on_press(Message::Clear)
|
||||
// .style(button::danger)
|
||||
// ]
|
||||
// .spacing(10)
|
||||
]
|
||||
.padding(10)
|
||||
.spacing(20)
|
||||
.align_y(Center)
|
||||
.into()
|
||||
}
|
||||
// row![
|
||||
// playback_controls,
|
||||
// speed_controls,
|
||||
// // checkbox("Grid", is_grid_enabled).on_toggle(Message::ToggleGrid),
|
||||
// // row![
|
||||
// // pick_list(preset::ALL, Some(preset), Message::PresetPicked),
|
||||
// // button("Clear")
|
||||
// // .on_press(Message::Clear)
|
||||
// // .style(button::danger)
|
||||
// // ]
|
||||
// // .spacing(10)
|
||||
// ]
|
||||
// .padding(10)
|
||||
// .spacing(20)
|
||||
// .align_y(Center)
|
||||
// .into()
|
||||
// }
|
||||
|
145
src/store.rs
Normal file
145
src/store.rs
Normal file
@ -0,0 +1,145 @@
|
||||
use crate::image::load_thumbnail;
|
||||
use crate::image::{
|
||||
ImflowImageBuffer, load_available_images, load_image,
|
||||
};
|
||||
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(crate) loaded_images: HashMap<PathBuf, ImflowImageBuffer>,
|
||||
pub(crate) loaded_images_thumbnails: HashMap<PathBuf, ImflowImageBuffer>,
|
||||
pub(crate) available_images: Vec<PathBuf>,
|
||||
pub(crate) current_image_path: PathBuf,
|
||||
pub(crate) pool: ThreadPool,
|
||||
pub(crate) loader_rx: mpsc::Receiver<(PathBuf, ImflowImageBuffer)>,
|
||||
pub(crate) loader_tx: mpsc::Sender<(PathBuf, ImflowImageBuffer)>,
|
||||
pub(crate) currently_loading: HashSet<PathBuf>,
|
||||
}
|
||||
|
||||
impl ImageStore {
|
||||
pub fn new(path: PathBuf) -> Self {
|
||||
let current_image_id: usize = 0;
|
||||
let loaded_images: HashMap<PathBuf, ImflowImageBuffer> = HashMap::new();
|
||||
let mut loaded_thumbnails: HashMap<PathBuf, 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 pool = ThreadPool::new(32);
|
||||
|
||||
let currently_loading = HashSet::new();
|
||||
|
||||
let total_start = Instant::now();
|
||||
let mut loaded = 0;
|
||||
let to_load = available_images.len();
|
||||
for path in &available_images {
|
||||
let buf = load_thumbnail(path);
|
||||
loaded_thumbnails.insert(path.clone(), buf);
|
||||
loaded += 1;
|
||||
println!("{}/{}", loaded, to_load);
|
||||
}
|
||||
let total_time = total_start.elapsed();
|
||||
println!(
|
||||
"all thumbnails load time: {:?} for {}",
|
||||
total_time,
|
||||
loaded_thumbnails.len()
|
||||
);
|
||||
|
||||
let mut state = Self {
|
||||
current_image_id,
|
||||
loaded_images,
|
||||
available_images,
|
||||
current_image_path: new_path,
|
||||
pool,
|
||||
loader_rx,
|
||||
loader_tx,
|
||||
currently_loading,
|
||||
loaded_images_thumbnails: loaded_thumbnails,
|
||||
};
|
||||
|
||||
state.preload_next_images(PRELOAD_NEXT_IMAGE_N);
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
pub fn preload_next_images(&mut self, n: usize) {
|
||||
for image in self
|
||||
.available_images
|
||||
.clone()
|
||||
.iter()
|
||||
.skip(self.current_image_id)
|
||||
.take(n)
|
||||
{
|
||||
self.request_load(image.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_load(&mut self, path: PathBuf) {
|
||||
if self.loaded_images.contains_key(&path) || self.currently_loading.contains(&path) {
|
||||
return;
|
||||
}
|
||||
let tx = self.loader_tx.clone();
|
||||
self.currently_loading.insert(path.clone());
|
||||
|
||||
self.pool.execute(move || {
|
||||
let image = load_image(path.clone());
|
||||
let _ = tx.send((path, image));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn check_loaded_images(&mut self) {
|
||||
while let Ok((path, image)) = self.loader_rx.try_recv() {
|
||||
self.loaded_images.insert(path.clone(), image);
|
||||
self.currently_loading.remove(&path);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_image(&mut self, change: i32) {
|
||||
self.current_image_id = (self.current_image_id as i32 + change)
|
||||
.clamp(0, self.available_images.len() as i32 - 1)
|
||||
as usize;
|
||||
|
||||
let new_path = self.available_images[self.current_image_id].clone();
|
||||
if !self.loaded_images.contains_key(&new_path) {
|
||||
self.request_load(new_path.clone());
|
||||
}
|
||||
self.current_image_path = new_path;
|
||||
self.preload_next_images(PRELOAD_NEXT_IMAGE_N);
|
||||
}
|
||||
|
||||
pub fn get_current_image(&self) -> Option<&ImflowImageBuffer> {
|
||||
self.loaded_images.get(&self.current_image_path)
|
||||
}
|
||||
|
||||
pub fn get_image(&self, path: &PathBuf) -> Option<&ImflowImageBuffer> {
|
||||
self.loaded_images.get(path)
|
||||
}
|
||||
|
||||
pub fn get_thumbnail(&mut self) -> &ImflowImageBuffer {
|
||||
if self
|
||||
.loaded_images_thumbnails
|
||||
.contains_key(&self.current_image_path)
|
||||
{
|
||||
return self
|
||||
.loaded_images_thumbnails
|
||||
.get(&self.current_image_path)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let buf = load_thumbnail(&self.current_image_path);
|
||||
self.loaded_images_thumbnails
|
||||
.insert(self.current_image_path.clone(), buf);
|
||||
return self
|
||||
.loaded_images_thumbnails
|
||||
.get(&self.current_image_path)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user