perf: cache ascii image resizes

This commit is contained in:
Matias Fontanini 2025-04-13 14:15:37 -07:00
parent a5e89eb5a3
commit 73211528a4
2 changed files with 29 additions and 11 deletions

View File

@ -43,8 +43,7 @@ vte = "0.15"
rstest = { version = "0.25", default-features = false }
[features]
# TODO
default = ["sixel"]
default = []
sixel = ["sixel-rs"]
[profile.dev]

View File

@ -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<DynamicImage>);
pub(crate) struct AsciiImage {
image: Arc<DynamicImage>,
cached_sizes: Arc<Mutex<HashMap<(u16, u16), RgbaImage>>>,
}
impl ImageProperties for AsciiImage {
fn dimensions(&self) -> (u32, u32) {
self.0.dimensions()
self.image.dimensions()
}
}
impl From<DynamicImage> 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<T>(&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.