mirror of
https://github.com/mfontanini/presenterm.git
synced 2025-05-31 23:25:17 +00:00
Allow controlling size of rendered sixel images
This commit is contained in:
parent
f2695a93a4
commit
28d072105b
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -920,6 +920,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"serde_yaml",
|
||||
"sixel-rs",
|
||||
"strum",
|
||||
"syntect",
|
||||
"tempfile",
|
||||
|
@ -16,6 +16,7 @@ crossterm = { version = "0.27", features = ["serde"] }
|
||||
hex = "0.4"
|
||||
flate2 = "1.0"
|
||||
image = "0.24"
|
||||
sixel-rs = { version = "0.3.3", optional = true }
|
||||
merge-struct = "0.1.0"
|
||||
itertools = "0.12"
|
||||
once_cell = "1.19"
|
||||
@ -42,7 +43,8 @@ rstest = { version = "0.18", default-features = false }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
sixel = ["viuer/sixel"]
|
||||
# TODO viuer
|
||||
sixel = ["viuer/sixel", "sixel-rs"]
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 0
|
||||
|
@ -41,12 +41,23 @@ pub enum ConfigLoadError {
|
||||
Invalid(#[from] serde_yaml::Error),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct DefaultsConfig {
|
||||
pub theme: Option<String>,
|
||||
|
||||
pub terminal_font_size: Option<u8>,
|
||||
#[serde(default = "default_font_size")]
|
||||
pub terminal_font_size: u8,
|
||||
}
|
||||
|
||||
impl Default for DefaultsConfig {
|
||||
fn default() -> Self {
|
||||
Self { theme: Default::default(), terminal_font_size: default_font_size() }
|
||||
}
|
||||
}
|
||||
|
||||
fn default_font_size() -> u8 {
|
||||
16
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
|
@ -142,7 +142,7 @@ impl<'a> Exporter<'a> {
|
||||
ImageSource::Generated => {
|
||||
let mut buffer = Vec::new();
|
||||
let dimensions = image.original.dimensions();
|
||||
let ImageResource::Viuer(resource) = image.original.resource.as_ref() else {
|
||||
let ImageResource::Ascii(resource) = image.original.resource.as_ref() else {
|
||||
panic!("not in viuer mode")
|
||||
};
|
||||
PngEncoder::new(&mut buffer).write_image(
|
||||
@ -268,7 +268,7 @@ impl ImageReplacer {
|
||||
}
|
||||
self.images.push(ReplacedImage { original: image, color });
|
||||
|
||||
Image::new(ImageResource::Viuer(replacement.into()), ImageSource::Generated)
|
||||
Image::new(ImageResource::Ascii(replacement.into()), ImageSource::Generated)
|
||||
}
|
||||
|
||||
fn allocate_color(&mut self) -> u32 {
|
||||
|
@ -189,10 +189,6 @@ fn run(mut cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
|
||||
display_acknowledgements();
|
||||
return Ok(());
|
||||
}
|
||||
if !cli.generate_pdf_metadata {
|
||||
// Pre-load this so we don't flicker on the first displayed image when using viuer.
|
||||
GraphicsMode::detect_graphics_protocol();
|
||||
}
|
||||
|
||||
let path = cli.path.take().unwrap_or_else(|| {
|
||||
Cli::command().error(ErrorKind::MissingRequiredArgument, "no path specified").exit();
|
||||
@ -200,7 +196,7 @@ fn run(mut cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let resources_path = path.parent().unwrap_or(Path::new("/"));
|
||||
let mut options = make_builder_options(&config, &mode, force_default_theme);
|
||||
let graphics_mode = select_graphics_mode(&cli);
|
||||
let printer = Rc::new(ImagePrinter::new(graphics_mode.clone())?);
|
||||
let printer = Rc::new(ImagePrinter::new(graphics_mode.clone(), config.defaults.terminal_font_size)?);
|
||||
let registry = ImageRegistry(printer.clone());
|
||||
let resources = Resources::new(resources_path, registry.clone());
|
||||
let typst = TypstRender::new(config.typst.ppi, registry);
|
||||
|
@ -2,22 +2,22 @@ use super::printer::{PrintImage, PrintImageError, PrintOptions, RegisterImageErr
|
||||
use image::{DynamicImage, GenericImageView};
|
||||
use std::{fs, ops::Deref};
|
||||
|
||||
pub(crate) struct ViuerResource(DynamicImage);
|
||||
pub(crate) struct AsciiResource(DynamicImage);
|
||||
|
||||
impl ResourceProperties for ViuerResource {
|
||||
impl ResourceProperties for AsciiResource {
|
||||
fn dimensions(&self) -> (u32, u32) {
|
||||
self.0.dimensions()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DynamicImage> for ViuerResource {
|
||||
impl From<DynamicImage> for AsciiResource {
|
||||
fn from(image: DynamicImage) -> Self {
|
||||
let image = image.into_rgba8();
|
||||
Self(image.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ViuerResource {
|
||||
impl Deref for AsciiResource {
|
||||
type Target = DynamicImage;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@ -25,38 +25,20 @@ impl Deref for ViuerResource {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sixel")]
|
||||
#[derive(Default)]
|
||||
pub(crate) enum SixelSupport {
|
||||
Enabled,
|
||||
#[default]
|
||||
Disabled,
|
||||
}
|
||||
pub struct AsciiPrinter;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ViuerPrinter {
|
||||
#[cfg(feature = "sixel")]
|
||||
sixel: SixelSupport,
|
||||
}
|
||||
|
||||
impl ViuerPrinter {
|
||||
#[cfg(feature = "sixel")]
|
||||
pub(crate) fn new(sixel: SixelSupport) -> Self {
|
||||
Self { sixel }
|
||||
}
|
||||
}
|
||||
|
||||
impl PrintImage for ViuerPrinter {
|
||||
type Resource = ViuerResource;
|
||||
impl PrintImage for AsciiPrinter {
|
||||
type Resource = AsciiResource;
|
||||
|
||||
fn register_image(&self, image: image::DynamicImage) -> Result<Self::Resource, RegisterImageError> {
|
||||
Ok(ViuerResource(image))
|
||||
Ok(AsciiResource(image))
|
||||
}
|
||||
|
||||
fn register_resource<P: AsRef<std::path::Path>>(&self, path: P) -> Result<Self::Resource, RegisterImageError> {
|
||||
let contents = fs::read(path)?;
|
||||
let image = image::load_from_memory(&contents)?;
|
||||
Ok(ViuerResource(image))
|
||||
Ok(AsciiResource(image))
|
||||
}
|
||||
|
||||
fn print<W>(&self, image: &Self::Resource, options: &PrintOptions, _writer: &mut W) -> Result<(), PrintImageError>
|
||||
@ -68,10 +50,10 @@ impl PrintImage for ViuerPrinter {
|
||||
height: Some(options.rows as u32),
|
||||
use_kitty: false,
|
||||
use_iterm: false,
|
||||
#[cfg(feature = "sixel")]
|
||||
use_sixel: false,
|
||||
x: options.cursor_position.column,
|
||||
y: options.cursor_position.row as i16,
|
||||
#[cfg(feature = "sixel")]
|
||||
use_sixel: matches!(self.sixel, SixelSupport::Enabled),
|
||||
..Default::default()
|
||||
};
|
||||
viuer::print(&image.0, &config)?;
|
@ -11,12 +11,3 @@ pub enum GraphicsMode {
|
||||
#[cfg(feature = "sixel")]
|
||||
Sixel,
|
||||
}
|
||||
|
||||
impl GraphicsMode {
|
||||
pub fn detect_graphics_protocol() {
|
||||
viuer::is_iterm_supported();
|
||||
viuer::get_kitty_support();
|
||||
#[cfg(feature = "sixel")]
|
||||
viuer::is_sixel_supported();
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
mod ascii;
|
||||
pub(crate) mod emulator;
|
||||
pub(crate) mod graphics;
|
||||
pub(crate) mod image;
|
||||
@ -6,4 +7,5 @@ pub(crate) mod kitty;
|
||||
pub(crate) mod printer;
|
||||
pub(crate) mod register;
|
||||
pub(crate) mod scale;
|
||||
mod viuer;
|
||||
#[cfg(feature = "sixel")]
|
||||
pub(crate) mod sixel;
|
||||
|
@ -1,11 +1,10 @@
|
||||
use crate::render::properties::CursorPosition;
|
||||
|
||||
use super::{
|
||||
ascii::{AsciiPrinter, AsciiResource},
|
||||
graphics::GraphicsMode,
|
||||
iterm::{ItermPrinter, ItermResource},
|
||||
kitty::{KittyMode, KittyPrinter, KittyResource},
|
||||
viuer::{ViuerPrinter, ViuerResource},
|
||||
};
|
||||
use crate::render::properties::CursorPosition;
|
||||
use image::{DynamicImage, ImageError};
|
||||
use std::{borrow::Cow, io, path::Path};
|
||||
|
||||
@ -38,7 +37,9 @@ pub(crate) struct PrintOptions {
|
||||
pub(crate) enum ImageResource {
|
||||
Kitty(KittyResource),
|
||||
Iterm(ItermResource),
|
||||
Viuer(ViuerResource),
|
||||
Ascii(AsciiResource),
|
||||
#[cfg(feature = "sixel")]
|
||||
Sixel(super::sixel::SixelResource),
|
||||
}
|
||||
|
||||
impl ResourceProperties for ImageResource {
|
||||
@ -46,7 +47,9 @@ impl ResourceProperties for ImageResource {
|
||||
match self {
|
||||
Self::Kitty(resource) => resource.dimensions(),
|
||||
Self::Iterm(resource) => resource.dimensions(),
|
||||
Self::Viuer(resource) => resource.dimensions(),
|
||||
Self::Ascii(resource) => resource.dimensions(),
|
||||
#[cfg(feature = "sixel")]
|
||||
Self::Sixel(resource) => resource.dimensions(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -54,7 +57,9 @@ impl ResourceProperties for ImageResource {
|
||||
pub enum ImagePrinter {
|
||||
Kitty(KittyPrinter),
|
||||
Iterm(ItermPrinter),
|
||||
Viuer(ViuerPrinter),
|
||||
Ascii(AsciiPrinter),
|
||||
#[cfg(feature = "sixel")]
|
||||
Sixel(super::sixel::SixelPrinter),
|
||||
}
|
||||
|
||||
impl Default for ImagePrinter {
|
||||
@ -64,13 +69,13 @@ impl Default for ImagePrinter {
|
||||
}
|
||||
|
||||
impl ImagePrinter {
|
||||
pub fn new(mode: GraphicsMode) -> io::Result<Self> {
|
||||
pub fn new(mode: GraphicsMode, #[allow(unused_variables)] font_size: u8) -> Result<Self, CreatePrinterError> {
|
||||
let printer = match mode {
|
||||
GraphicsMode::Kitty { mode, inside_tmux } => Self::new_kitty(mode, inside_tmux)?,
|
||||
GraphicsMode::Iterm2 => Self::new_iterm(),
|
||||
GraphicsMode::AsciiBlocks => Self::new_ascii(),
|
||||
#[cfg(feature = "sixel")]
|
||||
GraphicsMode::Sixel => Self::new_sixel(),
|
||||
GraphicsMode::Sixel => Self::new_sixel(font_size)?,
|
||||
};
|
||||
Ok(printer)
|
||||
}
|
||||
@ -84,12 +89,12 @@ impl ImagePrinter {
|
||||
}
|
||||
|
||||
fn new_ascii() -> Self {
|
||||
Self::Viuer(ViuerPrinter::default())
|
||||
Self::Ascii(AsciiPrinter)
|
||||
}
|
||||
|
||||
#[cfg(feature = "sixel")]
|
||||
fn new_sixel() -> Self {
|
||||
Self::Viuer(ViuerPrinter::new(super::viuer::SixelSupport::Enabled))
|
||||
fn new_sixel(font_size: u8) -> Result<Self, CreatePrinterError> {
|
||||
Ok(Self::Sixel(super::sixel::SixelPrinter::new(font_size)?))
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,7 +105,9 @@ impl PrintImage for ImagePrinter {
|
||||
let resource = match self {
|
||||
Self::Kitty(printer) => ImageResource::Kitty(printer.register_image(image)?),
|
||||
Self::Iterm(printer) => ImageResource::Iterm(printer.register_image(image)?),
|
||||
Self::Viuer(printer) => ImageResource::Viuer(printer.register_image(image)?),
|
||||
Self::Ascii(printer) => ImageResource::Ascii(printer.register_image(image)?),
|
||||
#[cfg(feature = "sixel")]
|
||||
Self::Sixel(printer) => ImageResource::Sixel(printer.register_image(image)?),
|
||||
};
|
||||
Ok(resource)
|
||||
}
|
||||
@ -109,7 +116,9 @@ impl PrintImage for ImagePrinter {
|
||||
let resource = match self {
|
||||
Self::Kitty(printer) => ImageResource::Kitty(printer.register_resource(path)?),
|
||||
Self::Iterm(printer) => ImageResource::Iterm(printer.register_resource(path)?),
|
||||
Self::Viuer(printer) => ImageResource::Viuer(printer.register_resource(path)?),
|
||||
Self::Ascii(printer) => ImageResource::Ascii(printer.register_resource(path)?),
|
||||
#[cfg(feature = "sixel")]
|
||||
Self::Sixel(printer) => ImageResource::Sixel(printer.register_resource(path)?),
|
||||
};
|
||||
Ok(resource)
|
||||
}
|
||||
@ -121,12 +130,23 @@ impl PrintImage for ImagePrinter {
|
||||
match (self, image) {
|
||||
(Self::Kitty(printer), ImageResource::Kitty(image)) => printer.print(image, options, writer),
|
||||
(Self::Iterm(printer), ImageResource::Iterm(image)) => printer.print(image, options, writer),
|
||||
(Self::Viuer(printer), ImageResource::Viuer(image)) => printer.print(image, options, writer),
|
||||
(Self::Ascii(printer), ImageResource::Ascii(image)) => printer.print(image, options, writer),
|
||||
#[cfg(feature = "sixel")]
|
||||
(Self::Sixel(printer), ImageResource::Sixel(image)) => printer.print(image, options, writer),
|
||||
_ => Err(PrintImageError::Unsupported),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum CreatePrinterError {
|
||||
#[error("io: {0}")]
|
||||
Io(#[from] io::Error),
|
||||
|
||||
#[error("unexpected: {0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum PrintImageError {
|
||||
#[error(transparent)]
|
||||
|
71
src/media/sixel.rs
Normal file
71
src/media/sixel.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use super::printer::{
|
||||
CreatePrinterError, PrintImage, PrintImageError, PrintOptions, RegisterImageError, ResourceProperties,
|
||||
};
|
||||
use image::{imageops::FilterType, DynamicImage, GenericImageView};
|
||||
use sixel_rs::{
|
||||
encoder::{Encoder, QuickFrameBuilder},
|
||||
optflags::EncodePolicy,
|
||||
sys::PixelFormat,
|
||||
};
|
||||
use std::{fs, io};
|
||||
|
||||
pub(crate) struct SixelResource(DynamicImage);
|
||||
|
||||
impl ResourceProperties for SixelResource {
|
||||
fn dimensions(&self) -> (u32, u32) {
|
||||
self.0.dimensions()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SixelPrinter {
|
||||
encoder: Encoder,
|
||||
font_size: u32,
|
||||
}
|
||||
|
||||
impl SixelPrinter {
|
||||
pub(crate) fn new(font_size: u8) -> Result<Self, CreatePrinterError> {
|
||||
let encoder =
|
||||
Encoder::new().map_err(|e| CreatePrinterError::Other(format!("creating sixel encoder: {e:?}")))?;
|
||||
encoder
|
||||
.set_encode_policy(EncodePolicy::Fast)
|
||||
.map_err(|e| CreatePrinterError::Other(format!("setting encoder policy: {e:?}")))?;
|
||||
Ok(Self { encoder, font_size: font_size.into() })
|
||||
}
|
||||
}
|
||||
|
||||
impl PrintImage for SixelPrinter {
|
||||
type Resource = SixelResource;
|
||||
|
||||
fn register_image(&self, image: image::DynamicImage) -> Result<Self::Resource, RegisterImageError> {
|
||||
Ok(SixelResource(image))
|
||||
}
|
||||
|
||||
fn register_resource<P: AsRef<std::path::Path>>(&self, path: P) -> Result<Self::Resource, RegisterImageError> {
|
||||
let contents = fs::read(path)?;
|
||||
let image = image::load_from_memory(&contents)?;
|
||||
Ok(SixelResource(image))
|
||||
}
|
||||
|
||||
fn print<W>(&self, image: &Self::Resource, options: &PrintOptions, writer: &mut W) -> Result<(), PrintImageError>
|
||||
where
|
||||
W: io::Write,
|
||||
{
|
||||
// We're already positioned in the right place but we may not have flushed that yet.
|
||||
writer.flush()?;
|
||||
|
||||
// This check was taken from viuer: it seems to be a bug in xterm
|
||||
let width = (self.font_size * options.columns as u32).min(1000);
|
||||
let height = self.font_size * 2 * options.rows as u32;
|
||||
let image = image.0.resize_exact(width, height, FilterType::Triangle);
|
||||
let bytes = image.into_rgba8().into_raw();
|
||||
|
||||
let frame = QuickFrameBuilder::new()
|
||||
.width(width as usize)
|
||||
.height(height as usize)
|
||||
.format(PixelFormat::RGBA8888)
|
||||
.pixels(bytes);
|
||||
|
||||
self.encoder.encode_bytes(frame).map_err(|e| PrintImageError::other(format!("encoding sixel image: {e:?}")))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ use std::{
|
||||
pub struct PresenterOptions {
|
||||
pub mode: PresentMode,
|
||||
pub builder_options: PresentationBuilderOptions,
|
||||
pub font_size_fallback: Option<u8>,
|
||||
pub font_size_fallback: u8,
|
||||
pub bindings: KeyBindingsConfig,
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ pub(crate) type RenderResult = Result<(), RenderError>;
|
||||
/// Allows drawing elements in the terminal.
|
||||
pub(crate) struct TerminalDrawer<W: io::Write> {
|
||||
terminal: Terminal<W>,
|
||||
font_size_fallback: Option<u8>,
|
||||
font_size_fallback: u8,
|
||||
}
|
||||
|
||||
impl<W> TerminalDrawer<W>
|
||||
@ -23,7 +23,7 @@ where
|
||||
W: io::Write,
|
||||
{
|
||||
/// Construct a drawer over a [std::io::Write].
|
||||
pub(crate) fn new(handle: W, image_printer: Rc<ImagePrinter>, font_size_fallback: Option<u8>) -> io::Result<Self> {
|
||||
pub(crate) fn new(handle: W, image_printer: Rc<ImagePrinter>, font_size_fallback: u8) -> io::Result<Self> {
|
||||
let terminal = Terminal::new(handle, image_printer)?;
|
||||
Ok(Self { terminal, font_size_fallback })
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
use crossterm::terminal;
|
||||
use std::io::{self, ErrorKind};
|
||||
|
||||
const DEFAULT_FONT_SIZE_FALLBACK: u8 = 16;
|
||||
|
||||
/// The size of the terminal window.
|
||||
///
|
||||
/// This is the same as [crossterm::terminal::window_size] except with some added functionality,
|
||||
@ -17,7 +15,7 @@ pub(crate) struct WindowSize {
|
||||
|
||||
impl WindowSize {
|
||||
/// Get the current window size.
|
||||
pub(crate) fn current(font_size_fallback: Option<u8>) -> io::Result<Self> {
|
||||
pub(crate) fn current(font_size_fallback: u8) -> io::Result<Self> {
|
||||
let mut size: Self = match terminal::window_size() {
|
||||
Ok(size) => size.into(),
|
||||
Err(e) if e.kind() == ErrorKind::Unsupported => {
|
||||
@ -27,7 +25,7 @@ impl WindowSize {
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
let font_size_fallback = font_size_fallback.unwrap_or(DEFAULT_FONT_SIZE_FALLBACK) as u16;
|
||||
let font_size_fallback = font_size_fallback as u16;
|
||||
if size.width == 0 {
|
||||
size.width = size.columns * font_size_fallback;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user