atanunq / viuer

Rust library for displaying images in the terminal.
MIT License
241 stars 43 forks source link

Do not stretch image in terminals supporting graphics protocol #45

Open DPDmancul opened 1 year ago

DPDmancul commented 1 year ago

It would be useful a feature (maybe activable with an option) to not stretch image on terminals supporting the image protocol.

For example the following code works for kitty:

diff --git a/Cargo.toml b/Cargo.toml
index 72bfd35..58da970 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -22,6 +22,7 @@ tempfile = "3.1"
 console = { version = "0.15", default-features = false }
 lazy_static = "1.4"
 sixel-sys = { version = "0.3.1", optional = true }
+nix = {version = "0.25.0", default-feature = false, features = [ "ioctl" ]}

 # avoid feature and crate name collision (thanks rabite0/hunter)
 [dependencies.sixel-rs]
diff --git a/src/printer/kitty.rs b/src/printer/kitty.rs
index b950254..a2e2cfe 100644
--- a/src/printer/kitty.rs
+++ b/src/printer/kitty.rs
@@ -122,22 +122,48 @@ fn print_local(
     img: &image::DynamicImage,
     config: &Config,
 ) -> ViuResult<(u32, u32)> {
-    let rgba = img.to_rgba8();
-    let raw_img = rgba.as_raw();
-    let path = store_in_tmp_file(raw_img)?;
-
     adjust_offset(stdout, config)?;

     // get the desired width and height
     let (w, h) = find_best_fit(img, config.width, config.height);

+    use nix::ioctl_read_bad;
+    use nix::libc::{c_ushort, TIOCGWINSZ};
+
+    #[repr(C)]
+   #[derive(Default, Debug)]
+   pub struct TermSize {
+       rows: c_ushort,
+       columns: c_ushort,
+       x: c_ushort,
+       y: c_ushort,
+   }
+   
+   ioctl_read_bad!(
+       /// Get terminal window size
+       tiocgwinsz,
+       TIOCGWINSZ,
+       TermSize
+   );
+
+    let mut tsize = TermSize::default();
+    unsafe { tiocgwinsz(0, &mut tsize as *mut _).expect("Cannot retrive kitty size") };
+
+    let img = img.resize(
+        (w as f64 * tsize.x as f64 / tsize.columns as f64) as u32,
+        (h as f64 * tsize.y as f64 / tsize.rows as f64) as u32,
+        image::imageops::FilterType::Triangle
+    );
+
+    let rgba = img.to_rgba8();
+    let raw_img = rgba.as_raw();
+    let path = store_in_tmp_file(raw_img)?;
+
     write!(
         stdout,
-        "\x1b_Gf=32,s={},v={},c={},r={},a=T,t=t;{}\x1b\\",
+        "\x1b_Gf=32,s={},v={},a=T,t=t;{}\x1b\\",
         img.width(),
         img.height(),
-        w,
-        h,
         base64::encode(path.to_str().ok_or_else(|| ViuError::Io(Error::new(
             ErrorKind::Other,
             "Could not convert path to &str"

Instead of telling kitty to stretch the image with the parameters c and r (number of columns and rows)of its graphics protocol, this code resizes the image (retaining aspect ratio) in a way it fits the desired number of rows and columns (bound width and height are computed multiplying the number of columns/rows by the ratio of size in pixel to the number of cells).

I attach an example to understand the importance of this feature: on the left the output of viu in kitty, on the right the output of the following code with the previous patch:

use viuer::{Config, print_from_file};

fn main() {
    let conf = Config {
        absolute_offset: false,
        ..Default::default()
    };
    print_from_file("sway.png", &conf).expect("Image printing failed.");
}

image

It can be seen how without this feature the image is unnaturally stretched; this is required for block printer, but easily avoidable for other printers.

Maybe there are also solutions better than the one I suggested.