193 lines
5.1 KiB
Rust
193 lines
5.1 KiB
Rust
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<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
|
|
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<ExitCode, Error> {
|
|
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
|
|
}
|
|
}
|
|
}
|