use std::error; use std::io; use std::thread; use std::time::Instant; use image::imageops; use image::EncodableLayout; use lb::buf; use lb::buf::Buffer; use lb::img; use lb::mat; use lb::prelude::*; use lb::typeset; use lb::typesetters; const FALLBACK_RATIO: f32 = 0.44; const FALLBACK_SIZE: mat::Size = mat::Size { width: 96, height: 32, }; struct Watch(Instant, bool); impl Watch { fn new(print: bool) -> Watch { Watch(Instant::now(), print) } fn lap(&mut self, name: &str) { if self.1 { eprintln!("{} took: {} ms", name, self.0.elapsed().as_millis()); } self.0 = Instant::now(); } } fn main() -> Result<(), Box> { let args: Vec = std::env::args().collect(); if args.len() < 3 { eprintln!("error: expected at least 2 CLI arguments"); eprintln!("usage: cargo run --example image -- "); std::process::exit(1); } let path = args.get(2).unwrap(); let bench = args .get(3) .filter(|a| *a == "-b" || *a == "--bench") .is_some(); match args[1].as_str() { "block" => print(&typesetters::Block, &path, bench), "half" => print(&typesetters::Half, &path, bench), "quadrant" => print(&typesetters::Quadrant, &path, bench), "sextant" => print(&typesetters::Sextant, &path, bench), "smooth" => print(&typesetters::Smooth, &path, bench), "asymmetric" => print(&typesetters::Asymmetric, &path, bench), _ => { eprintln!("unknown typesetter"); eprintln!("available typesetters are: block, half, quadrant, sextant, and asymmetric"); eprintln!("usage: cargo run --example image -- "); Ok(()) } } } fn print(typesetter: &T, path: &str, bench: bool) -> Result<(), Box> where T: typeset::Typesetter + Sync, { let mut watch = Watch::new(bench); let img = image::open(path)?; watch.lap("opening"); let ratio = correction_ratio().unwrap_or(FALLBACK_RATIO); let img_size: lb::Size = (img.width() as usize, img.height() as usize).into(); let buffer_size = img_size.to_buffer_size(typesetter); let buffer_size = typesetter.fix_aspect_ratio(buffer_size, ratio); let size = best_fit(buffer_size); let mut buffer = buf::SimpleBuffer::new(size); let re_size = size.to_image_size(typesetter); let img = { let img = img.resize_exact( re_size.width as u32, re_size.height as u32, imageops::FilterType::Triangle, ); watch.lap("resizing"); let size = (img.width() as usize, img.height() as usize).into(); let img = img.into_rgb8(); let raw = img.as_bytes(); img::ImgVec::from_bytes(&raw, size)? }; watch.lap("from_bytes"); let num_of_jobs = thread::available_parallelism()?; thread::scope(|s| { for workload in typesetter.workloads(buffer.as_mut(), img.as_ref(), num_of_jobs.get()) { s.spawn(move || workload.run()); } }); watch.lap("glyphing"); let string = buffer.render(); watch.lap("rendering"); print!("{}", string); watch.lap("printing"); Ok(()) } #[cfg(not(feature = "term"))] fn correction_ratio() -> io::Result { let size = termion::terminal_size()?; let size_px = termion::terminal_size_pixels()?; Ok((size.1 as f32 / size.0 as f32) * (size_px.0 as f32 / size_px.1 as f32)) } #[cfg(not(feature = "term"))] fn term_size() -> io::Result { let term_size = termion::terminal_size()?; Ok(mat::Size { width: term_size.0 as usize, height: term_size.1 as usize, }) } #[cfg(feature = "term")] fn correction_ratio() -> io::Result { lb::term::glyph_aspect_ratio() } #[cfg(feature = "term")] fn term_size() -> io::Result { lb::term::size() } fn best_fit(img_size: mat::Size) -> mat::Size { let term_size = term_size().unwrap_or(FALLBACK_SIZE); let ratio = { let rw = term_size.width as f32 / img_size.width as f32; let rh = term_size.height as f32 / img_size.height as f32; if rw < rh { rw } else { rh } }; mat::Size { width: (img_size.width as f32 * ratio) as usize, height: (img_size.height as f32 * ratio) as usize, } }