use super::{
Image, ImageSource,
protocols::{
ascii::{AsciiImage, AsciiPrinter},
iterm::{ItermImage, ItermPrinter},
kitty::{KittyImage, KittyPrinter},
raw::{RawImage, RawPrinter},
},
};
use crate::{
markdown::text_style::{Color, PaletteColorError},
terminal::{
GraphicsMode,
emulator::TerminalEmulator,
image::protocols::{
iterm::ItermMode,
sixel::{SixelImage, SixelPrinter},
},
printer::{TerminalError, TerminalIo},
},
};
use image::{DynamicImage, ImageError};
use std::{
borrow::Cow,
collections::HashMap,
fmt, io,
path::PathBuf,
sync::{Arc, Mutex},
};
pub(crate) trait PrintImage {
type Image: ImageProperties;
fn register(&self, spec: ImageSpec) -> Result<Self::Image, RegisterImageError>;
fn print<T>(&self, image: &Self::Image, options: &PrintOptions, terminal: &mut T) -> Result<(), PrintImageError>
where
T: TerminalIo;
}
pub(crate) trait ImageProperties {
fn dimensions(&self) -> (u32, u32);
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct PrintOptions {
pub(crate) columns: u16,
pub(crate) rows: u16,
pub(crate) z_index: i32,
pub(crate) background_color: Option<Color>,
#[allow(dead_code)]
pub(crate) column_width: u16,
#[allow(dead_code)]
pub(crate) row_height: u16,
}
pub(crate) enum TerminalImage {
Kitty(KittyImage),
Iterm(ItermImage),
Ascii(AsciiImage),
Raw(RawImage),
Sixel(SixelImage),
}
impl fmt::Debug for TerminalImage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Kitty(_) => f.debug_tuple("Kitty").finish(),
Self::Iterm(_) => f.debug_tuple("Iterm").finish(),
Self::Ascii(_) => f.debug_tuple("Ascii").finish(),
Self::Raw(_) => f.debug_tuple("Raw").finish(),
Self::Sixel(_) => f.debug_tuple("Sixel").finish(),
}
}
}
impl ImageProperties for TerminalImage {
fn dimensions(&self) -> (u32, u32) {
match self {
Self::Kitty(image) => image.dimensions(),
Self::Iterm(image) => image.dimensions(),
Self::Ascii(image) => image.dimensions(),
Self::Raw(image) => image.dimensions(),
Self::Sixel(image) => image.dimensions(),
}
}
}
pub enum ImagePrinter {
Kitty(KittyPrinter),
Iterm(ItermPrinter),
Ascii(AsciiPrinter),
Raw(RawPrinter),
Null,
Sixel(SixelPrinter),
}
impl Default for ImagePrinter {
fn default() -> Self {
Self::Ascii(AsciiPrinter)
}
}
impl ImagePrinter {
pub fn new(mode: GraphicsMode) -> Result<Self, CreatePrinterError> {
let capabilities = TerminalEmulator::capabilities();
let printer = match mode {
GraphicsMode::Kitty { mode } => Self::Kitty(KittyPrinter::new(mode, capabilities.tmux)?),
GraphicsMode::Iterm2 => Self::Iterm(ItermPrinter::new(ItermMode::Single, capabilities.tmux)),
GraphicsMode::Iterm2Multipart => Self::Iterm(ItermPrinter::new(ItermMode::Multipart, capabilities.tmux)),
GraphicsMode::AsciiBlocks => Self::Ascii(AsciiPrinter),
GraphicsMode::Raw => Self::Raw(RawPrinter),
GraphicsMode::Sixel => Self::Sixel(SixelPrinter::new()?),
};
Ok(printer)
}
}
impl PrintImage for ImagePrinter {
type Image = TerminalImage;
fn register(&self, spec: ImageSpec) -> Result<Self::Image, RegisterImageError> {
let image = match self {
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),
Self::Raw(printer) => TerminalImage::Raw(printer.register(spec)?),
Self::Sixel(printer) => TerminalImage::Sixel(printer.register(spec)?),
};
Ok(image)
}
fn print<T>(&self, image: &Self::Image, options: &PrintOptions, terminal: &mut T) -> Result<(), PrintImageError>
where
T: TerminalIo,
{
match (self, image) {
(Self::Kitty(printer), TerminalImage::Kitty(image)) => printer.print(image, options, terminal),
(Self::Iterm(printer), TerminalImage::Iterm(image)) => printer.print(image, options, terminal),
(Self::Ascii(printer), TerminalImage::Ascii(image)) => printer.print(image, options, terminal),
(Self::Null, _) => Ok(()),
(Self::Raw(printer), TerminalImage::Raw(image)) => printer.print(image, options, terminal),
(Self::Sixel(printer), TerminalImage::Sixel(image)) => printer.print(image, options, terminal),
_ => Err(PrintImageError::Unsupported),
}
}
}
#[derive(Clone, Default)]
pub(crate) struct ImageRegistry {
printer: Arc<ImagePrinter>,
images: Arc<Mutex<HashMap<PathBuf, Image>>>,
}
impl ImageRegistry {
pub fn new(printer: Arc<ImagePrinter>) -> Self {
Self { printer, images: Default::default() }
}
}
impl fmt::Debug for ImageRegistry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let inner = match self.printer.as_ref() {
ImagePrinter::Kitty(_) => "Kitty",
ImagePrinter::Iterm(_) => "Iterm",
ImagePrinter::Ascii(_) => "Ascii",
ImagePrinter::Null => "Null",
ImagePrinter::Raw(_) => "Raw",
ImagePrinter::Sixel(_) => "Sixel",
};
write!(f, "ImageRegistry<{inner}>")
}
}
impl ImageRegistry {
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) => {
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());
}
Ok(image)
}
pub(crate) fn clear(&self) {
self.images.lock().unwrap().clear();
}
}
pub(crate) enum ImageSpec {
Generated(DynamicImage),
Filesystem(PathBuf),
}
#[derive(Debug, thiserror::Error)]
pub(crate) enum CreatePrinterError {
#[error("io: {0}")]
Io(#[from] io::Error),
}
#[derive(Debug, thiserror::Error)]
pub(crate) enum PrintImageError {
#[error(transparent)]
Io(#[from] io::Error),
#[error("unsupported image type")]
Unsupported,
#[error("image decoding: {0}")]
Image(#[from] ImageError),
#[error("{0}")]
Other(Cow<'static, str>),
}
impl From<PaletteColorError> for PrintImageError {
fn from(e: PaletteColorError) -> Self {
Self::Other(e.to_string().into())
}
}
impl From<TerminalError> for PrintImageError {
fn from(e: TerminalError) -> Self {
match e {
TerminalError::Io(e) => Self::Io(e),
TerminalError::Image(e) => e,
}
}
}
#[derive(Debug, thiserror::Error)]
pub(crate) enum RegisterImageError {
#[error(transparent)]
Io(#[from] io::Error),
#[error("image decoding: {0}")]
Image(#[from] ImageError),
#[error("printer can't register images")]
Unsupported,
}
impl PrintImageError {
pub(crate) fn other<S>(message: S) -> Self
where
S: Into<Cow<'static, str>>,
{
Self::Other(message.into())
}
}