1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
// ansi_colours – true-colour ↔ ANSI terminal palette converter
// Copyright 2018 by Michał Nazarewicz <mina86@mina86.com>
//
// ansi_colours is free software: you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 3 of the License, or (at
// your option) any later version.
//
// ansi_colours is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
// General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with ansi_colours.  If not, see <http://www.gnu.org/licenses/>.

//! `ansi_colours` 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.  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.
//!
//! ## Cargo features
//!
//! 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` and `termcolor` features are available.  They
//! add support for `Colour` type from [`ansi_term`
//! crate](https://crates.io/crates/ansi_term) and `Color` type from
//! [`termcolor` crate](https://crates.io/crates/termcolor) respectively.
//! This includes support for calling `ansi256_from_rgb` with arguments of
//! those types and implementation of `ColourExt` trait which extends the
//! types with additional conversion methods.
//!
//! ## Usage
//!
//! Using this library with Cargo projects is as simple as adding a single
//! dependency:
//!
//! ```toml
//! [dependencies]
//! ansi_colours = "1.1"
//! ```
//!
//! and then using one of the two functions that the library provides:
//!
//! ```rust
//! use ansi_colours::*;
//!
//! fn main() {
//!     // Colour at given index:
//!     println!("{:-3}: {:?}", 50, rgb_from_ansi256(50));
//!
//!     // Approximate true-colour by colour in the palette:
#![cfg_attr(
    feature = "rgb",
    doc = r#"    let rgb = rgb::RGB8 { r: 100, g: 200, b: 150 };"#
)]
#![cfg_attr(not(feature = "rgb"), doc = r#"    let rgb = (100, 200, 150);"#)]
//!     let index = ansi256_from_rgb(rgb);
//!     println!("{:?} ~ {:-3} {:?}", rgb, index, rgb_from_ansi256(index));
//! }
//! ```

#![no_std]

mod ansi256;
mod impls;
#[cfg(test)]
mod test;

/// Returns sRGB colour corresponding to the index in the 256-colour ANSI
/// palette.
///
/// The first 16 colours (so-called system colours) are not standardised and
/// terminal emulators often allow them to be customised.  Because of this,
/// their value should not be relied upon.  For system colours, this function
/// returns default colours used by XTerm.
///
/// Remaining 240 colours consist of a 6×6×6 colour cube and a 24-step greyscale
/// ramp.  Those are standardised and thus should be the same on every terminal
/// which supports 256-colour colour palette.
///
/// # Examples
///
///
/// ```
/// assert_eq!((  0,   0,   0), ansi_colours::rgb_from_ansi256( 16));
/// assert_eq!(( 95, 135, 175), ansi_colours::rgb_from_ansi256( 67));
/// assert_eq!((255, 255, 255), ansi_colours::rgb_from_ansi256(231));
/// assert_eq!((238, 238, 238), ansi_colours::rgb_from_ansi256(255));
#[cfg_attr(
    feature = "rgb",
    doc = r#"

let rgb = rgb::RGB8::from(ansi_colours::rgb_from_ansi256(128));
assert_eq!(rgb::RGB8 { r: 175, g: 0, b: 215 }, rgb);

let grey = rgb::alt::Gray::<u8>(128);
assert_eq!(244, ansi_colours::ansi256_from_rgb(grey));
"#
)]
/// ```
#[inline]
pub fn rgb_from_ansi256(idx: u8) -> (u8, u8, u8) {
    let rgb = ansi256::ANSI_COLOURS[idx as usize];
    ((rgb >> 16) as u8, (rgb >> 8) as u8, rgb as u8)
}

/// Returns index of a colour in 256-colour ANSI palette approximating given
/// sRGB colour.
///
/// Because the first 16 colours of the palette are not standardised and usually
/// user-configurable, the function usually ignores them.
///
/// The first argument uses [`AsRGB`] trait so that the function can be called in
/// multiple ways using different representations of RGB colours such as
/// `0xRRGGBB` integer, `(r, g, b)` tuple or `[r, g, b]` array.  Calling the
/// function is equivalent to calling [`AsRGB::to_ansi256`] method.
///
/// # Examples
///
///
/// ```
/// assert_eq!( 16, ansi_colours::ansi256_from_rgb(0x000000));
/// assert_eq!( 16, ansi_colours::ansi256_from_rgb( (  1,   1,   1)));
/// assert_eq!( 16, ansi_colours::ansi256_from_rgb( [  0,   1,   2]));
/// assert_eq!( 67, ansi_colours::ansi256_from_rgb(&( 95, 135, 175)));
/// assert_eq!(231, ansi_colours::ansi256_from_rgb(&[255, 255, 255]));
#[cfg_attr(
    feature = "rgb",
    doc = r#"

let rgb = rgb::RGB8 { r: 175, g: 0, b: 215 };
assert_eq!(128, ansi_colours::ansi256_from_rgb(rgb));
let bgr = rgb::RGB8 { b: 215, g: 0, r: 175 };
assert_eq!(128, ansi_colours::ansi256_from_rgb(bgr));

