Working fast minifb

This commit is contained in:
Dawid Pietrykowski 2025-03-30 17:04:36 +02:00
parent dd982a9f42
commit 1b75a04ab2
5 changed files with 1362 additions and 108 deletions

818
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -4,17 +4,33 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
env_logger = "0.11.7"
iced = { version = "0.13.1", features = ["debug", "canvas", "tokio", "image"]} iced = { version = "0.13.1", features = ["debug", "canvas", "tokio", "image"]}
image = "0.25.6" image = "0.25.6"
itertools = "0.12" itertools = "0.12"
memmap2 = "0.9.5" memmap2 = "0.9.5"
minifb = "0.28.0"
pollster = "0.4.0"
# rustc-hash.workspace = true # rustc-hash.workspace = true
tokio = { version = "1.44.1", features = ["sync"] } tokio = { version = "1.44.1", features = ["sync"] }
tracing-subscriber = "0.3" tracing-subscriber = "0.3"
wgpu = "24.0.3"
winit = "0.30.9"
zune-image = {version = "0.4.15", features = ["all"]} zune-image = {version = "0.4.15", features = ["all"]}
[profile.dev.package.zune-jpeg] [profile.dev.package.zune-jpeg]
opt-level = 3 opt-level = 3
[profile.release]
opt-level = 3
debug = false
debug-assertions = false
overflow-checks = false
lto = false
panic = 'unwind'
incremental = false
codegen-units = 32
rpath = false
[dev-dependencies] [dev-dependencies]
criterion = "0.3" criterion = "0.3"

View File

@ -1,17 +1,44 @@
#![allow(unused)] #![allow(unused)]
use std::iter; use std::iter;
use std::time::Duration;
use criterion::BenchmarkId; use criterion::BenchmarkId;
use criterion::{Criterion, black_box, criterion_group, criterion_main}; use criterion::{Criterion, black_box, criterion_group, criterion_main};
use imflow::image::{Approach, load_thumbnail}; use imflow::image::{load_available_images, load_image_argb, load_image_argb_imagers, load_thumbnail, Approach};
const PATH: &str = "test_images/20240811-194516_DSC02274.JPG"; const PATH: &str = "test_images";
pub fn criterion_benchmark(c: &mut Criterion) { pub fn criterion_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("from_elem"); let mut group = c.benchmark_group("image_decode");
group.bench_function("mmap", |b| b.iter(|| load_thumbnail(PATH, Approach::Mmap)));
group.bench_function("path", |b| b.iter(|| load_thumbnail(PATH, Approach::Path))); group
group.bench_function("iced", |b| b.iter(|| load_thumbnail(PATH, Approach::Iced))); .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)
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.finish(); group.finish();
} }

View File

