Last active
June 15, 2023 15:51
-
-
Save cambiata/2a7f1510d27dacc322157f3e06d3a60e to your computer and use it in GitHub Desktop.
Rust image viewer from scratch: Matt Davies - https://www.youtube.com/watch?v=1yofBPRx864
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "testwinit" | |
version = "0.1.0" | |
edition = "2021" | |
authors = ["Matt Davies"] | |
description = "Rust image viewer from scratch - https://www.youtube.com/watch?v=1yofBPRx864" | |
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | |
[dependencies] | |
clap = { version = "4.3.4", features = ["derive"] } | |
image = "0.24.6" | |
pixels = "0.13.0" | |
thiserror = "1.0.40" | |
winit = "0.28.6" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::cmp::max; | |
use clap::Parser; | |
use pixels::{Pixels, SurfaceTexture}; | |
use thiserror::Error; | |
use winit::{ | |
dpi::PhysicalSize, | |
error::OsError, | |
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, | |
event_loop::{ControlFlow, EventLoop}, | |
window::WindowBuilder, | |
}; | |
const SCREEN_PERCENT: u32 = 90; | |
#[derive(Parser, Debug)] | |
#[clap(author, version, about, long_about = None)] | |
struct Config { | |
/// Name of the image to view. | |
#[arg(default_value = "./dragon.jpg")] | |
file_name: String, | |
} | |
#[derive(Debug, Error)] | |
enum RvuError { | |
#[error("An error occurred while loading the image.")] | |
IoError(#[from] std::io::Error), | |
#[error("An error occurred while processing the image.")] | |
ImageError(#[from] image::ImageError), | |
#[error("Unable to calculate maximum screen size of your primary monitor.")] | |
NoPrimaryMonitor, | |
#[error("Unable to create window.")] | |
WindowError(#[from] OsError), | |
#[error("Unable to create pixel buffer to display image.")] | |
PixelError(#[from] pixels::Error), | |
} | |
type Result<T> = std::result::Result<T, RvuError>; | |
fn main() -> Result<()> { | |
let config = Config::parse(); | |
let image = image::io::Reader::open(&config.file_name)?.decode()?; | |
let event_loop = EventLoop::new(); | |
let primary_monitor = event_loop | |
.primary_monitor() | |
.ok_or(RvuError::NoPrimaryMonitor)?; | |
let screen_size = primary_monitor.size(); | |
let max_screen_size = ( | |
screen_size.width * SCREEN_PERCENT / 100, | |
screen_size.height * SCREEN_PERCENT / 100, | |
); | |
// Calculate the scale | |
let horz_scale = calc_scale(max_screen_size.0, image.width()); | |
let vert_scale = calc_scale(max_screen_size.1, image.height()); | |
let scale = max(horz_scale, vert_scale); | |
let window_inner_size = PhysicalSize::new(image.width() / scale, image.height() / scale); | |
let window = WindowBuilder::new() | |
.with_title(&config.file_name) | |
.with_inner_size(window_inner_size) | |
.build(&event_loop)?; | |
let surface = SurfaceTexture::new(window_inner_size.width, window_inner_size.height, &window); | |
let mut pixels = Pixels::new(image.width(), image.height(), surface)?; | |
let image_bytes = image.as_rgb8().unwrap().as_flat_samples(); | |
let image_bytes = image_bytes.as_slice(); | |
let pixels_bytes = pixels.frame_mut(); | |
image_bytes | |
.chunks_exact(3) | |
.zip(pixels_bytes.chunks_exact_mut(4)) | |
.for_each(|(image_pixel, pixel)| { | |
pixel[0] = image_pixel[0]; | |
pixel[1] = image_pixel[1]; | |
pixel[2] = image_pixel[2]; | |
pixel[3] = 0xff; | |
}); | |
println!( | |
"Window size: ({}, {})", | |
window_inner_size.width, window_inner_size.height | |
); | |
println!("Backbuffer size: ({}, {})", image.width(), image.height()); | |
event_loop.run(move |event, _, control_flow| { | |
*control_flow = ControlFlow::Wait; | |
match event { | |
Event::WindowEvent { window_id, event } if window_id == window.id() => match event { | |
WindowEvent::Resized(size) => { | |
resize(&mut pixels, &size); | |
} | |
WindowEvent::CloseRequested | |
| WindowEvent::KeyboardInput { | |
input: | |
KeyboardInput { | |
state: ElementState::Pressed, | |
virtual_keycode: Some(VirtualKeyCode::Escape), | |
.. | |
}, | |
.. | |
} => *control_flow = ControlFlow::Exit, | |
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { | |
resize(&mut pixels, new_inner_size); | |
} | |
_ => {} | |
}, | |
Event::RedrawRequested(_) => { | |
let _ = pixels.render(); | |
} | |
_ => {} | |
} | |
}); | |
} | |
fn calc_scale(max_size: u32, current_size: u32) -> u32 { | |
if max_size >= current_size { | |
1 | |
} else { | |
((current_size as f32) / (max_size as f32)).ceil() as u32 | |
} | |
} | |
fn resize(pixels: &mut Pixels, size: &PhysicalSize<u32>) { | |
pixels.resize_surface(size.width, size.height); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment