Skip to content
This repository has been archived by the owner on Jan 10, 2025. It is now read-only.

Commit

Permalink
Downscale images to column width for kitty
Browse files Browse the repository at this point in the history
Instead of scaling down to window width scale down to the number of
allowed columns.  This makes sure the image fits to the surrounding text
if --columns passed is much smaller than the window size.
  • Loading branch information
swsnr committed Apr 21, 2023
1 parent 994d900 commit e871be7
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 53 deletions.
2 changes: 1 addition & 1 deletion pulldown-cmark-mdcat/src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,7 @@ pub fn write_event<'a, W: Write>(
let image_state = match (settings.terminal_capabilities.image, resolved_link) {
(Some(capability), Some(ref url)) => capability
.image_protocol()
.write_inline_image(writer, &resource_handler, url, &settings.terminal_size)
.write_inline_image(writer, &resource_handler, url, settings.terminal_size)
.map_err(|error| {
event!(Level::ERROR, ?error, %url, "failed to render image with capability {:?}: {:#}", capability, error);
error
Expand Down
2 changes: 1 addition & 1 deletion pulldown-cmark-mdcat/src/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use mime::Mime;
use url::Url;

mod file;
mod image;
pub(crate) mod image;

pub(crate) mod svg;

Expand Down
45 changes: 44 additions & 1 deletion pulldown-cmark-mdcat/src/resources/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,49 @@ pub trait InlineImageProtocol {
writer: &mut dyn Write,
resource_handler: &dyn ResourceUrlHandler,
url: &Url,
terminal_size: &TerminalSize,
terminal_size: TerminalSize,
) -> std::io::Result<()>;
}

/// Downsize an image to the given terminal size.
///
/// If the `image` is larger than the amount of columns in the given terminal `size` attempt to
/// downsize the image to fit into the given columns.
///
/// Return the downsized image if `image` was larger than the column limit and `size` defined the
/// terminal size in pixels.
///
/// Return `None` if `size` does not specify the cell size, or if `image` is already smaller than
/// the column limit.
#[cfg(feature = "image-processing")]
pub fn downsize_to_columns(
image: &image::DynamicImage,
size: TerminalSize,
) -> Option<image::DynamicImage> {
use image::{imageops::FilterType, GenericImageView};
use tracing::{event, Level};
let win_size = size.pixels?;
event!(
Level::DEBUG,
"Terminal size {:?}; image is {:?}",
size,
image.dimensions()
);
let (image_width, _) = image.dimensions();
if win_size.x < image_width {
Some(image.resize(win_size.x, win_size.y, FilterType::Nearest))
} else {
None
}
}

/// Downsize to columns, dummy implementation.
///
/// This implementation always returns `None` because image processing is disabled.
#[cfg(not(feature = "image-processing"))]
pub fn downsize_to_columns(
_image: &image::DynamicImage,
_size: TerminalSize,
) -> Option<image::DynamicImage> {
None
}
2 changes: 1 addition & 1 deletion pulldown-cmark-mdcat/src/terminal/capabilities/iterm2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl InlineImageProtocol for ITerm2 {
writer: &mut dyn Write,
resource_handler: &dyn ResourceUrlHandler,
url: &url::Url,
_terminal_size: &crate::TerminalSize,
_terminal_size: crate::TerminalSize,
) -> Result<()> {
let mime_data = resource_handler.read_resource(url)?;
event!(
Expand Down
76 changes: 28 additions & 48 deletions pulldown-cmark-mdcat/src/terminal/capabilities/kitty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ use base64::Engine;
use thiserror::Error;
use tracing::{event, instrument, Level};

use crate::resources::{InlineImageProtocol, MimeData};
use crate::resources::image::*;
use crate::resources::MimeData;
use crate::terminal::size::PixelSize;

/// An error which occurred while rendering or writing an image with the Kitty image protocol.
Expand Down Expand Up @@ -125,9 +126,10 @@ impl KittyImages {
fn render(
self,
mime_data: MimeData,
terminal_size: PixelSize,
terminal_size: crate::TerminalSize,
) -> Result<KittyImage, KittyImageError> {
use image::{GenericImageView, ImageFormat};
use image::ImageFormat;

let image = if let Some("image/svg+xml") = mime_data.mime_type_essence() {
event!(Level::DEBUG, "Rendering mime data to SVG");
let png_data = crate::resources::svg::render_svg_to_png(&mime_data.data)?;
Expand All @@ -146,27 +148,25 @@ impl KittyImages {
}
};

if mime_data.mime_type == Some(mime::IMAGE_PNG)
&& PixelSize::from_xy(image.dimensions()) <= terminal_size
{
event!(
Level::DEBUG,
"PNG image of appropriate size, rendering original data"
);
// If we know that the original data is in PNG format and of sufficient size we can
// discard the decoded image and instead render the original data directly.
//
// We kinda wasted the decoded image here (we do need it to check dimensions tho) but
// at least we don't have to encode it again.
Ok(self.render_as_png(mime_data.data))
} else {
event!(
Level::DEBUG,
"Image of other format or larger than terminal, rendering RGB data"
);
// The original data was not in PNG format, or we have to resize the image to terminal
// dimensions, so we need to encode the RGB data of the decoded image explicitly.
Ok(self.render_as_rgb_or_rgba(image, terminal_size))
match downsize_to_columns(&image, terminal_size) {
Some(downsized_image) => {
event!(
Level::DEBUG,
"Image scaled down to column limit, rendering RGB data"
);
Ok(self.render_as_rgb_or_rgba(downsized_image))
}
None if mime_data.mime_type_essence() == Some("image/png") => {
event!(
Level::DEBUG,
"PNG image of appropriate size, rendering original image data"
);
Ok(self.render_as_png(mime_data.data))
}
None => {
event!(Level::DEBUG, "Image not in PNG format, rendering RGB data");
Ok(self.render_as_rgb_or_rgba(image))
}
}
}

Expand Down Expand Up @@ -213,13 +213,9 @@ impl KittyImages {
///
/// If the image size exceeds `terminal_size` in either dimension scale the
/// image down to `terminal_size` (preserving aspect ratio).
#[cfg(feature = "image-processing")]
fn render_as_rgb_or_rgba(
self,
image: image::DynamicImage,
terminal_size: PixelSize,
) -> KittyImage {
fn render_as_rgb_or_rgba(self, image: image::DynamicImage) -> KittyImage {
use image::{ColorType, GenericImageView};

let format = match image.color() {
ColorType::L8 | ColorType::Rgb8 | ColorType::L16 | ColorType::Rgb16 => KittyFormat::Rgb,
// Default to RGBA format: We cannot match all colour types because
Expand All @@ -229,16 +225,6 @@ impl KittyImages {
_ => KittyFormat::Rgba,
};

let image = if PixelSize::from_xy(image.dimensions()) <= terminal_size {
image
} else {
image.resize(
terminal_size.x,
terminal_size.y,
image::imageops::FilterType::Nearest,
)
};

let size = PixelSize::from_xy(image.dimensions());

KittyImage {
Expand Down Expand Up @@ -295,21 +281,15 @@ impl InlineImageProtocol for KittyImages {
writer: &mut dyn Write,
resource_handler: &dyn crate::ResourceUrlHandler,
url: &url::Url,
terminal_size: &crate::TerminalSize,
terminal_size: crate::TerminalSize,
) -> std::io::Result<()> {
let pixel_size = terminal_size.pixels.ok_or_else(|| {
Error::new(
ErrorKind::InvalidData,
"kitty did not report pixel size, cannot write image",
)
})?;
let mime_data = resource_handler.read_resource(url)?;
event!(
Level::DEBUG,
"Received data of mime type {:?}",
mime_data.mime_type
);
let image = self.render(mime_data, pixel_size)?;
let image = self.render(mime_data, terminal_size)?;
image.write_to(writer)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ impl InlineImageProtocol for Terminology {
writer: &mut dyn Write,
_resource_handler: &dyn ResourceUrlHandler,
url: &Url,
terminal_size: &TerminalSize,
terminal_size: TerminalSize,
) -> Result<()> {
let columns = terminal_size.columns;
let lines = match get_image_dimensions(url) {
Expand Down

0 comments on commit e871be7

Please sign in to comment.