andiskaz/color/
eight_bit.rs

1//! This module provides 8-bit color utilies.
2
3use crate::color::{
4    brightness::{Channel, ChannelVector},
5    ApproxBrightness,
6    BadCmyColor,
7    BadGrayColor,
8    BasicColor,
9    Brightness,
10};
11use crossterm::style::Color as CrosstermColor;
12use std::{convert::TryFrom, fmt, ops::Not};
13
14/// A CMY (Cyan-Magenta-Yellow) color. The lower one of its component is, the
15/// more it subtracts.
16#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
17pub struct CmyColor {
18    /// `(0 .. 216)` Color code.
19    code: u8,
20}
21
22impl CmyColor {
23    /// Base of CMY colors (6).
24    pub const BASE: u8 = 6;
25
26    /// Creates a new `CmyColor` given its components. Returns an error if any
27    /// of the components is `>= `[`Self::BASE`].
28    pub fn try_new(
29        cyan: u8,
30        magenta: u8,
31        yellow: u8,
32    ) -> Result<Self, BadCmyColor> {
33        if cyan >= Self::BASE || magenta >= Self::BASE || yellow >= Self::BASE {
34            Err(BadCmyColor { cyan, magenta, yellow })
35        } else {
36            Ok(Self {
37                code: cyan * Self::BASE.pow(2) + magenta * Self::BASE + yellow,
38            })
39        }
40    }
41
42    /// Creates a new `CmyColor` given its components.
43    ///
44    /// # Panics
45    /// Panics if any of the components is `>= `[`Self::BASE`].
46    pub fn new(cyan: u8, magenta: u8, yellow: u8) -> Self {
47        Self::try_new(cyan, magenta, yellow).expect("Bad Cmy Color")
48    }
49
50    /// The level of cyan component.
51    pub const fn cyan(self) -> u8 {
52        self.code / Self::BASE / Self::BASE % Self::BASE
53    }
54
55    /// The level of magenta component.
56    pub const fn magenta(self) -> u8 {
57        self.code / Self::BASE % Self::BASE
58    }
59
60    /// The level of yellow component.
61    pub const fn yellow(self) -> u8 {
62        self.code % Self::BASE
63    }
64
65    /// The resulting code of the color.
66    pub const fn code(self) -> u8 {
67        self.code
68    }
69
70    /// Sets the cyan component.
71    ///
72    /// # Panics
73    /// Panics if the component is `>= `[`Self::BASE`].
74    pub fn set_cyan(self, cyan: u8) -> Self {
75        Self::new(cyan, self.magenta(), self.yellow())
76    }
77
78    /// Sets the magenta component.
79    ///
80    /// # Panics
81    /// Panics if the component is `>= `[`Self::BASE`].
82    pub fn set_magenta(self, magenta: u8) -> Self {
83        Self::new(self.cyan(), magenta, self.yellow())
84    }
85
86    /// Sets the yellow component.
87    ///
88    /// # Panics
89    /// Panics if the component is `>= `[`Self::BASE`].
90    pub fn set_yellow(self, yellow: u8) -> Self {
91        Self::new(self.cyan(), self.magenta(), yellow)
92    }
93
94    /// Creates a CMY color from the given channels.
95    fn from_channels(channels: [Channel; 3]) -> Self {
96        Self::new(channels[0].value(), channels[1].value(), channels[2].value())
97    }
98
99    /// Returns a CMY color's channels.
100    fn channels(self) -> [Channel; 3] {
101        [
102            Channel::new(self.cyan(), 30),
103            Channel::new(self.magenta(), 59),
104            Channel::new(self.yellow(), 11),
105        ]
106    }
107}
108
109impl fmt::Debug for CmyColor {
110    fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result {
111        fmtr.debug_struct("CmyColor")
112            .field("cyan", &self.cyan())
113            .field("magenta", &self.magenta())
114            .field("yellow", &self.yellow())
115            .finish()
116    }
117}
118
119impl Not for CmyColor {
120    type Output = Self;
121
122    fn not(self) -> Self::Output {
123        Self::new(
124            Self::BASE - self.cyan(),
125            Self::BASE - self.magenta(),
126            Self::BASE - self.yellow(),
127        )
128    }
129}
130
131impl ApproxBrightness for CmyColor {
132    fn approx_brightness(&self) -> Brightness {
133        let mut channels = self.channels();
134        let vector = ChannelVector::new(&mut channels, Self::BASE - 1);
135        vector.approx_brightness()
136    }
137
138    fn set_approx_brightness(&mut self, brightness: Brightness) {
139        let mut channels = self.channels();
140        let mut vector = ChannelVector::new(&mut channels, Self::BASE - 1);
141        vector.set_approx_brightness(brightness);
142        *self = Self::from_channels(channels);
143    }
144}
145
146/// A gray-scale color. Goes from white, to gray, to black.
147#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
148pub struct GrayColor {
149    /// Level of white.
150    brightness: u8,
151}
152
153impl GrayColor {
154    /// Minimum gray-scale brightness (0, black).
155    pub const MIN: Self = Self { brightness: 0 };
156    /// Half of maximum gray-scale brightness (gray).
157    pub const HALF: Self = Self { brightness: 12 };
158    /// Maximum gray-scale brightness (white).
159    pub const MAX: Self = Self { brightness: 23 };
160
161    /// Creates a new gray-scale color given its brightness. Returns an error if
162    /// `brightness > MAX`.
163    pub fn try_new(brightness: u8) -> Result<Self, BadGrayColor> {
164        if brightness > Self::MAX.brightness() {
165            Err(BadGrayColor { brightness })
166        } else {
167            Ok(Self { brightness })
168        }
169    }
170
171    /// Creates a new gray-scale color given its brightness.
172    ///
173    /// # Panics
174    /// Panics if `brightness > MAX`.
175    pub fn new(brightness: u8) -> Self {
176        Self::try_new(brightness).expect("Bad gray color")
177    }
178
179    /// Returns the brightness of this color.
180    pub const fn brightness(self) -> u8 {
181        self.brightness
182    }
183}
184
185impl Not for GrayColor {
186    type Output = Self;
187
188    fn not(self) -> Self::Output {
189        Self::new(Self::MAX.brightness() + 1 - self.brightness)
190    }
191}
192
193impl ApproxBrightness for GrayColor {
194    fn approx_brightness(&self) -> Brightness {
195        let brightness = Brightness { level: u16::from(self.brightness) };
196        brightness.spread(u16::from(Self::MAX.brightness))
197    }
198
199    fn set_approx_brightness(&mut self, brightness: Brightness) {
200        let compressed = brightness.compress(u16::from(Self::MAX.brightness));
201        let res = u8::try_from(compressed.level);
202        self.brightness = res.expect("Color brightness bug");
203    }
204}
205
206/// The kind of a color. `enum` representation of an 8-bit color.
207#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
208pub enum Color8BitKind {
209    /// 16 Basic colors.
210    Basic(BasicColor),
211    /// 216 CMY colors.
212    Cmy(CmyColor),
213    /// 24 Gray-scale colors.
214    Gray(GrayColor),
215}
216
217impl Not for Color8BitKind {
218    type Output = Self;
219
220    fn not(self) -> Self::Output {
221        match self {
222            Color8BitKind::Basic(color) => Color8BitKind::Basic(!color),
223            Color8BitKind::Cmy(color) => Color8BitKind::Cmy(!color),
224            Color8BitKind::Gray(color) => Color8BitKind::Gray(!color),
225        }
226    }
227}
228
229impl From<BasicColor> for Color8BitKind {
230    fn from(color: BasicColor) -> Self {
231        Color8BitKind::Basic(color)
232    }
233}
234
235impl From<CmyColor> for Color8BitKind {
236    fn from(color: CmyColor) -> Self {
237        Color8BitKind::Cmy(color)
238    }
239}
240
241impl From<GrayColor> for Color8BitKind {
242    fn from(color: GrayColor) -> Self {
243        Color8BitKind::Gray(color)
244    }
245}
246
247impl From<Color8Bit> for Color8BitKind {
248    fn from(color: Color8Bit) -> Self {
249        color.kind()
250    }
251}
252
253impl ApproxBrightness for Color8BitKind {
254    fn approx_brightness(&self) -> Brightness {
255        match self {
256            Color8BitKind::Basic(color) => color.approx_brightness(),
257            Color8BitKind::Cmy(color) => color.approx_brightness(),
258            Color8BitKind::Gray(color) => color.approx_brightness(),
259        }
260    }
261
262    fn set_approx_brightness(&mut self, brightness: Brightness) {
263        match self {
264            Color8BitKind::Basic(color) => color.set_approx_brightness(brightness),
265            Color8BitKind::Cmy(color) => color.set_approx_brightness(brightness),
266            Color8BitKind::Gray(color) => color.set_approx_brightness(brightness),
267        }
268    }
269}
270
271/// An 8-bit encoded color for the terminal.
272#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
273pub struct Color8Bit {
274    code: u8,
275}
276
277impl Color8Bit {
278    /// Size of basic colors.
279    const BASIC_SIZE: u8 = 16;
280    /// Size of basic colors + CMY colors.
281    const BASIC_CMY_SIZE: u8 =
282        Self::BASIC_SIZE + CmyColor::BASE * CmyColor::BASE * CmyColor::BASE;
283
284    /// Creates a color that is basic.
285    pub const fn basic(color: BasicColor) -> Self {
286        Self { code: color as u8 }
287    }
288
289    /// Creates a color that is CMY.
290    pub const fn cmy(color: CmyColor) -> Self {
291        Self { code: color.code() + Self::BASIC_SIZE }
292    }
293
294    /// Creates a color that is gray-scale.
295    pub const fn gray(color: GrayColor) -> Self {
296        Self { code: color.brightness() + Self::BASIC_CMY_SIZE }
297    }
298
299    /// Returns the color code.
300    pub const fn code(self) -> u8 {
301        self.code
302    }
303
304    /// Converts to en `enum` representation.
305    pub fn kind(self) -> Color8BitKind {
306        if self.code < 16 {
307            Color8BitKind::Basic(BasicColor::try_from(self.code).unwrap())
308        } else if self.code < Self::BASIC_CMY_SIZE {
309            Color8BitKind::Cmy(CmyColor { code: self.code - Self::BASIC_SIZE })
310        } else {
311            Color8BitKind::Gray(GrayColor {
312                brightness: self.code - Self::BASIC_CMY_SIZE,
313            })
314        }
315    }
316
317    /// Translates this color to a crossterm color.
318    pub(crate) fn to_crossterm(self) -> CrosstermColor {
319        CrosstermColor::AnsiValue(self.code())
320    }
321}
322
323impl fmt::Debug for Color8Bit {
324    fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result {
325        fmtr.debug_struct("Color8Bit").field("kind", &self.kind()).finish()
326    }
327}
328
329impl Not for Color8Bit {
330    type Output = Self;
331
332    fn not(self) -> Self::Output {
333        Self::from(!self.kind())
334    }
335}
336
337impl From<BasicColor> for Color8Bit {
338    fn from(color: BasicColor) -> Self {
339        Self::basic(color)
340    }
341}
342
343impl From<CmyColor> for Color8Bit {
344    fn from(color: CmyColor) -> Self {
345        Self::cmy(color)
346    }
347}
348
349impl From<GrayColor> for Color8Bit {
350    fn from(color: GrayColor) -> Self {
351        Self::gray(color)
352    }
353}
354
355impl From<Color8BitKind> for Color8Bit {
356    fn from(kind: Color8BitKind) -> Self {
357        match kind {
358            Color8BitKind::Basic(color) => Self::from(color),
359            Color8BitKind::Cmy(color) => Self::from(color),
360            Color8BitKind::Gray(color) => Self::from(color),
361        }
362    }
363}
364
365impl ApproxBrightness for Color8Bit {
366    fn approx_brightness(&self) -> Brightness {
367        self.kind().approx_brightness()
368    }
369
370    fn set_approx_brightness(&mut self, brightness: Brightness) {
371        *self = Self::from(self.kind().with_approx_brightness(brightness));
372    }
373}