@ -1,13 +1,28 @@
use iced::widget::image::Handle;
use iced::widget::image::Image as IcedImage;
use image::DynamicImage;
use image::ImageReader;
use memmap2::Mmap;
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::File; use std::fs::File;
use std::io;
use std::io::Read;
use std::ops::Deref;
use std::path::PathBuf;
use std::time::Instant;
pub enum Approach { pub enum Approach {
Mmap, Mmap,
Path, Path,
ImageRs,
Iced, Iced,
ImageRsPath,
} }
fn convert_rgb_to_rgba(rgb_data: Vec<Vec<u8>>) -> Vec<u8> {
pub fn convert_zune_rgb_to_rgba(rgb_data: Vec<Vec<u8>>) -> Vec<u8> {
let r_channel = &rgb_data[0]; let r_channel = &rgb_data[0];
let num_pixels = r_channel.len() / 3; let num_pixels = r_channel.len() / 3;
@ -22,56 +37,206 @@ fn convert_rgb_to_rgba(rgb_data: Vec<Vec<u8>>) -> Vec<u8> {
rgba_data 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 {
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( pub fn load_thumbnail(
path: &str, path: &str,
approach: Approach, approach: Approach,
) -> Result<iced::widget::image::Handle, String> { ) -> Result<iced::widget::image::Handle, String> {
match approach { match approach {
Approach::Mmap => { Approach::Mmap => {
let file = File::open(path).map_err(|e| e.to_string())?; let mmap = map_file(path).unwrap();
let mmap = unsafe { memmap2::Mmap::map(&file) }.map_err(|e| e.to_string())?;
println!("mapped file"); println!("mapped file");
let img = zune_image::image::Image::read(&*mmap, DecoderOptions::default()).unwrap(); let img = read_zune_image(mmap.deref())?;
let width = img.dimensions().0 as u32; let width = img.dimensions().0 as u32;
let height = img.dimensions().1 as u32; let height = img.dimensions().1 as u32;
println!("loaded"); println!("loaded");
let flat = img.flatten_to_u8(); let flat = flatten_zune_image(&img);
println!("flattened"); println!("flattened");
let rgba = convert_rgb_to_rgba(flat); let rgba = convert_zune_rgb_to_rgba(flat);
println!("rgbad"); println!("rgbad");
let conv = iced::widget::image::Handle::from_rgba(width, height, rgba); let conv = create_iced_handle(width, height, rgba);
println!("iced"); println!("iced");
Ok(conv) Ok(conv)
} }
Approach::Path => { Approach::Path => {
let img = zune_image::image::Image::open_with_options(path, DecoderOptions::default()) let img = read_zune_image_path(path);
.unwrap();
let width = img.dimensions().0 as u32; let width = img.dimensions().0 as u32;
let height = img.dimensions().1 as u32; let height = img.dimensions().1 as u32;
println!("loaded"); println!("loaded");
let flat = img.flatten_to_u8(); let flat = flatten_zune_image(&img);
println!("flattened"); println!("flattened");
let rgba = convert_rgb_to_rgba(flat); let rgba = convert_zune_rgb_to_rgba(flat);
println!("rgbad"); println!("rgbad");
let conv = iced::widget::image::Handle::from_rgba(width, height, rgba); let conv = create_iced_handle(width, height, rgba);
println!("iced"); println!("iced");
Ok(conv) Ok(conv)
} }
Approach::Iced => { Approach::ImageRs => {
let file = File::open(path).map_err(|e| e.to_string())?; let mmap = map_file(path).unwrap();
let mmap = unsafe { memmap2::Mmap::map(&file) }.map_err(|e| e.to_string())?; let img = image::load_from_memory(mmap.deref()).map_err(|e| e.to_string())?;
let img = image::load_from_memory(&mmap).map_err(|e| e.to_string())?;
let width = img.width(); let width = img.width();
let height = img.height(); let height = img.height();
println!("loaded"); println!("loaded");
let rgba = img.into_rgba8().into_raw(); let rgba = flatten_image_image(img);
println!("rgbad"); println!("rgbad");
let conv = iced::widget::image::Handle::from_rgba(width, height, rgba); let conv = create_iced_handle(width, height, rgba);
println!("iced"); println!("iced");
Ok(conv) 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();
// 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);
// 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 flat.chunks_mut(3).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,
}
}
pub struct ImflowImageBuffer {
pub width: usize,
pub height: usize,
pub argb_buffer: Vec<u32>,
}
pub fn load_image_argb_imagers(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);
// 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);
// 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);
// Stage 4: Convert to ARGB format
let stage4_start = Instant::now();
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 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,
}
}
pub fn load_available_images(dir: PathBuf) -> Vec<PathBuf> {
let mut files: Vec<PathBuf> = fs::read_dir(dir)
.unwrap()
.map(|f| f.unwrap().path())
.collect();
files.sort();
files
}

View File

@ -5,8 +5,9 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::{self}; use std::fs::{self};
use std::io::Read; use std::io::Read;
use std::ops::Deref;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::{self, Duration}; use std::time::{self, Duration, Instant};
use iced::futures::AsyncReadExt; use iced::futures::AsyncReadExt;
use iced::widget::shader::wgpu::core::command::LoadOp; use iced::widget::shader::wgpu::core::command::LoadOp;
@ -16,22 +17,391 @@ use iced::widget::{
Column, Container, button, center, checkbox, column, container, pick_list, row, slider, text, Column, Container, button, center, checkbox, column, container, pick_list, row, slider, text,
}; };
use iced::{Center, Element, Fill, Length, Size, Subscription, Task, Theme, keyboard}; use iced::{Center, Element, Fill, Length, Size, Subscription, Task, Theme, keyboard};
use image::{self, DynamicImage, EncodableLayout, ImageBuffer, ImageReader}; use image::{self, DynamicImage, EncodableLayout, GenericImageView, ImageBuffer, ImageReader};
use imflow::image::{Approach, load_thumbnail}; use imflow::image::{
flatten_image_image, flatten_zune_image, load_available_images, load_image_argb, load_image_argb_imagers, load_thumbnail, map_file, map_file_path, read_zune_image, Approach, ImflowImageBuffer
};
use minifb::{Key, Window, WindowOptions};
use zune_image::codecs::qoi::zune_core::options::DecoderOptions; // for general image operations use zune_image::codecs::qoi::zune_core::options::DecoderOptions; // for general image operations
// use image::io::Reader as ImageReader; // specifically for Reader // use image::io::Reader as ImageReader; // specifically for Reader
pub fn main() -> iced::Result { use std::sync::Arc;
tracing_subscriber::fmt::init();
iced::application("Game of Life - Iced", GameOfLife::update, GameOfLife::view) // use winit::{
.subscription(GameOfLife::subscription) // application::ApplicationHandler,
.theme(|_| Theme::Dark) // event::WindowEvent,
.antialiasing(true) // event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
.centered() // window::{Window, WindowId},
.window_size(Size::new(1500.0, 1000.0)) // };
.run() // struct State {
// window: Arc<Window>,
// device: wgpu::Device,
// queue: wgpu::Queue,
// size: winit::dpi::PhysicalSize<u32>,
// surface: wgpu::Surface<'static>,
// surface_format: wgpu::TextureFormat,
// }
// impl State {
// async fn new(window: Arc<Window>) -> State {
// let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
// let adapter = instance
// .request_adapter(&wgpu::RequestAdapterOptions::default())
// .await
// .unwrap();
// let (device, queue) = adapter
// .request_device(&wgpu::DeviceDescriptor::default(), None)
// .await
// .unwrap();
// let size = window.inner_size();
// let surface = instance.create_surface(window.clone()).unwrap();
// let cap = surface.get_capabilities(&adapter);
// let surface_format = cap.formats[0];
// let state = State {
// window,
// device,
// queue,
// size,
// surface,
// surface_format,
// };
// // Configure surface for the first time
// state.configure_surface();
// state
// }
// fn get_window(&self) -> &Window {
// &self.window
// }
// fn configure_surface(&self) {
// let surface_config = wgpu::SurfaceConfiguration {
// usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
// format: self.surface_format,
// // Request compatibility with the sRGB-format texture view were going to create later.
// view_formats: vec![self.surface_format.add_srgb_suffix()],
// alpha_mode: wgpu::CompositeAlphaMode::Auto,
// width: self.size.width,
// height: self.size.height,
// desired_maximum_frame_latency: 2,
// present_mode: wgpu::PresentMode::AutoVsync,
// };
// self.surface.configure(&self.device, &surface_config);
// }
// fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
// self.size = new_size;
// // reconfigure the surface
// self.configure_surface();
// }
// // fn render(&mut self) {
// // // Create texture view
// // let surface_texture = self
// // .surface
// // .get_current_texture()
// // .expect("failed to acquire next swapchain texture");
// // let texture_view = surface_texture
// // .texture
// // .create_view(&wgpu::TextureViewDescriptor {
// // // Without add_srgb_suffix() the image we will be working with
// // // might not be "gamma correct".
// // format: Some(self.surface_format.add_srgb_suffix()),
// // ..Default::default()
// // });
// // // Renders a GREEN screen
// // let mut encoder = self.device.create_command_encoder(&Default::default());
// // // Create the renderpass which will clear the screen.
// // let renderpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
// // label: None,
// // color_attachments: &[Some(wgpu::RenderPassColorAttachment {
// // view: &texture_view,
// // resolve_target: None,
// // ops: wgpu::Operations {
// // load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
// // store: wgpu::StoreOp::Store,
// // },
// // })],
// // depth_stencil_attachment: None,
// // timestamp_writes: None,
// // occlusion_query_set: None,
// // });
// // // If you wanted to call any drawing commands, they would go here.
// // // End the renderpass.
// // drop(renderpass);
// // // Submit the command in the queue to execute
// // self.queue.submit([encoder.finish()]);
// // self.window.pre_present_notify();
// // surface_texture.present();
// // }
// fn render(&mut self) {
// let mmap = map_file("test_images/20240811-194516_DSC02274.JPG").unwrap();
// println!("mapped file");
// let img = read_zune_image(mmap.deref()).unwrap();
// 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 rgb_bytes = flat[0].as_slice();
// // Assuming `self.rgb_bytes` is your buffer containing RGB data.
// let texture_extent = wgpu::Extent3d {
// width: width,
// height: height,
// depth_or_array_layers: 1,
// };
// // Create a wgpu texture
// let texture = self.device.create_texture(&wgpu::TextureDescriptor {
// label: Some("RGB Texture"),
// size: texture_extent,
// mip_level_count: 1,
// sample_count: 1,
// dimension: wgpu::TextureDimension::D2,
// format: wgpu::TextureFormat::Rgba8Unorm, // It's better to use RGBA with proper padding
// usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
// view_formats: &[],
// });
// // Upload your RGB data into the texture
// self.queue.write_texture(
// wgpu::TexelCopyTextureInfo{
// texture: &texture,
// mip_level: 0,
// origin: wgpu::Origin3d::ZERO,
// aspect: wgpu::TextureAspect::All,
// },
// &rgb_bytes,
// wgpu::TexelCopyBufferLayout {
// offset: 0,
// bytes_per_row: Some(4 * width), // Assuming padded row length
// rows_per_image: Some(height),
// },
// texture_extent,
// );
// // Create a texture view
// let surface_texture = self
// .surface
// .get_current_texture()
// .expect("failed to acquire next swapchain texture");
// let texture_view = surface_texture.texture.create_view(&wgpu::TextureViewDescriptor {
// format: Some(self.surface_format.add_srgb_suffix()),
// ..Default::default()
// });
// let rgb_texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
// let mut encoder = self.device.create_command_encoder(&Default::default());
// // Create the renderpass
// let mut renderpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
// label: None,
// color_attachments: &[Some(wgpu::RenderPassColorAttachment {
// view: &texture_view,
// resolve_target: None,
// ops: wgpu::Operations {
// load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
// store: wgpu::StoreOp::Store,
// },
// })],
// depth_stencil_attachment: None,
// timestamp_writes: None,
// occlusion_query_set: None,
// });
// // Bind and draw
// // renderpass.set_pipeline(&self.pipeline); // Ensure self.pipeline is your render pipeline setup
// // renderpass.set_bind_group(0, &self.texture_bind_group, &[]); // Assuming you have a bind group which holds the texture
// renderpass.draw(0..3, 0..1); // Draws a triangle to cover the viewport, modify as needed for quads
// // End the renderpass
// drop(renderpass);
// // Submit the command buffer
// // self.queue.submit(iter::once(encoder.finish()));
// self.window.pre_present_notify();
// surface_texture.present();
// }
// }
// #[derive(Default)]
// struct App {
// state: Option<State>,
// }
// impl ApplicationHandler for App {
// fn resumed(&mut self, event_loop: &ActiveEventLoop) {
// // Create window object
// let window = Arc::new(
// event_loop
// .create_window(Window::default_attributes())
// .unwrap(),
// );
// let state = pollster::block_on(State::new(window.clone()));
// self.state = Some(state);
// window.request_redraw();
// }
// fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
// let state = self.state.as_mut().unwrap();
// match event {
// WindowEvent::CloseRequested => {
// println!("The close button was pressed; stopping");
// event_loop.exit();
// }
// WindowEvent::RedrawRequested => {
// state.render();
// // Emits a new redraw requested event.
// state.get_window().request_redraw();
// }
// WindowEvent::Resized(size) => {
// // Reconfigures the size of the surface. We do not re-render
// // here as this event is always followed up by redraw request.
// state.resize(size);
// }
// _ => (),
// }
// }
// }
// pub fn main() -> iced::Result {
// tracing_subscriber::fmt::init();
// iced::application("Game of Life - Iced", GameOfLife::update, GameOfLife::view)
// .subscription(GameOfLife::subscription)
// .theme(|_| Theme::Dark)
// .antialiasing(true)
// .centered()
// .window_size(Size::new(1500.0, 1000.0))
// .run()
// }
// fn main() {
// let mut window = match Window::new("Test", 640, 400, WindowOptions::default()) {
// Ok(win) => win,
// Err(err) => {
// println!("Unable to create window {}", err);
// return;
// }
// }
// }
// fn main() {
// // wgpu uses `log` for all of our logging, so we initialize a logger with the `env_logger` crate.
// //
// // To change the log level, set the `RUST_LOG` environment variable. See the `env_logger`
// // documentation for more information.
// env_logger::init();
// let event_loop = EventLoop::new().unwrap();
// // When the current loop iteration finishes, immediately begin a new
// // iteration regardless of whether or not new events are available to
// // process. Preferred for applications that want to render as fast as
// // possible, like games.
// event_loop.set_control_flow(ControlFlow::Poll);
// // When the current loop iteration finishes, suspend the thread until
// // another event arrives. Helps keeping CPU utilization low if nothing
// // is happening, which is preferred if the application might be idling in
// // the background.
// // event_loop.set_control_flow(ControlFlow::Wait);
// let mut app = App::default();
// event_loop.run_app(&mut app).unwrap();
// }
//
struct State {
current_image_id: usize,
loaded_images: HashMap<PathBuf, ImflowImageBuffer>,
available_images: Vec<PathBuf>,
current_image_path: PathBuf,
}
impl State {
fn new() -> Self {
let current_image_id: usize = 0;
let mut loaded_images: HashMap<PathBuf, ImflowImageBuffer> = HashMap::new();
let available_images = load_available_images("./test_images".into());
let new_path = available_images[0].clone();
let current_image = load_image_argb_imagers(new_path.clone());
loaded_images.insert(new_path.clone(), current_image);
Self {
current_image_id,
loaded_images,
available_images,
current_image_path: new_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) {
let new_image = load_image_argb(new_path.clone());
self.loaded_images.insert(new_path.clone(), new_image);
}
self.current_image_path = new_path;
}
fn get_current_image(&self) -> &ImflowImageBuffer {
self.loaded_images.get(&self.current_image_path).unwrap()
}
}
fn main() {
const WIDTH: usize = 1920;
const HEIGHT: usize = 1080;
let mut window = Window::new(
"Test - ESC to exit",
WIDTH,
HEIGHT,
WindowOptions::default(),
)
.unwrap_or_else(|e| {
panic!("{}", e);
});
window.set_target_fps(60);
let mut state = State::new();
show_image(&mut window, state.get_current_image());
while window.is_open() && !window.is_key_down(Key::Escape) {
window.update();
if window.is_key_down(Key::Right) {
state.next_image(1);
show_image(&mut window, state.get_current_image());
} else if window.is_key_down(Key::Left) {
state.next_image(-1);
show_image(&mut window, state.get_current_image());
}
}
}
fn show_image(window: &mut Window, image: &ImflowImageBuffer) {
window
.update_with_buffer(&image.argb_buffer, image.width, image.height)
.unwrap();
} }
struct GameOfLife { struct GameOfLife {
@ -145,7 +515,7 @@ impl GameOfLife {
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::Iced).unwrap(), load_thumbnail(path.to_str().unwrap(), Approach::ImageRs).unwrap(),
); );
} }
} }