Skip to content

Commit

Permalink
Add optional support for ansi_term crate
Browse files Browse the repository at this point in the history
  • Loading branch information
mina86 committed Dec 11, 2022
1 parent 3535831 commit 3ef2983
Show file tree
Hide file tree
Showing 5 changed files with 391 additions and 90 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ rust:
- stable
matrix:
fast_finish: true
script:
- cargo test --verbose
- cargo test --verbose --all-features
- cargo test --verbose --no-default-features
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ documentation = "https://docs.rs/ansi_colours"
exclude = ["tools/**"]
edition = "2018"

[package.metadata.docs.rs]
all-features = true

[badges]
maintenance = { status = "actively-developed" }

[dependencies]
ansi_term = { version = "0.12", optional = true }
rgb = { version = "0.8", optional = true }

[features]
Expand Down
52 changes: 28 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,25 @@
[![Docs](https://docs.rs/ansi_colours/badge.svg)](https://docs.rs/ansi_colours)
[![License](https://img.shields.io/badge/license-LGPL-blue.svg)](https://github.com/mina86/ansi_colours/blob/master/LICENSE)

`ansi_colours` is a library which converts between 24-bit sRGB colours
and 8-bit colour palette used by ANSI terminals such as xterm on
rxvt-unicode in 256-colour mode.

The most common use case is when using 24-bit colours in a terminal
emulator which only support 8-bit colour palette. This package allows
true-colours to be approximated by values supported by the terminal.

When mapping true-colour into available 256-colour palette (of which
only 240 are actually usable), this package tries to balance accuracy
and performance. It doesn’t implement the fastest algorithm nor is it
the most accurate, instead it uses a formula which should be fast
enough and accurate enough for most use-cases.
`ansi_colours` converts between 24-bit sRGB colours and 8-bit colour
palette used by ANSI terminals such as xterm or rxvt-unicode in
256-colour mode. The most common use case is when using 24-bit
colours in a terminal emulator which only support 8-bit colour
palette. It allows true-colours to be approximated by values
supported by the terminal.

When mapping true-colour into available 256-colour palette, it tries
to balance accuracy and performance. It doesn’t implement the fastest
algorithm nor is it the most accurate, instead it uses a formula which
should be fast enough and accurate enough for most use-cases.

## Usage

This library has C and Rust implementations and can be easily used
from either of those languages (as well as from C++ of course). The
two implementations are equivalent and are provided for best
performance. Since version 1.0.4 the Rust crate has sped up by 25%
when doing True Colour → ANSI index conversion and 75% when doing
conversion in the other direction.
The algorithm has C and Rust implementations and can be easily used
from C, C++ or Rust. The two implementations are equivalent and are
provided for best performance. Since version 1.0.4 the Rust crate has
sped up by 25% when doing True Colour → ANSI index conversion and 75%
when doing conversion in the other direction.

### Rust

Expand Down Expand Up @@ -53,11 +50,18 @@ fn main() {
}
```

To facilitate better interoperability this library supports `rgb::RGB`
from [`rgb` crate](https://crates.io/crates/rgb). The support is
controlled by `rgb` feature. With it enabled (which is the default),
`ansi256_from_rgb` function accepts `RGB<u8>` and `RGB<u16>` values as
arguments (though `rgb_from_ansi256` still returns colours as tuples).
To facilitate better interoperability the crate defines `rgb` crate
feature (enabled by default). It adds support for the `RGB` type from
[`rgb` crate](https://crates.io/crates/rgb). Specifically, `RGB8`
(a.k.a. `RGB<u8>`) as well as `RGB16` (a.k.a. `RGB<u16>`) types are
supported.

Furthermore, `ansi_term` cargo features is also available which adds
support for `Colour` type from [`ansi_term`
crate](https://crates.io/crates/ansi_term). This includes support for
calling `ansi256_from_rgb` with a `Colour` argument and implementation
of `ColourExt` trait which extends `Colour` with additional conversion
methods.

### C and C++

Expand Down
235 changes: 235 additions & 0 deletions src/impls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
use crate::*;

/// Representation of an RGB colour as 24-bit `0xRRGGBB` integer.
impl AsRGB for u32 {
fn as_u32(&self) -> u32 { *self }
}

#[inline]
fn to_u32(r: u8, g: u8, b: u8) -> u32 {
((r as u32) << 16) | ((g as u32) << 8) | (b as u32)
}

impl AsRGB for (u8, u8, u8) {
#[inline]
fn as_u32(&self) -> u32 { to_u32(self.0, self.1, self.2) }
}

impl AsRGB for [u8; 3] {
#[inline]
fn as_u32(&self) -> u32 { to_u32(self[0], self[1], self[2]) }
}

#[cfg(feature = "rgb")]
impl AsRGB for rgb::RGB<u8> {
/// Returns representation of the sRGB colour as a 24-bit `0xRRGGBB`
/// integer.
///
/// This implementation is present only if `rgb` crate feature is enabled.
///
/// # Examples
///
/// ```
/// use ansi_colours::{AsRGB, ansi256_from_rgb};
///
/// assert_eq!(0x123456, rgb::RGB8::new(0x12, 0x34, 0x56).as_u32());
///
/// assert_eq!( 16, ansi256_from_rgb(rgb::RGB8::new( 1, 1, 1)));
/// assert_eq!( 16, ansi256_from_rgb(rgb::RGB8::new( 0, 1, 2)));
/// assert_eq!( 67, ansi256_from_rgb(rgb::RGB8::new( 95, 135, 175)));
/// assert_eq!(231, ansi256_from_rgb(rgb::RGB8::new(255, 255, 255)));
/// ```
#[inline]
fn as_u32(&self) -> u32 { to_u32(self.r, self.g, self.b) }
}

#[cfg(feature = "rgb")]
impl AsRGB for rgb::RGB<u16> {
/// Returns representation of the sRGB colour as a 24-bit `0xRRGGBB`
/// integer.
///
/// In current implementation, when converting, the eight least significant
/// bits of each components are ignored.
///
/// This implementation is present only if `rgb` crate feature is enabled.
///
/// # Examples
///
/// ```
/// use ansi_colours::{AsRGB, ansi256_from_rgb};
///
/// assert_eq!(0x123456, rgb::RGB8::new(0x12, 0x34, 0x56).as_u32());
///
/// assert_eq!( 16, ansi256_from_rgb(rgb::RGB16::new( 256, 511, 256)));
/// assert_eq!( 16, ansi256_from_rgb(rgb::RGB16::new( 128, 256, 512)));
/// assert_eq!( 67, ansi256_from_rgb(rgb::RGB16::new(24500, 34600, 44800)));
/// assert_eq!(231, ansi256_from_rgb(rgb::RGB16::new(65535, 65535, 65535)));
/// ```
#[inline]
fn as_u32(&self) -> u32 {
to_u32(
(self.r >> 8) as u8,
(self.g >> 8) as u8,
(self.b >> 8) as u8,
)
}
}

impl<'a, T: AsRGB + ?Sized> AsRGB for &'a T {
fn as_u32(&self) -> u32 { (*self).as_u32() }
}

#[cfg(feature = "ansi_term")]
impl AsRGB for ansi_term::Colour {
/// Returns sRGB colour corresponding to escape code represented by
/// [`ansi_term::Colour`].
///
/// Behaves slightly differently depending on the variant of the enum.
/// - For named colour variants (`Black`, `Red` etc. up till `White`),
/// returns corresponding system colour with indexes going from 0 to 7.
/// - Similarly, for `Fixed` variant returns colour corresponding to
/// specified index. See [`rgb_from_ansi256`](`rgb_from_ansi256`).
/// - Lastly, for `RGB` variant converts it to 24-bit `0xRRGGBB`
/// representation.
#[inline]
fn as_u32(&self) -> u32 {
match self.clone() {
Self::Black => ansi256::ANSI_COLOURS[0],
Self::Red => ansi256::ANSI_COLOURS[1],
Self::Green => ansi256::ANSI_COLOURS[2],
Self::Yellow => ansi256::ANSI_COLOURS[3],
Self::Blue => ansi256::ANSI_COLOURS[4],
Self::Purple => ansi256::ANSI_COLOURS[5],
Self::Cyan => ansi256::ANSI_COLOURS[6],
Self::White => ansi256::ANSI_COLOURS[7],
Self::Fixed(idx) => ansi256::ANSI_COLOURS[idx as usize],
Self::RGB(r, g, b) => (r, g, b).as_u32(),
}
}

/// Returns index of a colour in 256-colour ANSI palette approximating given
/// sRGB colour.
///
/// Behaves slightly differently depending on the variant of the enum.
/// - For named colour variants (`Black`, `Red` etc. up till `White`),
/// returns index going from 0 to 7.
/// - For `Fixed` variant simply returns index encoded in the variant.
/// - Lastly, for `RGB` variant, approximates the colour and returns index
/// of closest colour in 256-colour palette.
///
///
/// # Examples
///
/// ```
/// use ansi_colours::AsRGB;
///
/// assert_eq!( 0, ansi_term::Colour::Black.to_ansi256());
/// assert_eq!( 7, ansi_term::Colour::White.to_ansi256());
/// assert_eq!( 42, ansi_term::Colour::Fixed(42).to_ansi256());
/// assert_eq!( 16, ansi_term::Colour::RGB( 0, 0, 0).to_ansi256());
/// assert_eq!( 16, ansi_term::Colour::RGB( 1, 1, 1).to_ansi256());
/// assert_eq!( 16, ansi_term::Colour::RGB( 0, 1, 2).to_ansi256());
/// assert_eq!( 67, ansi_term::Colour::RGB( 95, 135, 175).to_ansi256());
/// assert_eq!(231, ansi_term::Colour::RGB(255, 255, 255).to_ansi256());
/// ```
#[inline]
fn to_ansi256(&self) -> u8 {
match self.clone() {
Self::Black => 0,
Self::Red => 1,
Self::Green => 2,
Self::Yellow => 3,
Self::Blue => 4,
Self::Purple => 5,
Self::Cyan => 6,
Self::White => 7,
Self::Fixed(idx) => idx,
Self::RGB(r, g, b) => (r, g, b).to_ansi256(),
}
}
}

#[cfg(feature = "ansi_term")]
impl super::ColourExt for ansi_term::Colour {
/// Constructs a `Fixed` colour which approximates given sRGB colour.
///
/// # Examples
///
/// ```
/// use ansi_colours::ColourExt;
/// use ansi_term::Colour;
///
/// assert_eq!(Colour::Fixed( 16), Colour::approx_rgb( 0, 0, 0));
/// assert_eq!(Colour::Fixed( 16), Colour::approx_rgb( 0, 1, 2));
/// assert_eq!(Colour::Fixed( 67), Colour::approx_rgb( 95, 135, 175));
/// assert_eq!(Colour::Fixed(231), Colour::approx_rgb(255, 255, 255));
/// ```
#[inline]
fn approx_rgb(r: u8, g: u8, b: u8) -> Self {
Self::Fixed(ansi256_from_rgb((r, g, b)))
}

/// Converts the colour into 256-colour-compatible format.
///
/// If the colour represents an RGB colour, converts it into a `Fixed`
/// variant using [`ansi256_from_rgb`] function. Otherwise, returns the
/// colour unchanged.
///
/// # Examples
///
/// ```
/// use ansi_colours::ColourExt;
/// use ansi_term::Colour;
///
/// assert_eq!(Colour::Red, Colour::Red.to_256());
/// assert_eq!(Colour::Fixed( 11), Colour::Fixed(11).to_256());
/// assert_eq!(Colour::Fixed( 16), Colour::RGB( 0, 0, 0).to_256());
/// assert_eq!(Colour::Fixed( 16), Colour::RGB( 0, 1, 2).to_256());
/// assert_eq!(Colour::Fixed( 67), Colour::RGB( 95, 135, 175).to_256());
/// assert_eq!(Colour::Fixed(231), Colour::RGB(255, 255, 255).to_256());
/// ```
#[inline]
fn to_256(&self) -> Self {
if let Self::RGB(r, g, b) = self {
Self::Fixed(ansi256_from_rgb((*r, *g, *b)))
} else {
*self
}
}

/// Converts the colour into sRGB.
///
/// Named colours (`Black`, `Red` etc. through `White`) are treated like
/// `Fixed` colours with indexes 0 through 7. `Fixed` colours are converted
/// into sRGB using [`rgb_from_ansi256`] function. `RGB` colours are
/// returned unchanged.
///
/// # Examples
///
/// ```
/// use ansi_colours::ColourExt;
/// use ansi_term::Colour;
///
/// assert_eq!(( 0, 0, 0), Colour::Fixed( 16).to_rgb());
/// assert_eq!(( 95, 135, 175), Colour::Fixed( 67).to_rgb());
/// assert_eq!((255, 255, 255), Colour::Fixed(231).to_rgb());
/// assert_eq!((238, 238, 238), Colour::Fixed(255).to_rgb());
/// assert_eq!(( 42, 24, 0), Colour::RGB(42, 24, 0).to_rgb());
/// ```
#[inline]
fn to_rgb(&self) -> (u8, u8, u8) {
let idx = match self.clone() {
Self::Black => 0,
Self::Red => 1,
Self::Green => 2,
Self::Yellow => 3,
Self::Blue => 4,
Self::Purple => 5,
Self::Cyan => 6,
Self::White => 7,
Self::Fixed(idx) => idx,
Self::RGB(r, g, b) => return (r, g, b),
};
rgb_from_ansi256(idx)
}
}
Loading

0 comments on commit 3ef2983

Please sign in to comment.