Files
yggdrasil/userspace/graphics/iv/src/main.rs
T

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
}
}
}