Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ Options:
Resize the image to a provided width
-h, --height <height>
Resize the image to a provided height
-o, --original
Display static images at original pixel size on Kitty/Ghostty and iTerm backends
-x <x>
X offset [default: 0]
-y <y>
Expand Down
83 changes: 80 additions & 3 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::config::Config;
use crossterm::terminal::{Clear, ClearType};
use crossterm::terminal::{window_size, Clear, ClearType, WindowSize};
use crossterm::{cursor, execute};
use image::{codecs::gif::GifDecoder, AnimationDecoder, DynamicImage};
use std::fs;
Expand Down Expand Up @@ -55,7 +55,7 @@ pub fn run(mut conf: Config) -> ViuResult {
//If stdin data is not a gif, treat it as a regular image

let img = image::load_from_memory(&buf)?;
viuer::print(&img, &conf.viuer_config)?;
print_static_image(&conf, &img)?;
};

Ok(())
Expand Down Expand Up @@ -137,7 +137,7 @@ fn view_file(conf: &Config, filename: &str, (tx, rx): TxRx) -> ViuResult {
let result = try_print_gif(conf, BufReader::new(file_in), (tx, rx));
//the provided image is not a gif so try to view it
if result.is_err() {
viuer::print_from_file(filename, &conf.viuer_config)?;
print_static_image_from_file(conf, filename)?;
}
}
if conf.caption {
Expand All @@ -147,6 +147,58 @@ fn view_file(conf: &Config, filename: &str, (tx, rx): TxRx) -> ViuResult {
Ok(())
}

fn is_pixel_backend_active(conf: &Config) -> bool {
(conf.viuer_config.use_iterm && viuer::is_iterm_supported())
|| (conf.viuer_config.use_kitty && viuer::get_kitty_support() != viuer::KittySupport::None)
}

fn original_size_in_cells_for_window(
img_width: u32,
img_height: u32,
terminal: &WindowSize,
) -> Option<(u32, u32)> {
if terminal.width == 0 || terminal.height == 0 || terminal.columns == 0 || terminal.rows == 0 {
return None;
}

let width = ((img_width as u64 * terminal.columns as u64) + (terminal.width as u64 / 2))
/ terminal.width as u64;
let height = ((img_height as u64 * terminal.rows as u64) + (terminal.height as u64 / 2))
/ terminal.height as u64;

Some((u32::max(1, width as u32), u32::max(1, height as u32)))
}

fn original_size_in_cells(img: &DynamicImage) -> Option<(u32, u32)> {
let terminal = window_size().ok()?;
original_size_in_cells_for_window(img.width(), img.height(), &terminal)
}

fn print_static_image(conf: &Config, img: &DynamicImage) -> ViuResult {
if conf.original && is_pixel_backend_active(conf) {
let mut viuer_config = conf.viuer_config.clone();
if let Some((width, height)) = original_size_in_cells(img) {
viuer_config.width = Some(width);
viuer_config.height = Some(height);
}
let _ = viuer::print(img, &viuer_config)?;
} else {
let _ = viuer::print(img, &conf.viuer_config)?;
}

Ok(())
}

fn print_static_image_from_file(conf: &Config, filename: &str) -> ViuResult {
if conf.original && is_pixel_backend_active(conf) {
let img = image::open(filename)?;
print_static_image(conf, &img)
} else {
let _ = viuer::print_from_file(filename, &conf.viuer_config)?;
Ok(())
}
}

fn try_print_gif<R>(conf: &Config, input_stream: R, (tx, rx): TxRx) -> ViuResult
where
R: Read + BufRead + Seek,
Expand Down Expand Up @@ -232,4 +284,29 @@ mod test {
let (tx, rx) = mpsc::channel();
view_file(&conf, "img/bfa", (&tx, &rx)).unwrap();
}

#[test]
fn test_original_size_in_cells_for_window() {
let terminal = WindowSize {
columns: 120,
rows: 30,
width: 960,
height: 480,
};
assert_eq!(
original_size_in_cells_for_window(64, 64, &terminal),
Some((8, 4))
);
}

#[test]
fn test_original_size_in_cells_for_window_with_missing_pixels() {
let terminal = WindowSize {
columns: 120,
rows: 30,
width: 0,
height: 0,
};
assert_eq!(original_size_in_cells_for_window(64, 64, &terminal), None);
}
}
4 changes: 4 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub struct Config<'a> {
pub caption: bool,
pub recursive: bool,
pub static_gif: bool,
pub original: bool,
pub viuer_config: ViuerConfig,
pub frame_duration: Option<Duration>,
}
Expand Down Expand Up @@ -58,6 +59,7 @@ impl<'a> Config<'a> {

let once = matches.get_flag("once");
let static_gif = matches.get_flag("static");
let original = matches.get_flag("original");
let loop_gif = files.len() <= 1 && !once;

Config {
Expand All @@ -67,6 +69,7 @@ impl<'a> Config<'a> {
caption: matches.get_flag("caption"),
recursive: matches.get_flag("recursive"),
static_gif,
original,
viuer_config,
frame_duration,
}
Expand All @@ -80,6 +83,7 @@ impl<'a> Config<'a> {
caption: false,
recursive: false,
static_gif: false,
original: false,
viuer_config: ViuerConfig {
absolute_offset: false,
use_kitty: false,
Expand Down
8 changes: 8 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ fn main() {
.value_parser(value_parser!(u32))
.help("Resize the image to a provided height"),
)
.arg(
Arg::new("original")
.short('o')
.long("original")
.action(SetTrue)
.conflicts_with_all(["width", "height"])
.help("Display static images at original pixel size on Kitty/Ghostty and iTerm backends"),
)
.arg(
Arg::new("x")
.short('x')
Expand Down