diff --git a/Cargo.toml b/Cargo.toml index d9f0ea9..1bfeb51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,8 +43,7 @@ vte = "0.15" rstest = { version = "0.25", default-features = false } [features] -# TODO -default = ["sixel"] +default = [] sixel = ["sixel-rs"] [profile.dev] diff --git a/src/terminal/image/protocols/ascii.rs b/src/terminal/image/protocols/ascii.rs index 0209340..355f22e 100644 --- a/src/terminal/image/protocols/ascii.rs +++ b/src/terminal/image/protocols/ascii.rs @@ -5,26 +5,34 @@ use crate::{ printer::{TerminalCommand, TerminalIo}, }, }; -use image::{DynamicImage, GenericImageView, Pixel, Rgba, imageops::FilterType}; +use image::{DynamicImage, GenericImageView, Pixel, Rgba, RgbaImage, imageops::FilterType}; use itertools::Itertools; -use std::{fs, ops::Deref, sync::Arc}; +use std::{ + collections::HashMap, + fs, + ops::Deref, + sync::{Arc, Mutex}, +}; const TOP_CHAR: &str = "▀"; const BOTTOM_CHAR: &str = "▄"; #[derive(Clone)] -pub(crate) struct AsciiImage(Arc); +pub(crate) struct AsciiImage { + image: Arc, + cached_sizes: Arc>>, +} impl ImageProperties for AsciiImage { fn dimensions(&self) -> (u32, u32) { - self.0.dimensions() + self.image.dimensions() } } impl From for AsciiImage { fn from(image: DynamicImage) -> Self { let image = image.into_rgba8(); - Self(Arc::new(image.into())) + Self { image: Arc::new(image.into()), cached_sizes: Default::default() } } } @@ -32,7 +40,7 @@ impl Deref for AsciiImage { type Target = DynamicImage; fn deref(&self) -> &Self::Target { - &self.0 + &self.image } } @@ -74,18 +82,29 @@ impl PrintImage for AsciiPrinter { image::load_from_memory(&contents)? } }; - Ok(AsciiImage(image.into())) + Ok(AsciiImage::from(image)) } fn print(&self, image: &Self::Image, options: &PrintOptions, terminal: &mut T) -> Result<(), PrintImageError> where T: TerminalIo, { + let columns = options.columns; + let rows = options.rows * 2; + let mut cached_sizes = image.cached_sizes.lock().unwrap(); + // lookup on cache/resize the image and store it in cache + let cache_key = (columns, rows); + let image = match cached_sizes.get(&cache_key) { + Some(image) => image, + None => { + let image = image.image.resize_exact(columns as u32, rows as u32, FilterType::Triangle); + cached_sizes.insert(cache_key, image.into_rgba8()); + cached_sizes.get(&cache_key).unwrap() + } + }; // The strategy here is taken from viuer: use half vertical ascii blocks in combination // with foreground/background colors to fit 2 vertical pixels per cell. That is, cell (x, y) // will contain the pixels at (x, y) and (x, y + 1) combined. - let image = image.0.resize_exact(options.columns as u32, 2 * options.rows as u32, FilterType::Triangle); - let image = image.into_rgba8(); let default_background = options.background_color.map(Color::from); // Iterate pixel rows in pairs to be able to merge both pixels in a single iteration.