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<dyn error::Error>> {
let args: Vec<String> = std::env::args().collect();
if args.len() < 3 {
eprintln!("error: expected at least 2 CLI arguments");
eprintln!("usage: cargo run --example image -- <typesetter> <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 -- <typesetter> <image>");
Ok(())
}
}
}
fn print<T>(typesetter: &T, path: &str, bench: bool) -> Result<(), Box<dyn error::Error>>
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<f32> {
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<mat::Size> {
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<f32> {
lb::term::glyph_aspect_ratio()
}
#[cfg(feature = "term")]
fn term_size() -> io::Result<mat::Size> {
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,
}
}