use std::{ path::{Path, PathBuf}, process::ExitCode, sync::{Arc, RwLock}, }; use clap::Parser; use libcolors::{ application::{ window::{EventOutcome, Window}, Application, }, event::KeyEvent, geometry::{Point, Rectangle}, input::Key, surface::{MappedSurface, Surface, VecSurface}, }; use crate::{error::Error, format::ImageData}; mod error; mod format; #[derive(Debug, Parser)] struct Args { filename: PathBuf, } struct State { image_surface: VecSurface, target_surface: VecSurface, scale: ScaleMethod, } #[derive(Debug, Clone, Copy)] pub enum ScaleMethod { Fit, Identity, Maximize(u32), // x scaling Minimize(u32), // 1/x scaling } impl ScaleMethod { pub fn bigger(self) -> ScaleMethod { match self { Self::Fit => ScaleMethod::Maximize(1), Self::Identity => ScaleMethod::Maximize(2), Self::Maximize(n) if n < 8 => Self::Maximize(n * 2), Self::Minimize(2) => Self::Identity, Self::Minimize(n) => Self::Minimize(n / 2), _ => self, } } pub fn smaller(self) -> ScaleMethod { match self { Self::Fit => ScaleMethod::Identity, Self::Identity => Self::Minimize(2), Self::Maximize(2) => Self::Identity, Self::Maximize(n) => Self::Maximize(n / 2), Self::Minimize(n) if n < 8 => Self::Minimize(n * 2), _ => self, } } } impl State { fn load_image>(path: P) -> Result { let image = ImageData::load_image(path)?; let image_surface = image.create_surface(); let target_surface = image_surface.clone(); Ok(Self { image_surface, target_surface, scale: ScaleMethod::Fit, }) } fn handle_key(&mut self, key: KeyEvent) -> EventOutcome { match key.key { Key::Up => { self.scale = self.scale.bigger(); EventOutcome::Redraw } Key::Down => { self.scale = self.scale.smaller(); EventOutcome::Redraw } _ => EventOutcome::None, } } fn scale_to_aspect(&mut self, target_width: u32, target_height: u32) { let target_aspect = self.image_surface.width() as f32 / self.image_surface.height() as f32; let w; let h; if (target_height as f32 * target_aspect) as u32 > target_width { w = target_width; h = (target_width as f32 / target_aspect) as u32; } else { w = (target_height as f32 * target_aspect) as u32; h = target_height; } if self.target_surface.width() == w && self.target_surface.height() == h { return; } let mut destination = VecSurface::new(w, h); format::scale_surface(&self.image_surface, &mut destination); self.target_surface = destination; } fn redraw(&mut self, surface: &mut MappedSurface) { // Scale to match aspect let (target_width, target_height) = match self.scale { ScaleMethod::Fit => (surface.width(), surface.height()), ScaleMethod::Identity => (self.image_surface.width(), self.image_surface.height()), ScaleMethod::Maximize(n) => ( self.image_surface.width() * n, self.image_surface.height() * n, ), ScaleMethod::Minimize(n) => ( self.image_surface.width() / n, self.image_surface.height() / n, ), }; self.scale_to_aspect(target_width, target_height); let iw = self.target_surface.width(); let ih = self.target_surface.height(); let sw = surface.width(); let sh = surface.height(); let center = surface.center(); let (off_x, w) = if iw > sw { ((iw - sw) / 2, sw) } else { (0, iw) }; let (off_y, h) = if ih > sh { ((ih - sh) / 2, sh) } else { (0, ih) }; let origin = center - Point::new(w / 2, h / 2); let rect = Rectangle { x: off_x, y: off_y, w, h, }; surface.fill(0); surface.copy_rect(&self.target_surface, rect, origin); } } fn run(args: Args) -> Result { let state = State::load_image(args.filename)?; let mut application = Application::new()?; let mut window = Window::new(&application)?; let state = Arc::new(RwLock::new(state)); let s = state.clone(); window.set_on_redraw_requested(move |surface| { s.write().unwrap().redraw(surface); }); let s = state.clone(); window.set_on_key_pressed(move |key| s.write().unwrap().handle_key(key)); application.add_window(window); Ok(application.run()) } fn main() -> ExitCode { logsink::setup_logging(true); let args = Args::parse(); match run(args) { Ok(code) => code, Err(error) => { eprintln!("{error}"); ExitCode::FAILURE } } }