Skip to content

Commit

Permalink
Added support for proper alpha blending, limited & updated dependenci…
Browse files Browse the repository at this point in the history
…es (#59)

* Bumped deps, fixes for image 0.25, better resizing filter.

* Support for proper alpha blending.

* Added support for premultiplied alpha blending.

* Removed unused depedencies from image crate by disabling default-features, added missing doc (tests were broken because of deny(missing_docs)

* Removed default features from crossterm dep.

---------

Co-authored-by: atanunq <[email protected]>
  • Loading branch information
virtualritz and atanunq authored Oct 13, 2024
1 parent 7c74a5a commit 834da5f
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 43 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
24 changes: 16 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
47 changes: 27 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 7 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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,
Expand Down
65 changes: 55 additions & 10 deletions src/printer/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -152,24 +152,69 @@ fn is_pixel_transparent(pixel: (u32, u32, &Rgba<u8>)) -> 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 {
Color::Ansi256(ansi256_from_rgb(rgb))
}
}

fn get_color_from_pixel(pixel: (u32, u32, &Rgba<u8>), 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<u8>), 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))
Expand Down
2 changes: 1 addition & 1 deletion src/printer/iterm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions src/printer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -95,7 +95,7 @@ pub fn resize(img: &DynamicImage, width: Option<u32>, height: Option<u32>) -> Dy
img.resize_exact(
w,
2 * h - img.height() % 2,
image::imageops::FilterType::Triangle,
image::imageops::FilterType::CatmullRom,
)
}

Expand Down
2 changes: 1 addition & 1 deletion src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 834da5f

Please sign in to comment.