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
//! Shades of gray; where
//!
//! - zero is black,
//! - sixteen is white, with
//! - fifteen shades of gray in between.

const fn shifted(grey: u8, by: u8) -> [u8; 4] {
    let blue = grey.saturating_add(by);
    [grey, grey, blue, 0xff]
}

/// Color progression is based upon a smoothed (reciprocal) decibel scale.
///
/// | n    | dB      | hex  | dec  |
/// | ---- | ------- | ---- | ---- |
/// | 0    | 1.00    | 0x00 | 0    |
/// | 1    | 0.80    | 0x33 | 51   |
/// | 2    | 0.66667 | 0x55 | 85   |
/// | 3    | 0.50    | 0x80 | 128  |
/// | 4    | 0.40    | 0x99 | 153  |
/// | 5    | 0.33333 | 0xAA | 170  |
/// | 6    | 0.25    | 0xBF | 191  |
/// | 7    | 0.20    | 0xCC | 204  |
/// | 8    | 0.16667 | 0xD5 | 213  |
/// | 9    | 0.125   | 0xDF | 223  |
/// | 10   | 0.10    | 0xE6 | 230  |
/// | 11   | 0.08333 | 0xEA | 234  |
/// | 12   | 0.0625  | 0xEF | 239  |
/// | 13   | 0.05    | 0xF2 | 242  |
/// | 14   | 0.04167 | 0xF4 | 244  |
/// | 15   | 0.03125 | 0xF7 | 247  |
const fn decibel(n: u8) -> u8 {
    debug_assert!(n <= 16);

    // we oversize the constant array because `let n = n.clamp(0, 16);` gives us:
    //
    //     error[E0015]: cannot call non-const fn `<u8 as Ord>::clamp` in constant functions
    //
    const GRAY: [u8; 256] = [
        0x00, // 255 - (10^(-n ÷ 10) × 255)
        0x33, 0x55, 0x80, 0x99, 0xaa, 0xbf, 0xcc, 0xd5, 0xdf, 0xe6, 0xea, 0xef, 0xf2, 0xf4, 0xf7,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    ];

    GRAY[n as usize]
}

/// Gray wih a noticable blue tint
pub const fn background(brightness: u8) -> [u8; 4] {
    shifted(decibel(brightness), 5)
}

/// Gray with a subtle blue tint
pub const fn foreground(brightness: u8) -> [u8; 4] {
    shifted(decibel(brightness), 2)
}

/// Gray with no tint; pure grays
pub const fn pure(brightness: u8) -> [u8; 4] {
    shifted(decibel(brightness), 0)
}