diff --git a/CHANGELOG.md b/CHANGELOG.md index 43d6b87..61fc4ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## Unreleased +- Remove unneeded features and update dependencies +- Use Catmull-Rom for up/downscaling +- Add `premultiplied_alpha` Config option + ## 0.7.1 - Bump `base64` and `crossterm` dependencies diff --git a/Cargo.toml b/Cargo.toml index ebea519..fb47f8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,19 +13,27 @@ keywords = ["terminal", "image"] exclude = [".github"] [dependencies] -termcolor = "1.1" -crossterm = "0.27" -ansi_colours = "1.0" -image = "0.24" -base64 = "0.21.4" -tempfile = "3.1" +ansi_colours = "1" +base64 = "0.22" console = { version = "0.15", default-features = false } -lazy_static = "1.4" +crossterm = { version = "0.28", default-features = false } +image = { version = "0.25", default-features = false, features = [ + "rayon", + "png", +] } +lazy_static = "1.5" +tempfile = "3" +termcolor = "1" [dependencies.sixel-rs] -version = "0.3.3" +version = "0.3" optional = true +[target.'cfg(windows)'.dependencies] +crossterm = { version = "0.28", default-features = false, features = [ + "windows", +] } + [features] default = [] sixel = ["sixel-rs"] diff --git a/README.md b/README.md index 7a171e3..5e1c45d 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,66 @@ -# viuer -Display images in the terminal with ease. +# `viuer` ![ci](https://github.com/atanunq/viuer/workflows/ci/badge.svg) -`viuer` is a Rust library that makes it easy to show images in the -terminal. It has a straightforward interface and is configured -through a single struct. The default printing method is through -lower half blocks (▄ or \u2585). However some custom graphics -protocols are supported. They result in full resolution images -being displayed in specific environments: +Display images in the terminal with ease. + +`viuer` is a Rust library that makes it easy to show images in the terminal. +It has a straightforward interface and is configured through a single struct. +The default printing method is through lower half blocks (`▄` or `\u2585`). +However some custom graphics protocols are supported. They result in full +resolution images being displayed in specific environments: - [Kitty](https://sw.kovidgoyal.net/kitty/graphics-protocol.html) - [iTerm](https://iterm2.com/documentation-images.html) -- [Sixel](https://github.com/saitoha/libsixel) (behind the "sixel" feature gate) +- [Sixel](https://github.com/saitoha/libsixel) (behind the `sixel` + feature gate) ## Usage + Add this to `Cargo.toml`: + ```toml [dependencies] -viuer = "0.6" +viuer = "0.8" ``` -For a demo of the library's usage and example screenshots, see [`viu`](https://github.com/atanunq/viu). +For a demo of the library's usage and example screenshots, see +[`viu`](https://github.com/atanunq/viu). ## Examples ```rust -// src/main.rs use viuer::{print_from_file, Config}; fn main() { let conf = Config { - // set offset + // Set offset. x: 20, y: 4, - // set dimensions + // Set dimensions. width: Some(80), height: Some(25), ..Default::default() }; - // starting from row 4 and column 20, - // display `img.jpg` with dimensions 80x25 (in terminal cells) - // note that the actual resolution in the terminal will be 80x50 + // Starting from row 4 and column 20, + // display `img.jpg` with dimensions 80×25 (in terminal cells). + // Note that the actual resolution in the terminal will be 80×50. print_from_file("img.jpg", &conf).expect("Image printing failed."); } ``` -Or if you have a [DynamicImage](https://docs.rs/image/*/image/enum.DynamicImage.html), you can use it directly: +Or if you have a [DynamicImage](https://docs.rs/image/*/image/enum.DynamicImage.html), +you can use it directly: + ```rust -// ..Config setup +// ... `Config` setup let img = image::DynamicImage::ImageRgba8(image::RgbaImage::new(20, 10)); viuer::print(&img, &conf).expect("Image printing failed."); ``` ## Docs -Check the [full documentation](https://docs.rs/crate/viuer) for examples and all the configuration options. + +Check the [full documentation](https://docs.rs/crate/viuer) for examples and all +the configuration options. diff --git a/src/config.rs b/src/config.rs index 961e8cc..0454a78 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,13 +5,18 @@ pub struct Config { /// Enable true transparency instead of checkerboard background. /// Available only for the block printer. Defaults to false. pub transparent: bool, + /// If we assume the alpha channel is premultiplied for blending with the + /// checkerboard background. + /// Defaults to false. + pub premultiplied_alpha: bool, /// Make the x and y offset be relative to the top left terminal corner. /// If false, the y offset is relative to the cursor's position. /// Defaults to true. pub absolute_offset: bool, /// X offset. Defaults to 0. pub x: u16, - /// Y offset. Can be negative only when `absolute_offset` is `false`. Defaults to 0. + /// Y offset. Can be negative only when `absolute_offset` is `false`. + /// Defaults to 0. pub y: i16, /// Take a note of cursor position before printing and restore it when finished. /// Defaults to false. @@ -35,6 +40,7 @@ impl std::default::Default for Config { fn default() -> Self { Self { transparent: false, + premultiplied_alpha: false, absolute_offset: true, x: 0, y: 0, diff --git a/src/printer/block.rs b/src/printer/block.rs index d68e0ee..0783425 100644 --- a/src/printer/block.rs +++ b/src/printer/block.rs @@ -61,10 +61,10 @@ fn print_to_writecolor( if config.transparent { None } else { - Some(get_transparency_color(curr_row, pixel.0, config.truecolor)) + Some(transparency_color(curr_row, pixel.0, config.truecolor)) } } else { - Some(get_color_from_pixel(pixel, config.truecolor)) + Some(color_from_pixel(curr_row, pixel, config)) }; // Even rows modify the background, odd rows the foreground @@ -152,13 +152,19 @@ fn is_pixel_transparent(pixel: (u32, u32, &Rgba)) -> bool { pixel.2[3] == 0 } -fn get_transparency_color(row: u32, col: u32, truecolor: bool) -> Color { - //imitate the transparent chess board pattern - let rgb = if row % 2 == col % 2 { +#[inline(always)] +fn checkerboard(row: u32, col: u32) -> (u8, u8, u8) { + if row % 2 == col % 2 { CHECKERBOARD_BACKGROUND_DARK } else { CHECKERBOARD_BACKGROUND_LIGHT - }; + } +} + +#[inline(always)] +fn transparency_color(row: u32, col: u32, truecolor: bool) -> Color { + //imitate the transparent chess board pattern + let rgb = checkerboard(row, col); if truecolor { Color::Rgb(rgb.0, rgb.1, rgb.2) } else { @@ -166,10 +172,49 @@ fn get_transparency_color(row: u32, col: u32, truecolor: bool) -> Color { } } -fn get_color_from_pixel(pixel: (u32, u32, &Rgba), truecolor: bool) -> Color { - let (_x, _y, data) = pixel; - let rgb = (data[0], data[1], data[2]); - if truecolor { +/// Composes the foreground over the background. +/// +/// This assumes unpremultiplied alpha. +#[inline(always)] +fn over(fg: u8, bg: u8, alpha: u8) -> u8 { + ((fg as u16 * alpha as u16 + bg as u16 * (255u16 - alpha as u16)) / 255) as _ +} + +/// Composes the foreground over the background. +/// +/// This assumes premultiplied alpha (standard Porter-Duff compositing). +#[inline(always)] +fn over_porter_duff(fg: u8, bg: u8, alpha: u8) -> u8 { + ((fg as u16 + bg as u16 * (255u16 - alpha as u16)) / 255) as _ +} + +#[inline(always)] +fn color_from_pixel(row: u32, pixel: (u32, u32, &Rgba), config: &Config) -> Color { + let (col, _y, color) = pixel; + let alpha = color[3]; + + let rgb = if !config.transparent && alpha < 255 { + // We need to blend the pixel's color with the checkerboard pattern. + let checker = checkerboard(row, col); + + if config.premultiplied_alpha { + ( + over_porter_duff(color[0], checker.0, alpha), + over_porter_duff(color[1], checker.1, alpha), + over_porter_duff(color[2], checker.2, alpha), + ) + } else { + ( + over(color[0], checker.0, alpha), + over(color[1], checker.1, alpha), + over(color[2], checker.2, alpha), + ) + } + } else { + (color[0], color[1], color[2]) + }; + + if config.truecolor { Color::Rgb(rgb.0, rgb.1, rgb.2) } else { Color::Ansi256(ansi256_from_rgb(rgb)) diff --git a/src/printer/iterm.rs b/src/printer/iterm.rs index b6263ee..c691c29 100644 --- a/src/printer/iterm.rs +++ b/src/printer/iterm.rs @@ -36,7 +36,7 @@ impl Printer for iTermPrinter { img.as_bytes(), width, height, - img.color(), + img.color().into(), )?; print_buffer(stdout, img, &png_bytes[..], config) diff --git a/src/printer/mod.rs b/src/printer/mod.rs index 76586fd..1e3ca7b 100644 --- a/src/printer/mod.rs +++ b/src/printer/mod.rs @@ -36,7 +36,7 @@ pub trait Printer { filename: P, config: &Config, ) -> ViuResult<(u32, u32)> { - let img = image::io::Reader::open(filename)? + let img = image::ImageReader::open(filename)? .with_guessed_format()? .decode()?; self.print(stdout, &img, config) @@ -95,7 +95,7 @@ pub fn resize(img: &DynamicImage, width: Option, height: Option) -> Dy img.resize_exact( w, 2 * h - img.height() % 2, - image::imageops::FilterType::Triangle, + image::imageops::FilterType::CatmullRom, ) } diff --git a/src/utils.rs b/src/utils.rs index 2e087f0..e08ae4a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -40,7 +40,7 @@ pub fn terminal_size() -> (u16, u16) { } } -// Return a constant when running the tests +/// Returns a constant and only used when running the tests. #[cfg(test)] pub fn terminal_size() -> (u16, u16) { DEFAULT_TERM_SIZE