Refactor, optimizations
This commit is contained in:
parent
aed4212043
commit
4e42cfdc05
@ -3,44 +3,59 @@
|
|||||||
use std::iter;
|
use std::iter;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use criterion::BenchmarkId;
|
use criterion::{AxisScale, BenchmarkId, PlotConfiguration};
|
||||||
use criterion::{Criterion, black_box, criterion_group, criterion_main};
|
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";
|
const PATH: &str = "test_images";
|
||||||
|
|
||||||
pub fn criterion_benchmark(c: &mut Criterion) {
|
// pub fn full_load_benchmark(c: &mut Criterion) {
|
||||||
let mut group = c.benchmark_group("image_decode");
|
// 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
|
group
|
||||||
.sample_size(10) // Reduce number of samples (default is 100)
|
.sample_size(10)
|
||||||
.measurement_time(Duration::from_millis(500)) // Reduce measurement time (default is 5 seconds)
|
.measurement_time(Duration::from_millis(500))
|
||||||
.warm_up_time(Duration::from_millis(200)); // Reduce warm-up time (default is 3 seconds)
|
.warm_up_time(Duration::from_millis(200));
|
||||||
|
|
||||||
let images = load_available_images(PATH.into());
|
let images = load_available_images(PATH.into());
|
||||||
for image in images.iter() {
|
group.bench_function("exif", |b| {
|
||||||
let image_name = image.to_str().unwrap();
|
for image in images.iter().take(10) {
|
||||||
|
b.iter(|| load_thumbnail_exif(image));
|
||||||
// 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("full", |b| {
|
||||||
|
for image in images.iter().take(10) {
|
||||||
|
b.iter(|| load_thumbnail_full(image));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
group.finish();
|
group.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
criterion_group!(benches, criterion_benchmark);
|
criterion_group!(benches, thumbnail_load_benchmark);
|
||||||
criterion_main!(benches);
|
criterion_main!(benches);
|
||||||
|
313
src/image.rs
313
src/image.rs
@ -1,273 +1,76 @@
|
|||||||
use iced::widget::image::Handle;
|
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::DynamicImage;
|
||||||
use image::ImageReader;
|
use image::imageops::FilterType;
|
||||||
use itertools::Itertools;
|
|
||||||
use memmap2::Mmap;
|
|
||||||
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;
|
||||||
use zune_image::codecs::qoi::zune_core::options::DecoderOptions;
|
use zune_image::codecs::qoi::zune_core::options::DecoderOptions;
|
||||||
use zune_image::image::Image as ZuneImage;
|
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::fs::read;
|
use std::fs::read;
|
||||||
use std::io;
|
use std::io::BufReader;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use std::io::Read;
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::time::Instant;
|
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 struct ImflowImageBuffer {
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
pub height: usize,
|
pub height: usize,
|
||||||
pub argb_buffer: Vec<u32>,
|
pub argb_buffer: Vec<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn image_to_argb_buffer(img: DynamicImage) -> Vec<u32> {
|
pub fn create_iced_handle(width: u32, height: u32, rgba: Vec<u8>) -> Handle {
|
||||||
let mut buffer: Vec<u32> = vec![0; (img.width() * img.height()) as usize];
|
Handle::from_rgba(width, height, rgba)
|
||||||
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 load_image_argb_imagers(path: PathBuf) -> ImflowImageBuffer {
|
pub fn load_image(path: PathBuf) -> ImflowImageBuffer {
|
||||||
let total_start = Instant::now();
|
let total_start = Instant::now();
|
||||||
|
|
||||||
// Stage 1: Memory map the file
|
let file = read(path).unwrap();
|
||||||
let stage1_start = Instant::now();
|
let mut decoder = JpegDecoder::new(&file);
|
||||||
let mmap = map_file_path(path).unwrap();
|
let options = DecoderOptions::new_fast().jpeg_set_out_colorspace(ColorSpace::BGRA);
|
||||||
let stage1_time = stage1_start.elapsed();
|
decoder.set_options(options);
|
||||||
// println!("File mapping took: {:?}", stage1_time);
|
|
||||||
|
|
||||||
// Stage 2: Read the image
|
decoder.decode_headers().unwrap();
|
||||||
let stage2_start = Instant::now();
|
let info = decoder.info().unwrap();
|
||||||
let img = image::load_from_memory(mmap.deref())
|
let width = info.width as usize;
|
||||||
.map_err(|e| e.to_string())
|
let height = info.height as usize;
|
||||||
.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);
|
|
||||||
|
|
||||||
// Stage 3: Flatten the image
|
let mut buffer: Vec<u8> = vec![0; width * height * 4];
|
||||||
let stage3_start = Instant::now();
|
decoder.decode_into(buffer.as_mut_slice()).unwrap();
|
||||||
let mut flat = img.into_rgba8().into_raw();
|
|
||||||
let stage3_time = stage3_start.elapsed();
|
|
||||||
// println!("Image flattening took: {:?}", stage3_time);
|
|
||||||
|
|
||||||
// Stage 4: Convert to ARGB format
|
// Reinterpret to avoid copying
|
||||||
let stage4_start = Instant::now();
|
let buffer_u32 = unsafe {
|
||||||
let mut buffer: Vec<u32> = vec![0; width * height];
|
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();
|
let total_time = total_start.elapsed();
|
||||||
println!("Total loading time: {:?}", total_time);
|
println!("Total loading time: {:?}", total_time);
|
||||||
|
|
||||||
ImflowImageBuffer {
|
ImflowImageBuffer {
|
||||||
width,
|
width,
|
||||||
height,
|
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> {
|
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()
|
||||||
@ -282,15 +85,25 @@ pub fn get_embedded_thumbnail(path: PathBuf) -> Option<Vec<u8>> {
|
|||||||
let meta = rexiv2::Metadata::new_from_path(path);
|
let meta = rexiv2::Metadata::new_from_path(path);
|
||||||
match meta {
|
match meta {
|
||||||
Ok(meta) => {
|
Ok(meta) => {
|
||||||
// println!("{:?}", meta.get_preview_images());
|
for preview in meta.get_preview_images().unwrap() {
|
||||||
meta.get_thumbnail().map(|v| v.to_vec())
|
return Some(preview.get_data().unwrap());
|
||||||
},
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
Err(_) => None,
|
Err(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_thumbnail(path: PathBuf) -> ImflowImageBuffer {
|
pub fn load_thumbnail(path: &PathBuf) -> ImflowImageBuffer {
|
||||||
if let Some(thumbnail) = get_embedded_thumbnail(path.clone()) {
|
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))
|
let decoder = image::ImageReader::new(Cursor::new(thumbnail))
|
||||||
.with_guessed_format()
|
.with_guessed_format()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -308,14 +121,25 @@ pub fn load_thumbnail(path: PathBuf) -> ImflowImageBuffer {
|
|||||||
*argb = r << 16 | g << 8 | b;
|
*argb = r << 16 | g << 8 | b;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImflowImageBuffer {
|
Some(ImflowImageBuffer {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
argb_buffer: buffer,
|
argb_buffer: buffer,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} else {
|
_ => None,
|
||||||
let reader = image::ImageReader::new(Cursor::new(read(path).unwrap()));
|
}
|
||||||
let image = reader.decode().unwrap().resize(640, 480, FilterType::Nearest);
|
}
|
||||||
|
|
||||||
|
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 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_argb_buffer(image);
|
||||||
@ -325,5 +149,4 @@ pub fn load_thumbnail(path: PathBuf) -> ImflowImageBuffer {
|
|||||||
height,
|
height,
|
||||||
argb_buffer: buffer,
|
argb_buffer: buffer,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1 +1,2 @@
|
|||||||
pub mod image;
|
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
|
// use std::fs::{self};
|
||||||
//! by John Conway. It leverages a `Canvas` together with other widgets.
|
// use std::path::{Path, PathBuf};
|
||||||
// use grid::Grid;
|
// 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::path::PathBuf;
|
||||||
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 clap::Parser;
|
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 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::{
|
// use winit::{
|
||||||
// application::ApplicationHandler,
|
// 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)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
struct Args {
|
struct Args {
|
||||||
@ -550,8 +338,8 @@ fn main() {
|
|||||||
window.set_target_fps(120);
|
window.set_target_fps(120);
|
||||||
|
|
||||||
let path = args.path.unwrap_or("./test_images".into());
|
let path = args.path.unwrap_or("./test_images".into());
|
||||||
let mut state = State::new(path);
|
let mut state = ImageStore::new(path);
|
||||||
let mut waiting = false;
|
let mut waiting = true;
|
||||||
window.set_key_repeat_delay(0.1);
|
window.set_key_repeat_delay(0.1);
|
||||||
window.set_key_repeat_rate(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) {
|
while window.is_open() && !window.is_key_down(Key::Escape) {
|
||||||
window.update();
|
window.update();
|
||||||
state.check_loaded_images();
|
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);
|
state.next_image(1);
|
||||||
|
if let Some(full) = state.get_current_image() {
|
||||||
|
show_image(&mut window, full);
|
||||||
|
} else {
|
||||||
show_image(&mut window, state.get_thumbnail());
|
show_image(&mut window, state.get_thumbnail());
|
||||||
// waiting = true;
|
waiting = true;
|
||||||
} else if !waiting && window.is_key_pressed(Key::Left, minifb::KeyRepeat::Yes) {
|
|
||||||
state.next_image(-1);
|
|
||||||
show_image(&mut window, state.get_thumbnail());
|
|
||||||
// waiting = true;
|
|
||||||
}
|
}
|
||||||
// if waiting {
|
} else if window.is_key_pressed(Key::Left, minifb::KeyRepeat::Yes) {
|
||||||
// if let Some(image) = state.get_current_image() {
|
state.next_image(-1);
|
||||||
// waiting = false;
|
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;
|
||||||
|
|
||||||
// show_image(&mut window, &image);
|
show_image(&mut window, &image);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -585,247 +381,247 @@ fn show_image(window: &mut Window, image: &ImflowImageBuffer) {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MainApp {
|
// struct MainApp {
|
||||||
is_playing: bool,
|
// is_playing: bool,
|
||||||
queued_ticks: usize,
|
// queued_ticks: usize,
|
||||||
speed: usize,
|
// speed: usize,
|
||||||
next_speed: Option<usize>,
|
// next_speed: Option<usize>,
|
||||||
version: usize,
|
// version: usize,
|
||||||
image_filter_method: FilterMethod,
|
// image_filter_method: FilterMethod,
|
||||||
current_image: Option<PathBuf>,
|
// current_image: Option<PathBuf>,
|
||||||
width: u32,
|
// width: u32,
|
||||||
available_images: Vec<PathBuf>,
|
// available_images: Vec<PathBuf>,
|
||||||
current_image_id: usize,
|
// current_image_id: usize,
|
||||||
loaded_images: HashMap<PathBuf, iced::widget::image::Handle>,
|
// loaded_images: HashMap<PathBuf, iced::widget::image::Handle>,
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
// #[derive(Debug, Clone)]
|
||||||
enum Message {
|
// enum Message {
|
||||||
TogglePlayback,
|
// TogglePlayback,
|
||||||
ToggleGrid(bool),
|
// ToggleGrid(bool),
|
||||||
Clear,
|
// Clear,
|
||||||
SpeedChanged(f32),
|
// SpeedChanged(f32),
|
||||||
Tick,
|
// Tick,
|
||||||
Next(i32),
|
// Next(i32),
|
||||||
ImageWidthChanged(u32),
|
// ImageWidthChanged(u32),
|
||||||
ImageUseNearestToggled(bool),
|
// ImageUseNearestToggled(bool),
|
||||||
}
|
// }
|
||||||
|
|
||||||
impl MainApp {
|
// impl MainApp {
|
||||||
fn new() -> Self {
|
// fn new() -> Self {
|
||||||
let mut dir: Vec<PathBuf> = fs::read_dir(Path::new("./test_images"))
|
// let mut dir: Vec<PathBuf> = fs::read_dir(Path::new("./test_images"))
|
||||||
.unwrap()
|
// .unwrap()
|
||||||
.map(|f| f.unwrap().path())
|
// .map(|f| f.unwrap().path())
|
||||||
.collect();
|
// .collect();
|
||||||
dir.sort();
|
// dir.sort();
|
||||||
let mut res = Self {
|
// let mut res = Self {
|
||||||
is_playing: false,
|
// is_playing: false,
|
||||||
queued_ticks: 0,
|
// queued_ticks: 0,
|
||||||
speed: 5,
|
// speed: 5,
|
||||||
next_speed: None,
|
// next_speed: None,
|
||||||
version: 0,
|
// version: 0,
|
||||||
image_filter_method: FilterMethod::Nearest,
|
// image_filter_method: FilterMethod::Nearest,
|
||||||
width: 1400,
|
// width: 1400,
|
||||||
current_image: Some(dir.first().unwrap().clone()),
|
// current_image: Some(dir.first().unwrap().clone()),
|
||||||
available_images: dir,
|
// available_images: dir,
|
||||||
current_image_id: 0,
|
// current_image_id: 0,
|
||||||
loaded_images: HashMap::new(),
|
// loaded_images: HashMap::new(),
|
||||||
};
|
// };
|
||||||
let _ = res.update(Message::Next(0));
|
// let _ = res.update(Message::Next(0));
|
||||||
res
|
// res
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> Task<Message> {
|
// fn update(&mut self, message: Message) -> Task<Message> {
|
||||||
match message {
|
// match message {
|
||||||
Message::Tick => {
|
// Message::Tick => {
|
||||||
self.queued_ticks = (self.queued_ticks + 1).min(self.speed);
|
// self.queued_ticks = (self.queued_ticks + 1).min(self.speed);
|
||||||
|
|
||||||
// if let Some(task) = self.grid.tick(self.queued_ticks) {
|
// // if let Some(task) = self.grid.tick(self.queued_ticks) {
|
||||||
// if let Some(speed) = self.next_speed.take() {
|
// // if let Some(speed) = self.next_speed.take() {
|
||||||
// self.speed = speed;
|
// // 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));
|
// // // return Task::perform(task, Message::Grid.with(version));
|
||||||
// }
|
// // }
|
||||||
}
|
// }
|
||||||
Message::TogglePlayback => {
|
// Message::TogglePlayback => {
|
||||||
self.is_playing = !self.is_playing;
|
// self.is_playing = !self.is_playing;
|
||||||
}
|
// }
|
||||||
Message::ToggleGrid(show_grid_lines) => {
|
// Message::ToggleGrid(show_grid_lines) => {
|
||||||
// self.grid.toggle_lines(show_grid_lines);
|
// // self.grid.toggle_lines(show_grid_lines);
|
||||||
}
|
// }
|
||||||
Message::Clear => {
|
// Message::Clear => {
|
||||||
// self.grid.clear();
|
// // self.grid.clear();
|
||||||
self.version += 1;
|
// self.version += 1;
|
||||||
}
|
// }
|
||||||
Message::SpeedChanged(speed) => {
|
// Message::SpeedChanged(speed) => {
|
||||||
if self.is_playing {
|
// if self.is_playing {
|
||||||
self.next_speed = Some(speed.round() as usize);
|
// self.next_speed = Some(speed.round() as usize);
|
||||||
} else {
|
// } else {
|
||||||
self.speed = speed.round() as usize;
|
// self.speed = speed.round() as usize;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
Message::ImageWidthChanged(image_width) => {
|
// Message::ImageWidthChanged(image_width) => {
|
||||||
self.width = image_width;
|
// self.width = image_width;
|
||||||
}
|
// }
|
||||||
Message::ImageUseNearestToggled(use_nearest) => {
|
// Message::ImageUseNearestToggled(use_nearest) => {
|
||||||
self.image_filter_method = if use_nearest {
|
// self.image_filter_method = if use_nearest {
|
||||||
FilterMethod::Nearest
|
// FilterMethod::Nearest
|
||||||
} else {
|
// } else {
|
||||||
FilterMethod::Linear
|
// FilterMethod::Linear
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
Message::Next(change) => {
|
// Message::Next(change) => {
|
||||||
let elements = self.available_images.len() as i32;
|
// let elements = self.available_images.len() as i32;
|
||||||
let new_id = (self.current_image_id as i32 + change).clamp(0, elements - 1);
|
// let new_id = (self.current_image_id as i32 + change).clamp(0, elements - 1);
|
||||||
println!(
|
// println!(
|
||||||
"updated id: {} from {} total {}",
|
// "updated id: {} from {} total {}",
|
||||||
new_id, self.current_image_id, elements
|
// new_id, self.current_image_id, elements
|
||||||
);
|
// );
|
||||||
self.current_image_id = new_id as usize;
|
// self.current_image_id = new_id as usize;
|
||||||
let path = self
|
// let path = self
|
||||||
.available_images
|
// .available_images
|
||||||
.get(self.current_image_id)
|
// .get(self.current_image_id)
|
||||||
.unwrap()
|
// .unwrap()
|
||||||
.clone();
|
// .clone();
|
||||||
self.current_image = Some(path.clone());
|
// self.current_image = Some(path.clone());
|
||||||
if !self.loaded_images.contains_key(&path.to_path_buf()) {
|
// if !self.loaded_images.contains_key(&path.to_path_buf()) {
|
||||||
// self.loaded_images.insert(
|
// // self.loaded_images.insert(
|
||||||
// path.to_path_buf(),
|
// // path.to_path_buf(),
|
||||||
// load_thumbnail(path.to_str().unwrap(), Approach::ImageRs).unwrap(),
|
// // load_thumbnail(path.to_str().unwrap(), Approach::ImageRs).unwrap(),
|
||||||
// );
|
// // );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
Task::none()
|
// Task::none()
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn subscription(&self) -> Subscription<Message> {
|
// fn subscription(&self) -> Subscription<Message> {
|
||||||
keyboard::on_key_press(|key, _modifiers| match key {
|
// 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::ArrowRight) => Some(Message::Next(1)),
|
||||||
keyboard::Key::Named(keyboard::key::Named::ArrowLeft) => Some(Message::Next(-1)),
|
// keyboard::Key::Named(keyboard::key::Named::ArrowLeft) => Some(Message::Next(-1)),
|
||||||
_ => None,
|
// _ => None,
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn view(&self) -> Element<'_, Message> {
|
// fn view(&self) -> Element<'_, Message> {
|
||||||
let version = self.version;
|
// let version = self.version;
|
||||||
let selected_speed = self.next_speed.unwrap_or(self.speed);
|
// let selected_speed = self.next_speed.unwrap_or(self.speed);
|
||||||
let controls = view_controls(
|
// let controls = view_controls(
|
||||||
self.is_playing,
|
// self.is_playing,
|
||||||
true,
|
// true,
|
||||||
// self.grid.are_lines_visible(),
|
// // self.grid.are_lines_visible(),
|
||||||
selected_speed,
|
// selected_speed,
|
||||||
// self.grid.preset(),
|
// // self.grid.preset(),
|
||||||
);
|
// );
|
||||||
|
|
||||||
let content = column![
|
// let content = column![
|
||||||
// image("/media/nfs/sphotos/Images/24-08-11-Copenhagen/24-08-12/20240812-175614_DSC03844.JPG").into(),
|
// // 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.grid.view().map(Message::Grid.with(version)),
|
||||||
self.image(),
|
// self.image(),
|
||||||
controls,
|
// controls,
|
||||||
]
|
// ]
|
||||||
.height(Fill);
|
// .height(Fill);
|
||||||
|
|
||||||
container(content).width(Fill).height(Fill).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()
|
// // image("/media/nfs/sphotos/Images/24-08-11-Copenhagen/24-08-12/20240812-175614_DSC03844.JPG").into()
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn image(&self) -> Column<Message> {
|
// fn image(&self) -> Column<Message> {
|
||||||
let width = self.width;
|
// let width = self.width;
|
||||||
let filter_method = self.image_filter_method;
|
// let filter_method = self.image_filter_method;
|
||||||
|
|
||||||
Self::container("Image")
|
// Self::container("Image")
|
||||||
.push("An image that tries to keep its aspect ratio.")
|
// .push("An image that tries to keep its aspect ratio.")
|
||||||
.push(self.ferris(
|
// .push(self.ferris(
|
||||||
width,
|
// width,
|
||||||
filter_method,
|
// filter_method,
|
||||||
self.current_image.as_ref().unwrap().as_ref(),
|
// self.current_image.as_ref().unwrap().as_ref(),
|
||||||
))
|
// ))
|
||||||
.push(slider(100..=1500, width, Message::ImageWidthChanged))
|
// .push(slider(100..=1500, width, Message::ImageWidthChanged))
|
||||||
.push(text!("Width: {width} px").width(Fill).align_x(Center))
|
// .push(text!("Width: {width} px").width(Fill).align_x(Center))
|
||||||
.push(
|
// .push(
|
||||||
checkbox(
|
// checkbox(
|
||||||
"Use nearest interpolation",
|
// "Use nearest interpolation",
|
||||||
filter_method == FilterMethod::Nearest,
|
// filter_method == FilterMethod::Nearest,
|
||||||
)
|
// )
|
||||||
.on_toggle(Message::ImageUseNearestToggled),
|
// .on_toggle(Message::ImageUseNearestToggled),
|
||||||
)
|
// )
|
||||||
.align_x(Center)
|
// .align_x(Center)
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn container(title: &str) -> Column<'_, Message> {
|
// fn container(title: &str) -> Column<'_, Message> {
|
||||||
column![text(title).size(50)].spacing(20)
|
// column![text(title).size(50)].spacing(20)
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn ferris<'a>(
|
// fn ferris<'a>(
|
||||||
&self,
|
// &self,
|
||||||
width: u32,
|
// width: u32,
|
||||||
filter_method: iced::widget::image::FilterMethod,
|
// filter_method: iced::widget::image::FilterMethod,
|
||||||
path: &Path,
|
// path: &Path,
|
||||||
) -> Container<'a, Message> {
|
// ) -> Container<'a, Message> {
|
||||||
if self.loaded_images.get(path).is_none() {
|
// if self.loaded_images.get(path).is_none() {
|
||||||
return center(text("loading"));
|
// return center(text("loading"));
|
||||||
}
|
// }
|
||||||
let img = iced::widget::image::Image::new(self.loaded_images.get(path).unwrap());
|
// let img = iced::widget::image::Image::new(self.loaded_images.get(path).unwrap());
|
||||||
center(
|
// center(
|
||||||
// This should go away once we unify resource loading on native
|
// // This should go away once we unify resource loading on native
|
||||||
// platforms
|
// // platforms
|
||||||
img.filter_method(filter_method)
|
// img.filter_method(filter_method)
|
||||||
.width(Length::Fixed(width as f32)),
|
// .width(Length::Fixed(width as f32)),
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
impl Default for MainApp {
|
// impl Default for MainApp {
|
||||||
fn default() -> Self {
|
// fn default() -> Self {
|
||||||
Self::new()
|
// Self::new()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn view_controls<'a>(
|
// fn view_controls<'a>(
|
||||||
is_playing: bool,
|
// is_playing: bool,
|
||||||
is_grid_enabled: bool,
|
// is_grid_enabled: bool,
|
||||||
speed: usize,
|
// speed: usize,
|
||||||
// preset: Preset,
|
// // preset: Preset,
|
||||||
) -> Element<'a, Message> {
|
// ) -> Element<'a, Message> {
|
||||||
let playback_controls = row![
|
// let playback_controls = row![
|
||||||
button(if is_playing { "Pause" } else { "Play" }).on_press(Message::TogglePlayback),
|
// button(if is_playing { "Pause" } else { "Play" }).on_press(Message::TogglePlayback),
|
||||||
button("Previous")
|
// button("Previous")
|
||||||
.on_press(Message::Next(-1))
|
// .on_press(Message::Next(-1))
|
||||||
.style(button::secondary),
|
// .style(button::secondary),
|
||||||
button("Next")
|
// button("Next")
|
||||||
.on_press(Message::Next(1))
|
// .on_press(Message::Next(1))
|
||||||
.style(button::secondary),
|
// .style(button::secondary),
|
||||||
]
|
// ]
|
||||||
.spacing(10);
|
// .spacing(10);
|
||||||
|
|
||||||
let speed_controls = row![
|
// let speed_controls = row![
|
||||||
slider(1.0..=1000.0, speed as f32, Message::SpeedChanged),
|
// slider(1.0..=1000.0, speed as f32, Message::SpeedChanged),
|
||||||
text!("x{speed}").size(16),
|
// text!("x{speed}").size(16),
|
||||||
]
|
// ]
|
||||||
.align_y(Center)
|
// .align_y(Center)
|
||||||
.spacing(10);
|
// .spacing(10);
|
||||||
|
|
||||||
row![
|
// row![
|
||||||
playback_controls,
|
// playback_controls,
|
||||||
speed_controls,
|
// speed_controls,
|
||||||
// checkbox("Grid", is_grid_enabled).on_toggle(Message::ToggleGrid),
|
// // checkbox("Grid", is_grid_enabled).on_toggle(Message::ToggleGrid),
|
||||||
// row![
|
// // row![
|
||||||
// pick_list(preset::ALL, Some(preset), Message::PresetPicked),
|
// // pick_list(preset::ALL, Some(preset), Message::PresetPicked),
|
||||||
// button("Clear")
|
// // button("Clear")
|
||||||
// .on_press(Message::Clear)
|
// // .on_press(Message::Clear)
|
||||||
// .style(button::danger)
|
// // .style(button::danger)
|
||||||
// ]
|
// // ]
|
||||||
// .spacing(10)
|
// // .spacing(10)
|
||||||
]
|
// ]
|
||||||
.padding(10)
|
// .padding(10)
|
||||||
.spacing(20)
|
// .spacing(20)
|
||||||
.align_y(Center)
|
// .align_y(Center)
|
||||||
.into()
|
// .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