mirror of
https://github.com/mfontanini/presenterm.git
synced 2025-05-05 23:42:59 +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 graphics_mode = Self::select_graphics_mode(cli, &config);
|
||||||
let printer = Arc::new(ImagePrinter::new(graphics_mode.clone())?);
|
let printer = Arc::new(ImagePrinter::new(graphics_mode.clone())?);
|
||||||
let registry = ImageRegistry(printer.clone());
|
let registry = ImageRegistry::new(printer.clone());
|
||||||
let resources = Resources::new(
|
let resources = Resources::new(
|
||||||
resources_path.clone(),
|
resources_path.clone(),
|
||||||
themes_path.unwrap_or_else(|| resources_path.clone()),
|
themes_path.unwrap_or_else(|| resources_path.clone()),
|
||||||
|
@ -22,7 +22,7 @@ use crate::{
|
|||||||
resource::Resources,
|
resource::Resources,
|
||||||
terminal::image::{
|
terminal::image::{
|
||||||
Image,
|
Image,
|
||||||
printer::{ImageRegistry, RegisterImageError},
|
printer::{ImageRegistry, ImageSpec, RegisterImageError},
|
||||||
},
|
},
|
||||||
theme::{
|
theme::{
|
||||||
Alignment, AuthorPositioning, ElementType, PresentationTheme, ProcessingThemeError, ThemeOptions,
|
Alignment, AuthorPositioning, ElementType, PresentationTheme, ProcessingThemeError, ThemeOptions,
|
||||||
@ -246,7 +246,7 @@ impl<'a> PresentationBuilder<'a> {
|
|||||||
};
|
};
|
||||||
let mut image = DynamicImage::new_rgba8(1, 1);
|
let mut image = DynamicImage::new_rgba8(1, 1);
|
||||||
image.as_mut_rgba8().unwrap().get_pixel_mut(0, 0).0 = rgba;
|
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)
|
Ok(image)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,7 +382,7 @@ impl<'a> Presenter<'a> {
|
|||||||
&mut self.third_party,
|
&mut self.third_party,
|
||||||
self.code_executor.clone(),
|
self.code_executor.clone(),
|
||||||
&self.themes,
|
&self.themes,
|
||||||
ImageRegistry(self.image_printer.clone()),
|
ImageRegistry::new(self.image_printer.clone()),
|
||||||
self.options.bindings.clone(),
|
self.options.bindings.clone(),
|
||||||
self.options.builder_options.clone(),
|
self.options.builder_options.clone(),
|
||||||
)?
|
)?
|
||||||
@ -425,13 +425,14 @@ impl<'a> Presenter<'a> {
|
|||||||
let Some(config) = self.options.transition.clone() else {
|
let Some(config) = self.options.transition.clone() else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
let registry = self.resources.image_registry();
|
||||||
let options = drawer.render_engine_options();
|
let options = drawer.render_engine_options();
|
||||||
let presentation = self.state.presentation_mut();
|
let presentation = self.state.presentation_mut();
|
||||||
let dimensions = WindowSize::current(self.options.font_size_fallback)?;
|
let dimensions = WindowSize::current(self.options.font_size_fallback)?;
|
||||||
presentation.jump_previous();
|
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();
|
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;
|
let direction = TransitionDirection::Next;
|
||||||
self.animate_transition(drawer, left, right, direction, dimensions, config)
|
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 {
|
let Some(config) = self.options.transition.clone() else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
let registry = self.resources.image_registry();
|
||||||
let options = drawer.render_engine_options();
|
let options = drawer.render_engine_options();
|
||||||
let presentation = self.state.presentation_mut();
|
let presentation = self.state.presentation_mut();
|
||||||
let dimensions = WindowSize::current(self.options.font_size_fallback)?;
|
let dimensions = WindowSize::current(self.options.font_size_fallback)?;
|
||||||
presentation.jump_next();
|
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();
|
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;
|
let direction = TransitionDirection::Previous;
|
||||||
self.animate_transition(drawer, left, right, direction, dimensions, config)
|
self.animate_transition(drawer, left, right, direction, dimensions, config)
|
||||||
}
|
}
|
||||||
@ -526,8 +528,9 @@ impl<'a> Presenter<'a> {
|
|||||||
slide: &Slide,
|
slide: &Slide,
|
||||||
dimensions: WindowSize,
|
dimensions: WindowSize,
|
||||||
options: &RenderEngineOptions,
|
options: &RenderEngineOptions,
|
||||||
|
registry: ImageRegistry,
|
||||||
) -> Result<TerminalGrid, RenderError> {
|
) -> 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());
|
let engine = RenderEngine::new(&mut term, dimensions.clone(), options.clone());
|
||||||
engine.render(slide.iter_visible_operations())?;
|
engine.render(slide.iter_visible_operations())?;
|
||||||
Ok(term.into_contents())
|
Ok(term.into_contents())
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
terminal::image::{
|
terminal::image::{
|
||||||
Image,
|
Image,
|
||||||
printer::{ImageRegistry, RegisterImageError},
|
printer::{ImageRegistry, ImageSpec, RegisterImageError},
|
||||||
},
|
},
|
||||||
theme::{raw::PresentationTheme, registry::LoadThemeError},
|
theme::{raw::PresentationTheme, registry::LoadThemeError},
|
||||||
};
|
};
|
||||||
@ -24,8 +24,6 @@ const LOOP_INTERVAL: Duration = Duration::from_millis(250);
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct ResourcesInner {
|
struct ResourcesInner {
|
||||||
images: HashMap<PathBuf, Image>,
|
|
||||||
theme_images: HashMap<PathBuf, Image>,
|
|
||||||
themes: HashMap<PathBuf, PresentationTheme>,
|
themes: HashMap<PathBuf, PresentationTheme>,
|
||||||
external_snippets: HashMap<PathBuf, String>,
|
external_snippets: HashMap<PathBuf, String>,
|
||||||
base_path: PathBuf,
|
base_path: PathBuf,
|
||||||
@ -56,8 +54,6 @@ impl Resources {
|
|||||||
let inner = ResourcesInner {
|
let inner = ResourcesInner {
|
||||||
base_path: base_path.into(),
|
base_path: base_path.into(),
|
||||||
themes_path: themes_path.into(),
|
themes_path: themes_path.into(),
|
||||||
images: Default::default(),
|
|
||||||
theme_images: Default::default(),
|
|
||||||
themes: Default::default(),
|
themes: Default::default(),
|
||||||
external_snippets: Default::default(),
|
external_snippets: Default::default(),
|
||||||
image_registry,
|
image_registry,
|
||||||
@ -73,14 +69,9 @@ impl Resources {
|
|||||||
|
|
||||||
/// Get the image at the given path.
|
/// Get the image at the given path.
|
||||||
pub(crate) fn image<P: AsRef<Path>>(&self, path: P) -> Result<Image, RegisterImageError> {
|
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);
|
let path = inner.base_path.join(path);
|
||||||
if let Some(image) = inner.images.get(&path) {
|
let image = inner.image_registry.register(ImageSpec::Filesystem(path.clone()))?;
|
||||||
return Ok(image.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let image = inner.image_registry.register_resource(path.clone())?;
|
|
||||||
inner.images.insert(path, image.clone());
|
|
||||||
Ok(image)
|
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);
|
let path = inner.themes_path.join(path);
|
||||||
if let Some(image) = inner.theme_images.get(&path) {
|
let image = inner.image_registry.register(ImageSpec::Filesystem(path.clone()))?;
|
||||||
return Ok(image.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let image = inner.image_registry.register_resource(path.clone())?;
|
|
||||||
inner.theme_images.insert(path, image.clone());
|
|
||||||
Ok(image)
|
Ok(image)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,9 +130,13 @@ impl Resources {
|
|||||||
/// Clears all resources.
|
/// Clears all resources.
|
||||||
pub(crate) fn clear(&self) {
|
pub(crate) fn clear(&self) {
|
||||||
let mut inner = self.inner.borrow_mut();
|
let mut inner = self.inner.borrow_mut();
|
||||||
inner.images.clear();
|
inner.image_registry.clear();
|
||||||
inner.themes.clear();
|
inner.themes.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn image_registry(&self) -> ImageRegistry {
|
||||||
|
self.inner.borrow().image_registry.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Watches for file changes.
|
/// Watches for file changes.
|
||||||
|
@ -16,19 +16,18 @@ use crate::{
|
|||||||
use image::{DynamicImage, ImageError};
|
use image::{DynamicImage, ImageError};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
|
collections::HashMap,
|
||||||
fmt, io,
|
fmt, io,
|
||||||
path::{Path, PathBuf},
|
ops::Deref,
|
||||||
sync::Arc,
|
path::PathBuf,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) trait PrintImage {
|
pub(crate) trait PrintImage {
|
||||||
type Image: ImageProperties;
|
type Image: ImageProperties;
|
||||||
|
|
||||||
/// Register an image.
|
/// Register an image.
|
||||||
fn register(&self, image: DynamicImage) -> Result<Self::Image, RegisterImageError>;
|
fn register(&self, spec: ImageSpec) -> 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 print<T>(&self, image: &Self::Image, options: &PrintOptions, terminal: &mut T) -> Result<(), PrintImageError>
|
fn print<T>(&self, image: &Self::Image, options: &PrintOptions, terminal: &mut T) -> Result<(), PrintImageError>
|
||||||
where
|
where
|
||||||
@ -120,26 +119,14 @@ impl ImagePrinter {
|
|||||||
impl PrintImage for ImagePrinter {
|
impl PrintImage for ImagePrinter {
|
||||||
type Image = TerminalImage;
|
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 {
|
let image = match self {
|
||||||
Self::Kitty(printer) => TerminalImage::Kitty(printer.register(image)?),
|
Self::Kitty(printer) => TerminalImage::Kitty(printer.register(spec)?),
|
||||||
Self::Iterm(printer) => TerminalImage::Iterm(printer.register(image)?),
|
Self::Iterm(printer) => TerminalImage::Iterm(printer.register(spec)?),
|
||||||
Self::Ascii(printer) => TerminalImage::Ascii(printer.register(image)?),
|
Self::Ascii(printer) => TerminalImage::Ascii(printer.register(spec)?),
|
||||||
Self::Null => return Err(RegisterImageError::Unsupported),
|
Self::Null => return Err(RegisterImageError::Unsupported),
|
||||||
#[cfg(feature = "sixel")]
|
#[cfg(feature = "sixel")]
|
||||||
Self::Sixel(printer) => TerminalImage::Sixel(printer.register(image)?),
|
Self::Sixel(printer) => TerminalImage::Sixel(printer.register(spec)?),
|
||||||
};
|
|
||||||
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)?),
|
|
||||||
};
|
};
|
||||||
Ok(image)
|
Ok(image)
|
||||||
}
|
}
|
||||||
@ -161,11 +148,21 @@ impl PrintImage for ImagePrinter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[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 {
|
impl fmt::Debug for ImageRegistry {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
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::Kitty(_) => "Kitty",
|
||||||
ImagePrinter::Iterm(_) => "Iterm",
|
ImagePrinter::Iterm(_) => "Iterm",
|
||||||
ImagePrinter::Ascii(_) => "Ascii",
|
ImagePrinter::Ascii(_) => "Ascii",
|
||||||
@ -178,17 +175,61 @@ impl fmt::Debug for ImageRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ImageRegistry {
|
impl ImageRegistry {
|
||||||
pub(crate) fn register_image(&self, image: DynamicImage) -> Result<Image, RegisterImageError> {
|
pub(crate) fn register(&self, spec: ImageSpec) -> Result<Image, RegisterImageError> {
|
||||||
let resource = self.0.register(image)?;
|
let mut images = self.images.lock().unwrap();
|
||||||
let image = Image::new(resource, ImageSource::Generated);
|
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)
|
Ok(image)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn register_resource(&self, path: PathBuf) -> Result<Image, RegisterImageError> {
|
pub(crate) fn clear(&self) {
|
||||||
let resource = self.0.register_from_path(&path)?;
|
self.images.lock().unwrap().clear();
|
||||||
let image = Image::new(resource, ImageSource::Filesystem(path));
|
self.ascii_images.lock().unwrap().clear();
|
||||||
Ok(image)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
@ -1,29 +1,38 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
markdown::text_style::{Color, Colors, TextStyle},
|
markdown::text_style::{Color, Colors, TextStyle},
|
||||||
terminal::{
|
terminal::{
|
||||||
image::printer::{ImageProperties, PrintImage, PrintImageError, PrintOptions, RegisterImageError},
|
image::printer::{ImageProperties, ImageSpec, PrintImage, PrintImageError, PrintOptions, RegisterImageError},
|
||||||
printer::{TerminalCommand, TerminalIo},
|
printer::{TerminalCommand, TerminalIo},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use image::{DynamicImage, GenericImageView, Pixel, Rgba, imageops::FilterType};
|
use image::{DynamicImage, GenericImageView, Pixel, Rgba, RgbaImage, imageops::FilterType};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use std::{fs, ops::Deref};
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fs,
|
||||||
|
ops::Deref,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
const TOP_CHAR: &str = "▀";
|
const TOP_CHAR: &str = "▀";
|
||||||
const BOTTOM_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 {
|
impl ImageProperties for AsciiImage {
|
||||||
fn dimensions(&self) -> (u32, u32) {
|
fn dimensions(&self) -> (u32, u32) {
|
||||||
self.0.dimensions()
|
self.image.dimensions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<DynamicImage> for AsciiImage {
|
impl From<DynamicImage> for AsciiImage {
|
||||||
fn from(image: DynamicImage) -> Self {
|
fn from(image: DynamicImage) -> Self {
|
||||||
let image = image.into_rgba8();
|
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;
|
type Target = DynamicImage;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,25 +74,37 @@ impl AsciiPrinter {
|
|||||||
impl PrintImage for AsciiPrinter {
|
impl PrintImage for AsciiPrinter {
|
||||||
type Image = AsciiImage;
|
type Image = AsciiImage;
|
||||||
|
|
||||||
fn register(&self, image: image::DynamicImage) -> Result<Self::Image, RegisterImageError> {
|
fn register(&self, spec: ImageSpec) -> Result<Self::Image, RegisterImageError> {
|
||||||
Ok(AsciiImage(image))
|
let image = match spec {
|
||||||
}
|
ImageSpec::Generated(image) => image,
|
||||||
|
ImageSpec::Filesystem(path) => {
|
||||||
fn register_from_path<P: AsRef<std::path::Path>>(&self, path: P) -> Result<Self::Image, RegisterImageError> {
|
let contents = fs::read(path)?;
|
||||||
let contents = fs::read(path)?;
|
image::load_from_memory(&contents)?
|
||||||
let image = image::load_from_memory(&contents)?;
|
}
|
||||||
Ok(AsciiImage(image))
|
};
|
||||||
|
Ok(AsciiImage::from(image))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print<T>(&self, image: &Self::Image, options: &PrintOptions, terminal: &mut T) -> Result<(), PrintImageError>
|
fn print<T>(&self, image: &Self::Image, options: &PrintOptions, terminal: &mut T) -> Result<(), PrintImageError>
|
||||||
where
|
where
|
||||||
T: TerminalIo,
|
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
|
// 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)
|
// 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.
|
// 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);
|
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.
|
// Iterate pixel rows in pairs to be able to merge both pixels in a single iteration.
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use crate::terminal::{
|
use crate::terminal::{
|
||||||
image::printer::{ImageProperties, PrintImage, PrintImageError, PrintOptions, RegisterImageError},
|
image::printer::{ImageProperties, ImageSpec, PrintImage, PrintImageError, PrintOptions, RegisterImageError},
|
||||||
printer::{TerminalCommand, TerminalIo},
|
printer::{TerminalCommand, TerminalIo},
|
||||||
};
|
};
|
||||||
use base64::{Engine, engine::general_purpose::STANDARD};
|
use base64::{Engine, engine::general_purpose::STANDARD};
|
||||||
use image::{GenericImageView, ImageEncoder, RgbaImage, codecs::png::PngEncoder};
|
use image::{GenericImageView, ImageEncoder, RgbaImage, codecs::png::PngEncoder};
|
||||||
use std::{fs, path::Path};
|
use std::fs;
|
||||||
|
|
||||||
pub(crate) struct ItermImage {
|
pub(crate) struct ItermImage {
|
||||||
dimensions: (u32, u32),
|
dimensions: (u32, u32),
|
||||||
@ -38,18 +38,21 @@ pub struct ItermPrinter;
|
|||||||
impl PrintImage for ItermPrinter {
|
impl PrintImage for ItermPrinter {
|
||||||
type Image = ItermImage;
|
type Image = ItermImage;
|
||||||
|
|
||||||
fn register(&self, image: image::DynamicImage) -> Result<Self::Image, RegisterImageError> {
|
fn register(&self, spec: ImageSpec) -> Result<Self::Image, RegisterImageError> {
|
||||||
let dimensions = image.dimensions();
|
match spec {
|
||||||
let mut contents = Vec::new();
|
ImageSpec::Generated(image) => {
|
||||||
let encoder = PngEncoder::new(&mut contents);
|
let dimensions = image.dimensions();
|
||||||
encoder.write_image(image.as_bytes(), dimensions.0, dimensions.1, image.color().into())?;
|
let mut contents = Vec::new();
|
||||||
Ok(ItermImage::new(contents, dimensions))
|
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)?;
|
ImageSpec::Filesystem(path) => {
|
||||||
let image = image::load_from_memory(&contents)?;
|
let contents = fs::read(path)?;
|
||||||
Ok(ItermImage::new(contents, image.dimensions()))
|
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>
|
fn print<T>(&self, image: &Self::Image, options: &PrintOptions, terminal: &mut T) -> Result<(), PrintImageError>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
markdown::text_style::{Color, TextStyle},
|
markdown::text_style::{Color, TextStyle},
|
||||||
terminal::{
|
terminal::{
|
||||||
image::printer::{ImageProperties, PrintImage, PrintImageError, PrintOptions, RegisterImageError},
|
image::printer::{ImageProperties, ImageSpec, PrintImage, PrintImageError, PrintOptions, RegisterImageError},
|
||||||
printer::{TerminalCommand, TerminalIo},
|
printer::{TerminalCommand, TerminalIo},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use base64::{Engine, engine::general_purpose::STANDARD};
|
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::{
|
use std::{
|
||||||
fmt,
|
fmt,
|
||||||
fs::{self, File},
|
fs::{self, File},
|
||||||
@ -395,20 +395,14 @@ impl KittyPrinter {
|
|||||||
impl PrintImage for KittyPrinter {
|
impl PrintImage for KittyPrinter {
|
||||||
type Image = KittyImage;
|
type Image = KittyImage;
|
||||||
|
|
||||||
fn register(&self, image: DynamicImage) -> Result<Self::Image, RegisterImageError> {
|
fn register(&self, spec: ImageSpec) -> Result<Self::Image, RegisterImageError> {
|
||||||
let resource = RawResource::Image(image.into_rgba8());
|
let image = match spec {
|
||||||
let resource = match &self.mode {
|
ImageSpec::Generated(image) => RawResource::Image(image.into_rgba8()),
|
||||||
KittyMode::Local => self.persist_resource(resource)?,
|
ImageSpec::Filesystem(path) => Self::load_raw_resource(&path)?,
|
||||||
KittyMode::Remote => resource.into_memory_resource(),
|
|
||||||
};
|
};
|
||||||
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 {
|
let resource = match &self.mode {
|
||||||
KittyMode::Local => self.persist_resource(resource)?,
|
KittyMode::Local => self.persist_resource(image)?,
|
||||||
KittyMode::Remote => resource.into_memory_resource(),
|
KittyMode::Remote => image.into_memory_resource(),
|
||||||
};
|
};
|
||||||
Ok(resource)
|
Ok(resource)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::terminal::{
|
use crate::terminal::{
|
||||||
image::printer::{
|
image::printer::{
|
||||||
CreatePrinterError, ImageProperties, PrintImage, PrintImageError, PrintOptions, RegisterImageError,
|
CreatePrinterError, ImageProperties, ImageSpec, PrintImage, PrintImageError, PrintOptions, RegisterImageError,
|
||||||
},
|
},
|
||||||
printer::{TerminalCommand, TerminalIo},
|
printer::{TerminalCommand, TerminalIo},
|
||||||
};
|
};
|
||||||
@ -38,14 +38,15 @@ impl SixelPrinter {
|
|||||||
impl PrintImage for SixelPrinter {
|
impl PrintImage for SixelPrinter {
|
||||||
type Image = SixelImage;
|
type Image = SixelImage;
|
||||||
|
|
||||||
fn register(&self, image: image::DynamicImage) -> Result<Self::Image, RegisterImageError> {
|
fn register(&self, spec: ImageSpec) -> Result<Self::Image, RegisterImageError> {
|
||||||
Ok(SixelImage(image))
|
match spec {
|
||||||
}
|
ImageSpec::Generated(image) => Ok(SixelImage(image)),
|
||||||
|
ImageSpec::Filesystem(path) => {
|
||||||
fn register_from_path<P: AsRef<std::path::Path>>(&self, path: P) -> Result<Self::Image, RegisterImageError> {
|
let contents = fs::read(path)?;
|
||||||
let contents = fs::read(path)?;
|
let image = image::load_from_memory(&contents)?;
|
||||||
let image = image::load_from_memory(&contents)?;
|
Ok(SixelImage(image))
|
||||||
Ok(SixelImage(image))
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print<T>(&self, image: &Self::Image, options: &PrintOptions, terminal: &mut T) -> Result<(), PrintImageError>
|
fn print<T>(&self, image: &Self::Image, options: &PrintOptions, terminal: &mut T) -> Result<(), PrintImageError>
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
use super::{
|
use super::{
|
||||||
image::{
|
image::{
|
||||||
Image,
|
Image,
|
||||||
printer::{PrintImage, PrintImageError, PrintOptions, TerminalImage},
|
printer::{PrintImage, PrintImageError, PrintOptions},
|
||||||
protocols::ascii::AsciiPrinter,
|
protocols::ascii::AsciiPrinter,
|
||||||
},
|
},
|
||||||
printer::{TerminalError, TerminalIo},
|
printer::{TerminalError, TerminalIo},
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
WindowSize,
|
ImageRegistry, WindowSize,
|
||||||
markdown::{
|
markdown::{
|
||||||
elements::Text,
|
elements::Text,
|
||||||
text_style::{Color, Colors, TextStyle},
|
text_style::{Color, Colors, TextStyle},
|
||||||
},
|
},
|
||||||
terminal::printer::TerminalCommand,
|
terminal::printer::TerminalCommand,
|
||||||
};
|
};
|
||||||
use image::DynamicImage;
|
use std::{collections::HashMap, io};
|
||||||
use std::{collections::HashMap, io, ops::Deref};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub(crate) struct PrintedImage {
|
pub(crate) struct PrintedImage {
|
||||||
@ -188,24 +187,10 @@ impl VirtualTerminal {
|
|||||||
let image = PrintedImage { image: image.clone(), width_columns: options.columns };
|
let image = PrintedImage { image: image.clone(), width_columns: options.columns };
|
||||||
self.images.insert(key, image);
|
self.images.insert(key, image);
|
||||||
}
|
}
|
||||||
ImageBehavior::PrintAscii => {
|
ImageBehavior::PrintAscii(registry) => {
|
||||||
|
let image = registry.as_ascii(image);
|
||||||
let image_printer = AsciiPrinter;
|
let image_printer = AsciiPrinter;
|
||||||
match image.image.deref() {
|
image_printer.print(&image, options, self)?
|
||||||
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)
|
|
||||||
}
|
|
||||||
}?;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -243,7 +228,7 @@ impl TerminalIo for VirtualTerminal {
|
|||||||
pub(crate) enum ImageBehavior {
|
pub(crate) enum ImageBehavior {
|
||||||
#[default]
|
#[default]
|
||||||
Store,
|
Store,
|
||||||
PrintAscii,
|
PrintAscii(ImageRegistry),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
@ -12,7 +12,10 @@ use crate::{
|
|||||||
},
|
},
|
||||||
properties::WindowSize,
|
properties::WindowSize,
|
||||||
},
|
},
|
||||||
terminal::image::{Image, printer::RegisterImageError},
|
terminal::image::{
|
||||||
|
Image,
|
||||||
|
printer::{ImageSpec, RegisterImageError},
|
||||||
|
},
|
||||||
theme::{Alignment, MermaidStyle, PresentationTheme, TypstStyle, raw::RawColor},
|
theme::{Alignment, MermaidStyle, PresentationTheme, TypstStyle, raw::RawColor},
|
||||||
tools::{ExecutionError, ThirdPartyTools},
|
tools::{ExecutionError, ThirdPartyTools},
|
||||||
};
|
};
|
||||||
@ -269,7 +272,7 @@ impl Worker {
|
|||||||
fn load_image(&self, snippet: ImageSnippet, path: &Path) -> Result<Image, ThirdPartyRenderError> {
|
fn load_image(&self, snippet: ImageSnippet, path: &Path) -> Result<Image, ThirdPartyRenderError> {
|
||||||
let contents = fs::read(path)?;
|
let contents = fs::read(path)?;
|
||||||
let image = image::load_from_memory(&contents)?;
|
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());
|
self.state.lock().unwrap().cache.insert(snippet, image.clone());
|
||||||
Ok(image)
|
Ok(image)
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,10 @@ use crate::{
|
|||||||
},
|
},
|
||||||
terminal::{
|
terminal::{
|
||||||
ansi::AnsiSplitter,
|
ansi::AnsiSplitter,
|
||||||
image::{Image, printer::ImageRegistry},
|
image::{
|
||||||
|
Image,
|
||||||
|
printer::{ImageRegistry, ImageSpec},
|
||||||
|
},
|
||||||
should_hide_cursor,
|
should_hide_cursor,
|
||||||
},
|
},
|
||||||
theme::{Alignment, ExecutionOutputBlockStyle, ExecutionStatusBlockStyle, Margin},
|
theme::{Alignment, ExecutionOutputBlockStyle, ExecutionStatusBlockStyle, Margin},
|
||||||
@ -404,7 +407,7 @@ impl RunImageSnippet {
|
|||||||
return Err(e.to_string());
|
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