mirror of
https://github.com/mfontanini/presenterm.git
synced 2025-05-05 15:32:58 +00:00
perf: cache resized ascii images (#547)
Some checks are pending
Deploy docs / build-and-deploy (push) Waiting to run
Merge checks / Checks (push) Waiting to run
Merge checks / Validate nix flake (push) Waiting to run
Merge checks / Validate bat assets (push) Waiting to run
Merge checks / Validate JSON schemas (push) Waiting to run
Some checks are pending
Deploy docs / build-and-deploy (push) Waiting to run
Merge checks / Checks (push) Waiting to run
Merge checks / Validate nix flake (push) Waiting to run
Merge checks / Validate bat assets (push) Waiting to run
Merge checks / Validate JSON schemas (push) Waiting to run
This caches:
* Ascii images so we don't reload them every time we're doing slide
transitions.
* Ascii image resizes, so if we go to a slide that contains an image and
we're using slide transitions, the next transition that includes that
image won't require resizing it to that same sizes.
The one performance "issue" still present when using slide transitions
is that we still need to pay for that first image -> ascii image
conversion and that first ascii image resize the first time we
transition to a slide that contains an image. This is currently
noticeable in debug mode and not in release mode (at least in my machine
™️), but it would be sweet if it wasn't there ever. For this we need to
run through all slides, find all images, ascii convert them and resize
them to the current terminal size. The same should be done when the
terminal is resized.
This commit is contained in:
commit
9e1f2beca2
@ -230,7 +230,7 @@ impl CoreComponents {
|
||||
}
|
||||
let graphics_mode = Self::select_graphics_mode(cli, &config);
|
||||
let printer = Arc::new(ImagePrinter::new(graphics_mode.clone())?);
|
||||
let registry = ImageRegistry(printer.clone());
|
||||
let registry = ImageRegistry::new(printer.clone());
|
||||
let resources = Resources::new(
|
||||
resources_path.clone(),
|
||||
themes_path.unwrap_or_else(|| resources_path.clone()),
|
||||
|
@ -22,7 +22,7 @@ use crate::{
|
||||
resource::Resources,
|
||||
terminal::image::{
|
||||
Image,
|
||||
printer::{ImageRegistry, RegisterImageError},
|
||||
printer::{ImageRegistry, ImageSpec, RegisterImageError},
|
||||
},
|
||||
theme::{
|
||||
Alignment, AuthorPositioning, ElementType, PresentationTheme, ProcessingThemeError, ThemeOptions,
|
||||
@ -246,7 +246,7 @@ impl<'a> PresentationBuilder<'a> {
|
||||
};
|
||||
let mut image = DynamicImage::new_rgba8(1, 1);
|
||||
image.as_mut_rgba8().unwrap().get_pixel_mut(0, 0).0 = rgba;
|
||||
let image = self.image_registry.register_image(image)?;
|
||||
let image = self.image_registry.register(ImageSpec::Generated(image))?;
|
||||
Ok(image)
|
||||
}
|
||||
|
||||
|
@ -382,7 +382,7 @@ impl<'a> Presenter<'a> {
|
||||
&mut self.third_party,
|
||||
self.code_executor.clone(),
|
||||
&self.themes,
|
||||
ImageRegistry(self.image_printer.clone()),
|
||||
ImageRegistry::new(self.image_printer.clone()),
|
||||
self.options.bindings.clone(),
|
||||
self.options.builder_options.clone(),
|
||||
)?
|
||||
@ -425,13 +425,14 @@ impl<'a> Presenter<'a> {
|
||||
let Some(config) = self.options.transition.clone() else {
|
||||
return Ok(());
|
||||
};
|
||||
let registry = self.resources.image_registry();
|
||||
let options = drawer.render_engine_options();
|
||||
let presentation = self.state.presentation_mut();
|
||||
let dimensions = WindowSize::current(self.options.font_size_fallback)?;
|
||||
presentation.jump_previous();
|
||||
let left = Self::virtual_render(presentation.current_slide(), dimensions.clone(), &options)?;
|
||||
let left = Self::virtual_render(presentation.current_slide(), dimensions.clone(), &options, registry.clone())?;
|
||||
presentation.jump_next();
|
||||
let right = Self::virtual_render(presentation.current_slide(), dimensions.clone(), &options)?;
|
||||
let right = Self::virtual_render(presentation.current_slide(), dimensions.clone(), &options, registry)?;
|
||||
let direction = TransitionDirection::Next;
|
||||
self.animate_transition(drawer, left, right, direction, dimensions, config)
|
||||
}
|
||||
@ -440,13 +441,14 @@ impl<'a> Presenter<'a> {
|
||||
let Some(config) = self.options.transition.clone() else {
|
||||
return Ok(());
|
||||
};
|
||||
let registry = self.resources.image_registry();
|
||||
let options = drawer.render_engine_options();
|
||||
let presentation = self.state.presentation_mut();
|
||||
let dimensions = WindowSize::current(self.options.font_size_fallback)?;
|
||||
presentation.jump_next();
|
||||
let right = Self::virtual_render(presentation.current_slide(), dimensions.clone(), &options)?;
|
||||
let right = Self::virtual_render(presentation.current_slide(), dimensions.clone(), &options, registry.clone())?;
|
||||
presentation.jump_previous();
|
||||
let left = Self::virtual_render(presentation.current_slide(), dimensions.clone(), &options)?;
|
||||
let left = Self::virtual_render(presentation.current_slide(), dimensions.clone(), &options, registry)?;
|
||||
let direction = TransitionDirection::Previous;
|
||||
self.animate_transition(drawer, left, right, direction, dimensions, config)
|
||||
}
|
||||
@ -526,8 +528,9 @@ impl<'a> Presenter<'a> {
|
||||
slide: &Slide,
|
||||
dimensions: WindowSize,
|
||||
options: &RenderEngineOptions,
|
||||
registry: ImageRegistry,
|
||||
) -> Result<TerminalGrid, RenderError> {
|
||||
let mut term = VirtualTerminal::new(dimensions.clone(), ImageBehavior::PrintAscii);
|
||||
let mut term = VirtualTerminal::new(dimensions.clone(), ImageBehavior::PrintAscii(registry));
|
||||
let engine = RenderEngine::new(&mut term, dimensions.clone(), options.clone());
|
||||
engine.render(slide.iter_visible_operations())?;
|
||||
Ok(term.into_contents())
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
terminal::image::{
|
||||
Image,
|
||||
printer::{ImageRegistry, RegisterImageError},
|
||||
printer::{ImageRegistry, ImageSpec, RegisterImageError},
|
||||
},
|
||||
theme::{raw::PresentationTheme, registry::LoadThemeError},
|
||||
};
|
||||
@ -24,8 +24,6 @@ const LOOP_INTERVAL: Duration = Duration::from_millis(250);
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ResourcesInner {
|
||||
images: HashMap<PathBuf, Image>,
|
||||
theme_images: HashMap<PathBuf, Image>,
|
||||
themes: HashMap<PathBuf, PresentationTheme>,
|
||||
external_snippets: HashMap<PathBuf, String>,
|
||||
base_path: PathBuf,
|
||||
@ -56,8 +54,6 @@ impl Resources {
|
||||
let inner = ResourcesInner {
|
||||
base_path: base_path.into(),
|
||||
themes_path: themes_path.into(),
|
||||
images: Default::default(),
|
||||
theme_images: Default::default(),
|
||||
themes: Default::default(),
|
||||
external_snippets: Default::default(),
|
||||
image_registry,
|
||||
@ -73,14 +69,9 @@ impl Resources {
|
||||
|
||||
/// Get the image at the given path.
|
||||
pub(crate) fn image<P: AsRef<Path>>(&self, path: P) -> Result<Image, RegisterImageError> {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
let inner = self.inner.borrow();
|
||||
let path = inner.base_path.join(path);
|
||||
if let Some(image) = inner.images.get(&path) {
|
||||
return Ok(image.clone());
|
||||
}
|
||||
|
||||
let image = inner.image_registry.register_resource(path.clone())?;
|
||||
inner.images.insert(path, image.clone());
|
||||
let image = inner.image_registry.register(ImageSpec::Filesystem(path.clone()))?;
|
||||
Ok(image)
|
||||
}
|
||||
|
||||
@ -91,14 +82,9 @@ impl Resources {
|
||||
_ => (),
|
||||
};
|
||||
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
let inner = self.inner.borrow();
|
||||
let path = inner.themes_path.join(path);
|
||||
if let Some(image) = inner.theme_images.get(&path) {
|
||||
return Ok(image.clone());
|
||||
}
|
||||
|
||||
let image = inner.image_registry.register_resource(path.clone())?;
|
||||
inner.theme_images.insert(path, image.clone());
|
||||
let image = inner.image_registry.register(ImageSpec::Filesystem(path.clone()))?;
|
||||
Ok(image)
|
||||
}
|
||||
|
||||
@ -144,9 +130,13 @@ impl Resources {
|
||||
/// Clears all resources.
|
||||
pub(crate) fn clear(&self) {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
inner.images.clear();
|
||||
inner.image_registry.clear();
|
||||
inner.themes.clear();
|
||||
}
|
||||
|
||||
pub(crate) fn image_registry(&self) -> ImageRegistry {
|
||||
self.inner.borrow().image_registry.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Watches for file changes.
|
||||
|
@ -16,19 +16,18 @@ use crate::{
|
||||
use image::{DynamicImage, ImageError};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::HashMap,
|
||||
fmt, io,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
ops::Deref,
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
pub(crate) trait PrintImage {
|
||||
type Image: ImageProperties;
|
||||
|
||||
/// Register an image.
|
||||
fn register(&self, image: DynamicImage) -> Result<Self::Image, RegisterImageError>;
|
||||
|
||||
/// Load and register an image from the given path.
|
||||
fn register_from_path<P: AsRef<Path>>(&self, path: P) -> Result<Self::Image, RegisterImageError>;
|
||||
fn register(&self, spec: ImageSpec) -> Result<Self::Image, RegisterImageError>;
|
||||
|
||||
fn print<T>(&self, image: &Self::Image, options: &PrintOptions, terminal: &mut T) -> Result<(), PrintImageError>
|
||||
where
|
||||
@ -120,26 +119,14 @@ impl ImagePrinter {
|
||||
impl PrintImage for ImagePrinter {
|
||||
type Image = TerminalImage;
|
||||
|
||||
fn register(&self, image: DynamicImage) -> Result<Self::Image, RegisterImageError> {
|
||||
fn register(&self, spec: ImageSpec) -> Result<Self::Image, RegisterImageError> {
|
||||
let image = match self {
|
||||
Self::Kitty(printer) => TerminalImage::Kitty(printer.register(image)?),
|
||||
Self::Iterm(printer) => TerminalImage::Iterm(printer.register(image)?),
|
||||
Self::Ascii(printer) => TerminalImage::Ascii(printer.register(image)?),
|
||||
Self::Kitty(printer) => TerminalImage::Kitty(printer.register(spec)?),
|
||||
Self::Iterm(printer) => TerminalImage::Iterm(printer.register(spec)?),
|
||||
Self::Ascii(printer) => TerminalImage::Ascii(printer.register(spec)?),
|
||||
Self::Null => return Err(RegisterImageError::Unsupported),
|
||||
#[cfg(feature = "sixel")]
|
||||
Self::Sixel(printer) => TerminalImage::Sixel(printer.register(image)?),
|
||||
};
|
||||
Ok(image)
|
||||
}
|
||||
|
||||
fn register_from_path<P: AsRef<Path>>(&self, path: P) -> Result<Self::Image, RegisterImageError> {
|
||||
let image = match self {
|
||||
Self::Kitty(printer) => TerminalImage::Kitty(printer.register_from_path(path)?),
|
||||
Self::Iterm(printer) => TerminalImage::Iterm(printer.register_from_path(path)?),
|
||||
Self::Ascii(printer) => TerminalImage::Ascii(printer.register_from_path(path)?),
|
||||
Self::Null => return Err(RegisterImageError::Unsupported),
|
||||
#[cfg(feature = "sixel")]
|
||||
Self::Sixel(printer) => TerminalImage::Sixel(printer.register_from_path(path)?),
|
||||
Self::Sixel(printer) => TerminalImage::Sixel(printer.register(spec)?),
|
||||
};
|
||||
Ok(image)
|
||||
}
|
||||
@ -161,11 +148,21 @@ impl PrintImage for ImagePrinter {
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct ImageRegistry(pub Arc<ImagePrinter>);
|
||||
pub(crate) struct ImageRegistry {
|
||||
printer: Arc<ImagePrinter>,
|
||||
images: Arc<Mutex<HashMap<PathBuf, Image>>>,
|
||||
ascii_images: Arc<Mutex<HashMap<PathBuf, AsciiImage>>>,
|
||||
}
|
||||
|
||||
impl ImageRegistry {
|
||||
pub fn new(printer: Arc<ImagePrinter>) -> Self {
|
||||
Self { printer, images: Default::default(), ascii_images: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ImageRegistry {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let inner = match self.0.as_ref() {
|
||||
let inner = match self.printer.as_ref() {
|
||||
ImagePrinter::Kitty(_) => "Kitty",
|
||||
ImagePrinter::Iterm(_) => "Iterm",
|
||||
ImagePrinter::Ascii(_) => "Ascii",
|
||||
@ -178,17 +175,61 @@ impl fmt::Debug for ImageRegistry {
|
||||
}
|
||||
|
||||
impl ImageRegistry {
|
||||
pub(crate) fn register_image(&self, image: DynamicImage) -> Result<Image, RegisterImageError> {
|
||||
let resource = self.0.register(image)?;
|
||||
let image = Image::new(resource, ImageSource::Generated);
|
||||
pub(crate) fn register(&self, spec: ImageSpec) -> Result<Image, RegisterImageError> {
|
||||
let mut images = self.images.lock().unwrap();
|
||||
let (source, cache_key) = match &spec {
|
||||
ImageSpec::Generated(_) => (ImageSource::Generated, None),
|
||||
ImageSpec::Filesystem(path) => {
|
||||
// Return if already cached
|
||||
if let Some(image) = images.get(path) {
|
||||
return Ok(image.clone());
|
||||
}
|
||||
(ImageSource::Filesystem(path.clone()), Some(path.clone()))
|
||||
}
|
||||
};
|
||||
let resource = self.printer.register(spec)?;
|
||||
let image = Image::new(resource, source);
|
||||
if let Some(key) = cache_key {
|
||||
images.insert(key.clone(), image.clone());
|
||||
drop(images);
|
||||
if let TerminalImage::Ascii(image) = image.image.as_ref() {
|
||||
self.ascii_images.lock().unwrap().insert(key, image.clone());
|
||||
}
|
||||
}
|
||||
Ok(image)
|
||||
}
|
||||
|
||||
pub(crate) fn register_resource(&self, path: PathBuf) -> Result<Image, RegisterImageError> {
|
||||
let resource = self.0.register_from_path(&path)?;
|
||||
let image = Image::new(resource, ImageSource::Filesystem(path));
|
||||
Ok(image)
|
||||
pub(crate) fn clear(&self) {
|
||||
self.images.lock().unwrap().clear();
|
||||
self.ascii_images.lock().unwrap().clear();
|
||||
}
|
||||
|
||||
pub(crate) fn as_ascii(&self, image: &Image) -> AsciiImage {
|
||||
if let ImageSource::Filesystem(path) = &image.source {
|
||||
if let Some(image) = self.ascii_images.lock().unwrap().get(path) {
|
||||
return image.clone();
|
||||
}
|
||||
if let Some(TerminalImage::Ascii(image)) = self.images.lock().unwrap().get(path).map(|i| i.image.as_ref()) {
|
||||
return image.clone();
|
||||
}
|
||||
}
|
||||
let ascii_image = match image.image.deref() {
|
||||
TerminalImage::Ascii(image) => image.clone(),
|
||||
TerminalImage::Kitty(image) => DynamicImage::from(image.as_rgba8()).into(),
|
||||
TerminalImage::Iterm(image) => DynamicImage::from(image.as_rgba8()).into(),
|
||||
#[cfg(feature = "sixel")]
|
||||
TerminalImage::Sixel(image) => DynamicImage::from(image.as_rgba8()).into(),
|
||||
};
|
||||
if let ImageSource::Filesystem(path) = &image.source {
|
||||
self.ascii_images.lock().unwrap().insert(path.clone(), ascii_image.clone());
|
||||
}
|
||||
ascii_image
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum ImageSpec {
|
||||
Generated(DynamicImage),
|
||||
Filesystem(PathBuf),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
@ -1,29 +1,38 @@
|
||||
use crate::{
|
||||
markdown::text_style::{Color, Colors, TextStyle},
|
||||
terminal::{
|
||||
image::printer::{ImageProperties, PrintImage, PrintImageError, PrintOptions, RegisterImageError},
|
||||
image::printer::{ImageProperties, ImageSpec, PrintImage, PrintImageError, PrintOptions, RegisterImageError},
|
||||
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};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
ops::Deref,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
const TOP_CHAR: &str = "▀";
|
||||
const BOTTOM_CHAR: &str = "▄";
|
||||
|
||||
pub(crate) struct AsciiImage(DynamicImage);
|
||||
#[derive(Clone)]
|
||||
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(image.into())
|
||||
Self { image: Arc::new(image.into()), cached_sizes: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,7 +40,7 @@ impl Deref for AsciiImage {
|
||||
type Target = DynamicImage;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
&self.image
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,25 +74,37 @@ impl AsciiPrinter {
|
||||
impl PrintImage for AsciiPrinter {
|
||||
type Image = AsciiImage;
|
||||
|
||||
fn register(&self, image: image::DynamicImage) -> Result<Self::Image, RegisterImageError> {
|
||||
Ok(AsciiImage(image))
|
||||
}
|
||||
|
||||
fn register_from_path<P: AsRef<std::path::Path>>(&self, path: P) -> Result<Self::Image, RegisterImageError> {
|
||||
let contents = fs::read(path)?;
|
||||
let image = image::load_from_memory(&contents)?;
|
||||
Ok(AsciiImage(image))
|
||||
fn register(&self, spec: ImageSpec) -> Result<Self::Image, RegisterImageError> {
|
||||
let image = match spec {
|
||||
ImageSpec::Generated(image) => image,
|
||||
ImageSpec::Filesystem(path) => {
|
||||
let contents = fs::read(path)?;
|
||||
image::load_from_memory(&contents)?
|
||||
}
|
||||
};
|
||||
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.
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::terminal::{
|
||||
image::printer::{ImageProperties, PrintImage, PrintImageError, PrintOptions, RegisterImageError},
|
||||
image::printer::{ImageProperties, ImageSpec, PrintImage, PrintImageError, PrintOptions, RegisterImageError},
|
||||
printer::{TerminalCommand, TerminalIo},
|
||||
};
|
||||
use base64::{Engine, engine::general_purpose::STANDARD};
|
||||
use image::{GenericImageView, ImageEncoder, RgbaImage, codecs::png::PngEncoder};
|
||||
use std::{fs, path::Path};
|
||||
use std::fs;
|
||||
|
||||
pub(crate) struct ItermImage {
|
||||
dimensions: (u32, u32),
|
||||
@ -38,18 +38,21 @@ pub struct ItermPrinter;
|
||||
impl PrintImage for ItermPrinter {
|
||||
type Image = ItermImage;
|
||||
|
||||
fn register(&self, image: image::DynamicImage) -> Result<Self::Image, RegisterImageError> {
|
||||
let dimensions = image.dimensions();
|
||||
let mut contents = Vec::new();
|
||||
let encoder = PngEncoder::new(&mut contents);
|
||||
encoder.write_image(image.as_bytes(), dimensions.0, dimensions.1, image.color().into())?;
|
||||
Ok(ItermImage::new(contents, dimensions))
|
||||
}
|
||||
|
||||
fn register_from_path<P: AsRef<Path>>(&self, path: P) -> Result<Self::Image, RegisterImageError> {
|
||||
let contents = fs::read(path)?;
|
||||
let image = image::load_from_memory(&contents)?;
|
||||
Ok(ItermImage::new(contents, image.dimensions()))
|
||||
fn register(&self, spec: ImageSpec) -> Result<Self::Image, RegisterImageError> {
|
||||
match spec {
|
||||
ImageSpec::Generated(image) => {
|
||||
let dimensions = image.dimensions();
|
||||
let mut contents = Vec::new();
|
||||
let encoder = PngEncoder::new(&mut contents);
|
||||
encoder.write_image(image.as_bytes(), dimensions.0, dimensions.1, image.color().into())?;
|
||||
Ok(ItermImage::new(contents, dimensions))
|
||||
}
|
||||
ImageSpec::Filesystem(path) => {
|
||||
let contents = fs::read(path)?;
|
||||
let image = image::load_from_memory(&contents)?;
|
||||
Ok(ItermImage::new(contents, image.dimensions()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print<T>(&self, image: &Self::Image, options: &PrintOptions, terminal: &mut T) -> Result<(), PrintImageError>
|
||||
|
@ -1,12 +1,12 @@
|
||||
use crate::{
|
||||
markdown::text_style::{Color, TextStyle},
|
||||
terminal::{
|
||||
image::printer::{ImageProperties, PrintImage, PrintImageError, PrintOptions, RegisterImageError},
|
||||
image::printer::{ImageProperties, ImageSpec, PrintImage, PrintImageError, PrintOptions, RegisterImageError},
|
||||
printer::{TerminalCommand, TerminalIo},
|
||||
},
|
||||
};
|
||||
use base64::{Engine, engine::general_purpose::STANDARD};
|
||||
use image::{AnimationDecoder, Delay, DynamicImage, EncodableLayout, ImageReader, RgbaImage, codecs::gif::GifDecoder};
|
||||
use image::{AnimationDecoder, Delay, EncodableLayout, ImageReader, RgbaImage, codecs::gif::GifDecoder};
|
||||
use std::{
|
||||
fmt,
|
||||
fs::{self, File},
|
||||
@ -395,20 +395,14 @@ impl KittyPrinter {
|
||||
impl PrintImage for KittyPrinter {
|
||||
type Image = KittyImage;
|
||||
|
||||
fn register(&self, image: DynamicImage) -> Result<Self::Image, RegisterImageError> {
|
||||
let resource = RawResource::Image(image.into_rgba8());
|
||||
let resource = match &self.mode {
|
||||
KittyMode::Local => self.persist_resource(resource)?,
|
||||
KittyMode::Remote => resource.into_memory_resource(),
|
||||
fn register(&self, spec: ImageSpec) -> Result<Self::Image, RegisterImageError> {
|
||||
let image = match spec {
|
||||
ImageSpec::Generated(image) => RawResource::Image(image.into_rgba8()),
|
||||
ImageSpec::Filesystem(path) => Self::load_raw_resource(&path)?,
|
||||
};
|
||||
Ok(resource)
|
||||
}
|
||||
|
||||
fn register_from_path<P: AsRef<Path>>(&self, path: P) -> Result<Self::Image, RegisterImageError> {
|
||||
let resource = Self::load_raw_resource(path.as_ref())?;
|
||||
let resource = match &self.mode {
|
||||
KittyMode::Local => self.persist_resource(resource)?,
|
||||
KittyMode::Remote => resource.into_memory_resource(),
|
||||
KittyMode::Local => self.persist_resource(image)?,
|
||||
KittyMode::Remote => image.into_memory_resource(),
|
||||
};
|
||||
Ok(resource)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::terminal::{
|
||||
image::printer::{
|
||||
CreatePrinterError, ImageProperties, PrintImage, PrintImageError, PrintOptions, RegisterImageError,
|
||||
CreatePrinterError, ImageProperties, ImageSpec, PrintImage, PrintImageError, PrintOptions, RegisterImageError,
|
||||
},
|
||||
printer::{TerminalCommand, TerminalIo},
|
||||
};
|
||||
@ -38,14 +38,15 @@ impl SixelPrinter {
|
||||
impl PrintImage for SixelPrinter {
|
||||
type Image = SixelImage;
|
||||
|
||||
fn register(&self, image: image::DynamicImage) -> Result<Self::Image, RegisterImageError> {
|
||||
Ok(SixelImage(image))
|
||||
}
|
||||
|
||||
fn register_from_path<P: AsRef<std::path::Path>>(&self, path: P) -> Result<Self::Image, RegisterImageError> {
|
||||
let contents = fs::read(path)?;
|
||||
let image = image::load_from_memory(&contents)?;
|
||||
Ok(SixelImage(image))
|
||||
fn register(&self, spec: ImageSpec) -> Result<Self::Image, RegisterImageError> {
|
||||
match spec {
|
||||
ImageSpec::Generated(image) => Ok(SixelImage(image)),
|
||||
ImageSpec::Filesystem(path) => {
|
||||
let contents = fs::read(path)?;
|
||||
let image = image::load_from_memory(&contents)?;
|
||||
Ok(SixelImage(image))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print<T>(&self, image: &Self::Image, options: &PrintOptions, terminal: &mut T) -> Result<(), PrintImageError>
|
||||
|
@ -1,21 +1,20 @@
|
||||
use super::{
|
||||
image::{
|
||||
Image,
|
||||
printer::{PrintImage, PrintImageError, PrintOptions, TerminalImage},
|
||||
printer::{PrintImage, PrintImageError, PrintOptions},
|
||||
protocols::ascii::AsciiPrinter,
|
||||
},
|
||||
printer::{TerminalError, TerminalIo},
|
||||
};
|
||||
use crate::{
|
||||
WindowSize,
|
||||
ImageRegistry, WindowSize,
|
||||
markdown::{
|
||||
elements::Text,
|
||||
text_style::{Color, Colors, TextStyle},
|
||||
},
|
||||
terminal::printer::TerminalCommand,
|
||||
};
|
||||
use image::DynamicImage;
|
||||
use std::{collections::HashMap, io, ops::Deref};
|
||||
use std::{collections::HashMap, io};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub(crate) struct PrintedImage {
|
||||
@ -188,24 +187,10 @@ impl VirtualTerminal {
|
||||
let image = PrintedImage { image: image.clone(), width_columns: options.columns };
|
||||
self.images.insert(key, image);
|
||||
}
|
||||
ImageBehavior::PrintAscii => {
|
||||
ImageBehavior::PrintAscii(registry) => {
|
||||
let image = registry.as_ascii(image);
|
||||
let image_printer = AsciiPrinter;
|
||||
match image.image.deref() {
|
||||
TerminalImage::Kitty(image) => {
|
||||
let image = DynamicImage::from(image.as_rgba8());
|
||||
image_printer.print(&image.into(), options, self)
|
||||
}
|
||||
TerminalImage::Iterm(image) => {
|
||||
let image = DynamicImage::from(image.as_rgba8());
|
||||
image_printer.print(&image.into(), options, self)
|
||||
}
|
||||
TerminalImage::Ascii(image) => image_printer.print(image, options, self),
|
||||
#[cfg(feature = "sixel")]
|
||||
TerminalImage::Sixel(image) => {
|
||||
let image = DynamicImage::from(image.as_rgba8());
|
||||
image_printer.print(&image.into(), options, self)
|
||||
}
|
||||
}?;
|
||||
image_printer.print(&image, options, self)?
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
@ -243,7 +228,7 @@ impl TerminalIo for VirtualTerminal {
|
||||
pub(crate) enum ImageBehavior {
|
||||
#[default]
|
||||
Store,
|
||||
PrintAscii,
|
||||
PrintAscii(ImageRegistry),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
|
@ -12,7 +12,10 @@ use crate::{
|
||||
},
|
||||
properties::WindowSize,
|
||||
},
|
||||
terminal::image::{Image, printer::RegisterImageError},
|
||||
terminal::image::{
|
||||
Image,
|
||||
printer::{ImageSpec, RegisterImageError},
|
||||
},
|
||||
theme::{Alignment, MermaidStyle, PresentationTheme, TypstStyle, raw::RawColor},
|
||||
tools::{ExecutionError, ThirdPartyTools},
|
||||
};
|
||||
@ -269,7 +272,7 @@ impl Worker {
|
||||
fn load_image(&self, snippet: ImageSnippet, path: &Path) -> Result<Image, ThirdPartyRenderError> {
|
||||
let contents = fs::read(path)?;
|
||||
let image = image::load_from_memory(&contents)?;
|
||||
let image = self.state.lock().unwrap().image_registry.register_image(image)?;
|
||||
let image = self.state.lock().unwrap().image_registry.register(ImageSpec::Generated(image))?;
|
||||
self.state.lock().unwrap().cache.insert(snippet, image.clone());
|
||||
Ok(image)
|
||||
}
|
||||
|
@ -17,7 +17,10 @@ use crate::{
|
||||
},
|
||||
terminal::{
|
||||
ansi::AnsiSplitter,
|
||||
image::{Image, printer::ImageRegistry},
|
||||
image::{
|
||||
Image,
|
||||
printer::{ImageRegistry, ImageSpec},
|
||||
},
|
||||
should_hide_cursor,
|
||||
},
|
||||
theme::{Alignment, ExecutionOutputBlockStyle, ExecutionStatusBlockStyle, Margin},
|
||||
@ -404,7 +407,7 @@ impl RunImageSnippet {
|
||||
return Err(e.to_string());
|
||||
}
|
||||
};
|
||||
self.image_registry.register_image(image).map_err(|e| e.to_string())
|
||||
self.image_registry.register(ImageSpec::Generated(image)).map_err(|e| e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user