Use unicode placeholders to print images under kitty+tmux

This commit is contained in:
Matias Fontanini 2024-01-23 16:22:21 -08:00
parent 6d359d24f5
commit a9cd24fe65
7 changed files with 151 additions and 66 deletions

View File

@ -72,8 +72,12 @@ impl TryFrom<&ImageProtocol> for GraphicsMode {
emulator.preferred_protocol() emulator.preferred_protocol()
} }
ImageProtocol::Iterm2 => GraphicsMode::Iterm2, ImageProtocol::Iterm2 => GraphicsMode::Iterm2,
ImageProtocol::KittyLocal => GraphicsMode::Kitty(KittyMode::Local), ImageProtocol::KittyLocal => {
ImageProtocol::KittyRemote => GraphicsMode::Kitty(KittyMode::Remote), GraphicsMode::Kitty { mode: KittyMode::Local, inside_tmux: TerminalEmulator::is_inside_tmux() }
}
ImageProtocol::KittyRemote => {
GraphicsMode::Kitty { mode: KittyMode::Remote, inside_tmux: TerminalEmulator::is_inside_tmux() }
}
ImageProtocol::AsciiBlocks => GraphicsMode::AsciiBlocks, ImageProtocol::AsciiBlocks => GraphicsMode::AsciiBlocks,
#[cfg(feature = "sixel")] #[cfg(feature = "sixel")]
ImageProtocol::Sixel => GraphicsMode::Sixel, ImageProtocol::Sixel => GraphicsMode::Sixel,
@ -212,7 +216,7 @@ fn run(mut cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
} }
} else { } else {
let commands = CommandSource::new(&path, config.bindings.clone())?; let commands = CommandSource::new(&path, config.bindings.clone())?;
options.print_modal_background = matches!(graphics_mode, GraphicsMode::Kitty(_)); options.print_modal_background = matches!(graphics_mode, GraphicsMode::Kitty { .. });
let options = PresenterOptions { let options = PresenterOptions {
builder_options: options, builder_options: options,

View File

@ -1,8 +1,8 @@
use super::kitty::local_mode_supported;
use crate::{GraphicsMode, KittyMode}; use crate::{GraphicsMode, KittyMode};
use std::env; use std::env;
use super::kitty::local_mode_supported; #[derive(Debug)]
pub enum TerminalEmulator { pub enum TerminalEmulator {
Kitty, Kitty,
Iterm2, Iterm2,
@ -12,6 +12,10 @@ pub enum TerminalEmulator {
} }
impl TerminalEmulator { impl TerminalEmulator {
pub fn is_inside_tmux() -> bool {
env::var("TERM_PROGRAM").ok().as_deref() == Some("tmux")
}
pub fn detect() -> Self { pub fn detect() -> Self {
if Self::is_kitty() { if Self::is_kitty() {
Self::Kitty Self::Kitty
@ -27,10 +31,11 @@ impl TerminalEmulator {
} }
pub fn preferred_protocol(&self) -> GraphicsMode { pub fn preferred_protocol(&self) -> GraphicsMode {
let inside_tmux = Self::is_inside_tmux();
let modes = [ let modes = [
GraphicsMode::Iterm2, GraphicsMode::Iterm2,
GraphicsMode::Kitty(KittyMode::Local), GraphicsMode::Kitty { mode: KittyMode::Local, inside_tmux },
GraphicsMode::Kitty(KittyMode::Remote), GraphicsMode::Kitty { mode: KittyMode::Remote, inside_tmux },
#[cfg(feature = "sixel")] #[cfg(feature = "sixel")]
GraphicsMode::Sixel, GraphicsMode::Sixel,
GraphicsMode::AsciiBlocks, GraphicsMode::AsciiBlocks,
@ -45,8 +50,8 @@ impl TerminalEmulator {
fn supports_graphics_mode(&self, mode: &GraphicsMode) -> bool { fn supports_graphics_mode(&self, mode: &GraphicsMode) -> bool {
match (mode, self) { match (mode, self) {
(GraphicsMode::Kitty(mode), Self::Kitty | Self::WezTerm) => match mode { (GraphicsMode::Kitty { mode, inside_tmux }, Self::Kitty | Self::WezTerm) => match mode {
KittyMode::Local => local_mode_supported().unwrap_or_default(), KittyMode::Local => local_mode_supported(*inside_tmux).unwrap_or_default(),
KittyMode::Remote => true, KittyMode::Remote => true,
}, },
(GraphicsMode::Iterm2, Self::Iterm2 | Self::WezTerm | Self::Mintty) => true, (GraphicsMode::Iterm2, Self::Iterm2 | Self::WezTerm | Self::Mintty) => true,

View File

@ -1,46 +1,18 @@
use super::kitty::KittyMode; use super::kitty::KittyMode;
use viuer::{get_kitty_support, is_iterm_supported, KittySupport};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum GraphicsMode { pub enum GraphicsMode {
Iterm2, Iterm2,
Kitty(KittyMode), Kitty {
mode: KittyMode,
inside_tmux: bool,
},
AsciiBlocks, AsciiBlocks,
#[cfg(feature = "sixel")] #[cfg(feature = "sixel")]
Sixel, Sixel,
} }
impl Default for GraphicsMode {
fn default() -> Self {
let modes = &[
Self::Iterm2,
Self::Kitty(KittyMode::Local),
Self::Kitty(KittyMode::Remote),
#[cfg(feature = "sixel")]
Self::Sixel,
Self::AsciiBlocks,
];
for mode in modes {
if mode.is_supported() {
return mode.clone();
}
}
Self::AsciiBlocks
}
}
impl GraphicsMode { impl GraphicsMode {
pub fn is_supported(&self) -> bool {
match self {
Self::Iterm2 => is_iterm_supported(),
Self::Kitty(KittyMode::Local) => get_kitty_support() == KittySupport::Local,
Self::Kitty(KittyMode::Remote) => get_kitty_support() == KittySupport::Remote,
Self::AsciiBlocks => true,
#[cfg(feature = "sixel")]
Self::Sixel => viuer::is_sixel_supported(),
}
}
pub fn detect_graphics_protocol() { pub fn detect_graphics_protocol() {
viuer::is_iterm_supported(); viuer::is_iterm_supported();
viuer::get_kitty_support(); viuer::get_kitty_support();

View File

@ -1,6 +1,8 @@
use super::printer::{PrintImage, PrintImageError, PrintOptions, RegisterImageError, ResourceProperties}; use super::printer::{PrintImage, PrintImageError, PrintOptions, RegisterImageError, ResourceProperties};
use crate::style::Color;
use base64::{engine::general_purpose::STANDARD, Engine}; use base64::{engine::general_purpose::STANDARD, Engine};
use console::{Key, Term}; use console::{Key, Term};
use crossterm::{cursor::MoveToColumn, style::SetForegroundColor, QueueableCommand};
use image::{codecs::gif::GifDecoder, io::Reader, AnimationDecoder, Delay, DynamicImage, EncodableLayout, RgbaImage}; use image::{codecs::gif::GifDecoder, io::Reader, AnimationDecoder, Delay, DynamicImage, EncodableLayout, RgbaImage};
use rand::Rng; use rand::Rng;
use std::{ use std::{
@ -9,9 +11,35 @@ use std::{
io::{self, BufReader, Write}, io::{self, BufReader, Write},
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::atomic::{AtomicU32, Ordering}, sync::atomic::{AtomicU32, Ordering},
u8,
}; };
use tempfile::{tempdir, NamedTempFile, TempDir}; use tempfile::{tempdir, NamedTempFile, TempDir};
const IMAGE_PLACEHOLDER: &str = "\u{10EEEE}";
const DIACRITICS: &[u32] = &[
0x305, 0x30d, 0x30e, 0x310, 0x312, 0x33d, 0x33e, 0x33f, 0x346, 0x34a, 0x34b, 0x34c, 0x350, 0x351, 0x352, 0x357,
0x35b, 0x363, 0x364, 0x365, 0x366, 0x367, 0x368, 0x369, 0x36a, 0x36b, 0x36c, 0x36d, 0x36e, 0x36f, 0x483, 0x484,
0x485, 0x486, 0x487, 0x592, 0x593, 0x594, 0x595, 0x597, 0x598, 0x599, 0x59c, 0x59d, 0x59e, 0x59f, 0x5a0, 0x5a1,
0x5a8, 0x5a9, 0x5ab, 0x5ac, 0x5af, 0x5c4, 0x610, 0x611, 0x612, 0x613, 0x614, 0x615, 0x616, 0x617, 0x657, 0x658,
0x659, 0x65a, 0x65b, 0x65d, 0x65e, 0x6d6, 0x6d7, 0x6d8, 0x6d9, 0x6da, 0x6db, 0x6dc, 0x6df, 0x6e0, 0x6e1, 0x6e2,
0x6e4, 0x6e7, 0x6e8, 0x6eb, 0x6ec, 0x730, 0x732, 0x733, 0x735, 0x736, 0x73a, 0x73d, 0x73f, 0x740, 0x741, 0x743,
0x745, 0x747, 0x749, 0x74a, 0x7eb, 0x7ec, 0x7ed, 0x7ee, 0x7ef, 0x7f0, 0x7f1, 0x7f3, 0x816, 0x817, 0x818, 0x819,
0x81b, 0x81c, 0x81d, 0x81e, 0x81f, 0x820, 0x821, 0x822, 0x823, 0x825, 0x826, 0x827, 0x829, 0x82a, 0x82b, 0x82c,
0x82d, 0x951, 0x953, 0x954, 0xf82, 0xf83, 0xf86, 0xf87, 0x135d, 0x135e, 0x135f, 0x17dd, 0x193a, 0x1a17, 0x1a75,
0x1a76, 0x1a77, 0x1a78, 0x1a79, 0x1a7a, 0x1a7b, 0x1a7c, 0x1b6b, 0x1b6d, 0x1b6e, 0x1b6f, 0x1b70, 0x1b71, 0x1b72,
0x1b73, 0x1cd0, 0x1cd1, 0x1cd2, 0x1cda, 0x1cdb, 0x1ce0, 0x1dc0, 0x1dc1, 0x1dc3, 0x1dc4, 0x1dc5, 0x1dc6, 0x1dc7,
0x1dc8, 0x1dc9, 0x1dcb, 0x1dcc, 0x1dd1, 0x1dd2, 0x1dd3, 0x1dd4, 0x1dd5, 0x1dd6, 0x1dd7, 0x1dd8, 0x1dd9, 0x1dda,
0x1ddb, 0x1ddc, 0x1ddd, 0x1dde, 0x1ddf, 0x1de0, 0x1de1, 0x1de2, 0x1de3, 0x1de4, 0x1de5, 0x1de6, 0x1dfe, 0x20d0,
0x20d1, 0x20d4, 0x20d5, 0x20d6, 0x20d7, 0x20db, 0x20dc, 0x20e1, 0x20e7, 0x20e9, 0x20f0, 0x2cef, 0x2cf0, 0x2cf1,
0x2de0, 0x2de1, 0x2de2, 0x2de3, 0x2de4, 0x2de5, 0x2de6, 0x2de7, 0x2de8, 0x2de9, 0x2dea, 0x2deb, 0x2dec, 0x2ded,
0x2dee, 0x2def, 0x2df0, 0x2df1, 0x2df2, 0x2df3, 0x2df4, 0x2df5, 0x2df6, 0x2df7, 0x2df8, 0x2df9, 0x2dfa, 0x2dfb,
0x2dfc, 0x2dfd, 0x2dfe, 0x2dff, 0xa66f, 0xa67c, 0xa67d, 0xa6f0, 0xa6f1, 0xa8e0, 0xa8e1, 0xa8e2, 0xa8e3, 0xa8e4,
0xa8e5, 0xa8e6, 0xa8e7, 0xa8e8, 0xa8e9, 0xa8ea, 0xa8eb, 0xa8ec, 0xa8ed, 0xa8ee, 0xa8ef, 0xa8f0, 0xa8f1, 0xaab0,
0xaab2, 0xaab3, 0xaab7, 0xaab8, 0xaabe, 0xaabf, 0xaac1, 0xfe20, 0xfe21, 0xfe22, 0xfe23, 0xfe24, 0xfe25, 0xfe26,
0x10a0f, 0x10a38, 0x1d185, 0x1d186, 0x1d187, 0x1d188, 0x1d189, 0x1d1aa, 0x1d1ab, 0x1d1ac, 0x1d1ad, 0x1d242,
0x1d243, 0x1d244,
];
enum GenericResource<B> { enum GenericResource<B> {
Image(B), Image(B),
Gif(Vec<GifFrame<B>>), Gif(Vec<GifFrame<B>>),
@ -70,14 +98,15 @@ struct GifFrame<T> {
pub struct KittyPrinter { pub struct KittyPrinter {
mode: KittyMode, mode: KittyMode,
tmux: bool,
base_directory: TempDir, base_directory: TempDir,
next: AtomicU32, next: AtomicU32,
} }
impl KittyPrinter { impl KittyPrinter {
pub(crate) fn new(mode: KittyMode) -> io::Result<Self> { pub(crate) fn new(mode: KittyMode, tmux: bool) -> io::Result<Self> {
let base_directory = tempdir()?; let base_directory = tempdir()?;
Ok(Self { mode, base_directory, next: Default::default() }) Ok(Self { mode, tmux, base_directory, next: Default::default() })
} }
fn allocate_tempfile(&self) -> PathBuf { fn allocate_tempfile(&self) -> PathBuf {
@ -115,6 +144,10 @@ impl KittyPrinter {
} }
} }
fn generate_image_id() -> u32 {
rand::thread_rng().gen_range(1..u32::MAX)
}
fn print_image<W>( fn print_image<W>(
&self, &self,
dimensions: (u32, u32), dimensions: (u32, u32),
@ -125,7 +158,7 @@ impl KittyPrinter {
where where
W: io::Write, W: io::Write,
{ {
let options = vec![ let mut options = vec![
ControlOption::Format(ImageFormat::Rgba), ControlOption::Format(ImageFormat::Rgba),
ControlOption::Action(Action::TransmitAndDisplay), ControlOption::Action(Action::TransmitAndDisplay),
ControlOption::Width(dimensions.0), ControlOption::Width(dimensions.0),
@ -135,11 +168,21 @@ impl KittyPrinter {
ControlOption::ZIndex(print_options.z_index), ControlOption::ZIndex(print_options.z_index),
ControlOption::Quiet(2), ControlOption::Quiet(2),
]; ];
let mut image_id = 0;
if self.tmux {
image_id = Self::generate_image_id();
options.extend([ControlOption::UnicodePlaceholder, ControlOption::ImageId(image_id)]);
}
match &buffer { match &buffer {
KittyBuffer::Filesystem(path) => Self::print_local(options, path, writer), KittyBuffer::Filesystem(path) => self.print_local(options, path, writer)?,
KittyBuffer::Memory(buffer) => Self::print_remote(options, buffer, writer, false), KittyBuffer::Memory(buffer) => self.print_remote(options, buffer, writer, false)?,
};
if self.tmux {
self.print_unicode_placeholders(writer, print_options, image_id)?;
} }
Ok(())
} }
fn print_gif<W>( fn print_gif<W>(
@ -152,10 +195,10 @@ impl KittyPrinter {
where where
W: io::Write, W: io::Write,
{ {
let image_id = rand::thread_rng().gen(); let image_id = Self::generate_image_id();
for (frame_id, frame) in frames.iter().enumerate() { for (frame_id, frame) in frames.iter().enumerate() {
let (num, denom) = frame.delay.numer_denom_ms(); let (num, denom) = frame.delay.numer_denom_ms();
// default to 100ms in case somehow the denomiator is 0 // default to 100ms in case somehow the denominator is 0
let delay = num.checked_div(denom).unwrap_or(100); let delay = num.checked_div(denom).unwrap_or(100);
let mut options = vec![ let mut options = vec![
ControlOption::Format(ImageFormat::Rgba), ControlOption::Format(ImageFormat::Rgba),
@ -171,14 +214,17 @@ impl KittyPrinter {
ControlOption::Columns(print_options.columns), ControlOption::Columns(print_options.columns),
ControlOption::Rows(print_options.rows), ControlOption::Rows(print_options.rows),
]); ]);
if self.tmux {
options.push(ControlOption::UnicodePlaceholder);
}
} else { } else {
options.extend([ControlOption::Action(Action::TransmitFrame), ControlOption::Delay(delay)]); options.extend([ControlOption::Action(Action::TransmitFrame), ControlOption::Delay(delay)]);
} }
let is_frame = frame_id > 0; let is_frame = frame_id > 0;
match &frame.buffer { match &frame.buffer {
KittyBuffer::Filesystem(path) => Self::print_local(options, path, writer)?, KittyBuffer::Filesystem(path) => self.print_local(options, path, writer)?,
KittyBuffer::Memory(buffer) => Self::print_remote(options, buffer, writer, is_frame)?, KittyBuffer::Memory(buffer) => self.print_remote(options, buffer, writer, is_frame)?,
}; };
if frame_id == 0 { if frame_id == 0 {
@ -188,7 +234,7 @@ impl KittyPrinter {
ControlOption::FrameId(1), ControlOption::FrameId(1),
ControlOption::Loops(1), ControlOption::Loops(1),
]; ];
let command = ControlCommand(options, ""); let command = self.make_command(options, "");
write!(writer, "{command}")?; write!(writer, "{command}")?;
} else if frame_id == 1 { } else if frame_id == 1 {
let options = &[ let options = &[
@ -197,10 +243,13 @@ impl KittyPrinter {
ControlOption::FrameId(1), ControlOption::FrameId(1),
ControlOption::AnimationState(2), ControlOption::AnimationState(2),
]; ];
let command = ControlCommand(options, ""); let command = self.make_command(options, "");
write!(writer, "{command}")?; write!(writer, "{command}")?;
} }
} }
if self.tmux {
self.print_unicode_placeholders(writer, print_options, image_id)?;
}
let options = &[ let options = &[
ControlOption::Action(Action::Animate), ControlOption::Action(Action::Animate),
ControlOption::ImageId(image_id), ControlOption::ImageId(image_id),
@ -209,12 +258,21 @@ impl KittyPrinter {
ControlOption::Loops(1), ControlOption::Loops(1),
ControlOption::Quiet(2), ControlOption::Quiet(2),
]; ];
let command = ControlCommand(options, ""); let command = self.make_command(options, "");
write!(writer, "{command}")?; write!(writer, "{command}")?;
Ok(()) Ok(())
} }
fn print_local<W>(mut options: Vec<ControlOption>, path: &Path, writer: &mut W) -> Result<(), PrintImageError> fn make_command<'a, P>(&self, options: &'a [ControlOption], payload: P) -> ControlCommand<'a, P> {
ControlCommand { options, payload, tmux: self.tmux }
}
fn print_local<W>(
&self,
mut options: Vec<ControlOption>,
path: &Path,
writer: &mut W,
) -> Result<(), PrintImageError>
where where
W: io::Write, W: io::Write,
{ {
@ -224,12 +282,13 @@ impl KittyPrinter {
let encoded_path = STANDARD.encode(path); let encoded_path = STANDARD.encode(path);
options.push(ControlOption::Medium(TransmissionMedium::LocalFile)); options.push(ControlOption::Medium(TransmissionMedium::LocalFile));
let command = ControlCommand(&options, &encoded_path); let command = self.make_command(&options, &encoded_path);
write!(writer, "{command}")?; write!(writer, "{command}")?;
Ok(()) Ok(())
} }
fn print_remote<W>( fn print_remote<W>(
&self,
mut options: Vec<ControlOption>, mut options: Vec<ControlOption>,
frame: &[u8], frame: &[u8],
writer: &mut W, writer: &mut W,
@ -252,7 +311,7 @@ impl KittyPrinter {
options.push(ControlOption::MoreData(more)); options.push(ControlOption::MoreData(more));
let payload = &payload[start..end]; let payload = &payload[start..end];
let command = ControlCommand(&options, payload); let command = self.make_command(&options, payload);
write!(writer, "{command}")?; write!(writer, "{command}")?;
options.clear(); options.clear();
@ -263,6 +322,33 @@ impl KittyPrinter {
Ok(()) Ok(())
} }
fn print_unicode_placeholders<W: Write>(
&self,
writer: &mut W,
options: &PrintOptions,
image_id: u32,
) -> Result<(), PrintImageError> {
let color = Color::new((image_id >> 16) as u8, (image_id >> 8) as u8, image_id as u8);
writer.queue(SetForegroundColor(color.into()))?;
if options.rows.max(options.columns) >= DIACRITICS.len() as u16 {
return Err(PrintImageError::other("image is too large to fit in tmux"));
}
let last_byte = char::from_u32(DIACRITICS[(image_id >> 24) as usize]).unwrap();
for row in 0..options.rows {
let row_diacritic = char::from_u32(DIACRITICS[row as usize]).unwrap();
for column in 0..options.columns {
let column_diacritic = char::from_u32(DIACRITICS[column as usize]).unwrap();
write!(writer, "{IMAGE_PLACEHOLDER}{row_diacritic}{column_diacritic}{last_byte}")?;
}
if row != options.rows - 1 {
writeln!(writer)?;
}
writer.queue(MoveToColumn(options.cursor_position.column))?;
}
Ok(())
}
fn load_raw_resource(path: &Path) -> Result<RawResource, RegisterImageError> { fn load_raw_resource(path: &Path) -> Result<RawResource, RegisterImageError> {
let file = File::open(path)?; let file = File::open(path)?;
if path.extension().unwrap_or_default() == "gif" { if path.extension().unwrap_or_default() == "gif" {
@ -324,18 +410,30 @@ pub enum KittyMode {
Remote, Remote,
} }
struct ControlCommand<'a, D>(&'a [ControlOption], D); struct ControlCommand<'a, D> {
options: &'a [ControlOption],
payload: D,
tmux: bool,
}
impl<'a, D: fmt::Display> fmt::Display for ControlCommand<'a, D> { impl<'a, D: fmt::Display> fmt::Display for ControlCommand<'a, D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.tmux {
write!(f, "\x1bPtmux;\x1b")?;
}
write!(f, "\x1b_G")?; write!(f, "\x1b_G")?;
for (index, option) in self.0.iter().enumerate() { for (index, option) in self.options.iter().enumerate() {
if index > 0 { if index > 0 {
write!(f, ",")?; write!(f, ",")?;
} }
write!(f, "{option}")?; write!(f, "{option}")?;
} }
write!(f, ";{}\x1b\\", &self.1)?; write!(f, ";{}", &self.payload)?;
if self.tmux {
write!(f, "\x1b\x1b\\\x1b\\")?;
} else {
write!(f, "\x1b\\")?;
}
Ok(()) Ok(())
} }
} }
@ -357,6 +455,7 @@ enum ControlOption {
Loops(u32), Loops(u32),
Quiet(u32), Quiet(u32),
ZIndex(i32), ZIndex(i32),
UnicodePlaceholder,
} }
impl fmt::Display for ControlOption { impl fmt::Display for ControlOption {
@ -379,6 +478,7 @@ impl fmt::Display for ControlOption {
Loops(count) => write!(f, "v={count}"), Loops(count) => write!(f, "v={count}"),
Quiet(option) => write!(f, "q={option}"), Quiet(option) => write!(f, "q={option}"),
ZIndex(index) => write!(f, "z={index}"), ZIndex(index) => write!(f, "z={index}"),
UnicodePlaceholder => write!(f, "U=1"),
} }
} }
} }
@ -436,7 +536,7 @@ impl fmt::Display for Action {
} }
} }
pub(crate) fn local_mode_supported() -> io::Result<bool> { pub(crate) fn local_mode_supported(inside_tmux: bool) -> io::Result<bool> {
let mut file = NamedTempFile::new()?; let mut file = NamedTempFile::new()?;
let image = DynamicImage::new_rgba8(1, 1); let image = DynamicImage::new_rgba8(1, 1);
file.write_all(image.into_rgba8().as_raw().as_bytes())?; file.write_all(image.into_rgba8().as_raw().as_bytes())?;
@ -455,7 +555,7 @@ pub(crate) fn local_mode_supported() -> io::Result<bool> {
ControlOption::Height(1), ControlOption::Height(1),
]; ];
let mut writer = io::stdout(); let mut writer = io::stdout();
let command = ControlCommand(options, encoded_path); let command = ControlCommand { options, payload: encoded_path, tmux: inside_tmux };
write!(writer, "{command}")?; write!(writer, "{command}")?;
writer.flush()?; writer.flush()?;

View File

@ -27,6 +27,7 @@ pub(crate) trait ResourceProperties {
fn dimensions(&self) -> (u32, u32); fn dimensions(&self) -> (u32, u32);
} }
#[derive(Debug)]
pub(crate) struct PrintOptions { pub(crate) struct PrintOptions {
pub(crate) columns: u16, pub(crate) columns: u16,
pub(crate) rows: u16, pub(crate) rows: u16,
@ -65,7 +66,7 @@ impl Default for ImagePrinter {
impl ImagePrinter { impl ImagePrinter {
pub fn new(mode: GraphicsMode) -> io::Result<Self> { pub fn new(mode: GraphicsMode) -> io::Result<Self> {
let printer = match mode { let printer = match mode {
GraphicsMode::Kitty(mode) => Self::new_kitty(mode)?, GraphicsMode::Kitty { mode, inside_tmux } => Self::new_kitty(mode, inside_tmux)?,
GraphicsMode::Iterm2 => Self::new_iterm(), GraphicsMode::Iterm2 => Self::new_iterm(),
GraphicsMode::AsciiBlocks => Self::new_ascii(), GraphicsMode::AsciiBlocks => Self::new_ascii(),
#[cfg(feature = "sixel")] #[cfg(feature = "sixel")]
@ -74,8 +75,8 @@ impl ImagePrinter {
Ok(printer) Ok(printer)
} }
fn new_kitty(mode: KittyMode) -> io::Result<Self> { fn new_kitty(mode: KittyMode, inside_tmux: bool) -> io::Result<Self> {
Ok(Self::Kitty(KittyPrinter::new(mode)?)) Ok(Self::Kitty(KittyPrinter::new(mode, inside_tmux)?))
} }
fn new_iterm() -> Self { fn new_iterm() -> Self {

View File

@ -28,6 +28,7 @@ pub(crate) fn scale_image(
// Don't go too far wide. // Don't go too far wide.
let width_in_columns = width_in_columns.min(column_margin); let width_in_columns = width_in_columns.min(column_margin);
let height_in_rows = (width_in_columns as f64 * aspect_ratio / 2.0) as u16; let height_in_rows = (width_in_columns as f64 * aspect_ratio / 2.0) as u16;
let height_in_rows = height_in_rows.max(1);
// Draw it in the middle // Draw it in the middle
let start_column = dimensions.columns / 2 - (width_in_columns / 2) as u16; let start_column = dimensions.columns / 2 - (width_in_columns / 2) as u16;

View File

@ -501,8 +501,10 @@ impl<'a> PresentationBuilder<'a> {
fn push_image(&mut self, image: Image) { fn push_image(&mut self, image: Image) {
let properties = ImageProperties { z_index: DEFAULT_Z_INDEX, size: Default::default(), restore_cursor: false }; let properties = ImageProperties { z_index: DEFAULT_Z_INDEX, size: Default::default(), restore_cursor: false };
self.chunk_operations.push(RenderOperation::RenderImage(image, properties)); self.chunk_operations.extend([
self.chunk_operations.push(RenderOperation::SetColors(self.theme.default_style.colors.clone())); RenderOperation::RenderImage(image, properties),
RenderOperation::SetColors(self.theme.default_style.colors.clone()),
]);
} }
fn push_list(&mut self, list: Vec<ListItem>) { fn push_list(&mut self, list: Vec<ListItem>) {