let grey = rgb::alt::Gray::<u8>(128);
assert_eq!(244, ansi_colours::ansi256_from_rgb(grey));
"#
)]
/// ```
#[inline]
pub fn ansi256_from_rgb<C: AsRGB>(rgb: C) -> u8 { rgb.to_ansi256() }

/// Returns index of a colour in 256-colour ANSI palette approximating given
/// shade of grey.
///
/// This gives the same results as `ansi256_from_rgb((component, component,
/// component))` but is faster.  Provided that the `rgb` crate feature is
/// enabled, it is equivalent (in behaviour and performance) to
/// `ansi256_from_rgb(rgb::alt::Grey(component))`.
///
/// # Examples
///
///
/// ```
/// assert_eq!( 16, ansi_colours::ansi256_from_grey(0));
/// assert_eq!( 16, ansi_colours::ansi256_from_grey(1));
/// assert_eq!(231, ansi_colours::ansi256_from_grey(255));
#[cfg_attr(
    feature = "rgb",
    doc = r#"

let grey = rgb::alt::Gray::<u8>(128);
assert_eq!(244, ansi_colours::ansi256_from_grey(*grey));
assert_eq!(244, ansi_colours::ansi256_from_rgb(grey));
"#
)]
/// ```
#[inline]
pub fn ansi256_from_grey(component: u8) -> u8 {
    ansi256::ANSI256_FROM_GREY[component as usize]
}

/// Type which (can) represent an sRGB colour.  Used to provide overloaded
/// versions of `ansi256_from_rgb` function.
pub trait AsRGB {
    /// Returns representation of the sRGB colour as a 24-bit `0xRRGGBB`
    /// integer.
    fn as_u32(&self) -> u32;

    /// Returns index of a colour in 256-colour ANSI palette approximating given
    /// sRGB colour.
    ///
    /// This is provided by default and uses [`Self::as_u32`] to determine
    /// 24-bit sRGB representation of the colour.
    ///
    /// An implementation should provide its own definition if it can offer
    /// a more direct approximation.  For example, if `Self` represents shades
    /// of grey, it’s faster to use [`ansi256_from_grey`] than relay on `to_u32`
    /// conversion; or if it represents a variant which can store index in the
    /// palette or an RGB colour, it’s better to either return the index or
    /// perform approximation depending on the variant.
    #[inline]
    fn to_ansi256(&self) -> u8 {
        crate::ansi256::ansi256_from_rgb(self.as_u32())
    }
}

/// Extension to types representing ANSI colours adding methods converting
/// between RGB and indexed (a.k.a. fixed) representations.
pub trait ColourExt: Sized {
    /// Constructs an indexed colour which approximates given sRGB colour.
    ///
    /// # Examples
    ///
    #[cfg_attr(feature = "ansi_term", doc = "```")]
    #[cfg_attr(not(feature = "ansi_term"), doc = "```ignore")]
    /// 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));
    /// ```
    ///
    /// Note that the example requires `ansi_term` cargo feature to be enabled.
    fn approx_rgb(r: u8, g: u8, b: u8) -> Self;

    /// Constructs an indexed colour which approximates given sRGB colour.
    ///
    /// Behaves like [`approx_rgb`](`Self::approx_rgb`) but takes a single
    /// argument which implements [`AsRGB`].  Note that types which implement
    /// `ColourExt` typically also implement `AsRGB` which means this method can
    /// be called with `Self` argument.  It’s usually better to call
    /// [`to_256`](`ColourExt::to_256`) instead.
    #[inline]
    fn approx<C: AsRGB>(rgb: C) -> Self {
        let rgb = rgb.as_u32();
        Self::approx_rgb((rgb >> 16) as u8, (rgb >> 8) as u8, rgb as u8)
    }

    /// Converts the colour into 256-colour-compatible format.
    ///
    /// If the colour represents an RGB colour, converts it into indexed
    /// representation using [`ansi256_from_rgb`] function.  Otherwise, returns
    /// the colour unchanged.
    ///
    /// # Examples
    ///
    #[cfg_attr(feature = "ansi_term", doc = "```")]
    #[cfg_attr(not(feature = "ansi_term"), doc = "```ignore")]
    /// 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());
    /// ```
    ///
    /// Note that the example requires `ansi_term` cargo feature to be enabled.
    fn to_256(&self) -> Self;

    /// Converts the colour colour into sRGB.
    ///
    /// Named colours (black, red etc. through white) are treated like indexed
    /// colours with indexes 0 through 7.  Indexed colours are converted into
    /// sRGB using [`rgb_from_ansi256`] function.  RGB colours are returned
    /// unchanged.
    ///
    /// # Examples
    ///
    #[cfg_attr(feature = "ansi_term", doc = "```")]
    #[cfg_attr(not(feature = "ansi_term"), doc = "```ignore")]
    /// 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());
    /// ```
    ///
    /// Note that the example requires `ansi_term` cargo feature to be enabled.
    fn to_rgb(&self) -> (u8, u8, u8);
}