Skip to main content

a7n_platform/feedback/
palette.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3//! The fixed feedback palette, shared by the renderer and GUI swatches.
4
5use super::Color;
6
7struct Entry {
8    color: Color,
9    key: &'static str,
10    name: &'static str,
11    stops: &'static [[u8; 3]],
12}
13
14const PALETTE: &[Entry] = &[
15    Entry {
16        color: Color::Accent,
17        key: "accent",
18        name: "Accent",
19        stops: &[[0x2f, 0x5b, 0xea]],
20    },
21    Entry {
22        color: Color::Green,
23        key: "green",
24        name: "Green",
25        stops: &[[0x43, 0xc7, 0x7f]],
26    },
27    Entry {
28        color: Color::Orange,
29        key: "orange",
30        name: "Orange",
31        stops: &[[0xe9, 0xa2, 0x3b]],
32    },
33    Entry {
34        color: Color::Pink,
35        key: "pink",
36        name: "Pink",
37        stops: &[[0xed, 0x78, 0xb5]],
38    },
39    Entry {
40        color: Color::Ibtesam,
41        key: "ibtesam",
42        name: "Ibtesam",
43        stops: &[[0x00, 0xf5, 0xa0], [0x00, 0xd9, 0xf5]],
44    },
45    Entry {
46        color: Color::Lunada,
47        key: "lunada",
48        name: "Lunada",
49        stops: &[[0x54, 0x33, 0xff], [0x20, 0xbd, 0xff], [0xa5, 0xfe, 0xcb]],
50    },
51    Entry {
52        color: Color::Stripe,
53        key: "stripe",
54        name: "Stripe",
55        stops: &[[0x1f, 0xa2, 0xff], [0x12, 0xd8, 0xfa], [0xa6, 0xff, 0xcb]],
56    },
57    Entry {
58        color: Color::Ali,
59        key: "ali",
60        name: "Ali",
61        stops: &[[0xff, 0x4b, 0x1f], [0x1f, 0xdd, 0xff]],
62    },
63    Entry {
64        color: Color::Alihossein,
65        key: "alihossein",
66        name: "Alihossein",
67        stops: &[[0xf7, 0xff, 0x00], [0xdb, 0x36, 0xa4]],
68    },
69    Entry {
70        color: Color::Transfile,
71        key: "transfile",
72        name: "Transfile",
73        stops: &[[0x16, 0xbf, 0xfd], [0xcb, 0x30, 0x66]],
74    },
75    Entry {
76        color: Color::ElectricViolet,
77        key: "electric_violet",
78        name: "Electric Violet",
79        stops: &[[0x47, 0x76, 0xe6], [0x8e, 0x54, 0xe9]],
80    },
81    Entry {
82        color: Color::Martini,
83        key: "martini",
84        name: "Martini",
85        stops: &[[0xfd, 0xfc, 0x47], [0x24, 0xfe, 0x41]],
86    },
87    Entry {
88        color: Color::Superman,
89        key: "superman",
90        name: "Superman",
91        stops: &[[0x00, 0x99, 0xf7], [0xf1, 0x17, 0x12]],
92    },
93    Entry {
94        color: Color::EdsSunsetGradient,
95        key: "eds_sunset_gradient",
96        name: "Ed's Sunset Gradient",
97        stops: &[[0xff, 0x7e, 0x5f], [0xfe, 0xb4, 0x7b]],
98    },
99    Entry {
100        color: Color::SublimeLight,
101        key: "sublime_light",
102        name: "Sublime Light",
103        stops: &[[0xfc, 0x5c, 0x7d], [0x6a, 0x82, 0xfb]],
104    },
105    Entry {
106        color: Color::Argon,
107        key: "argon",
108        name: "Argon",
109        stops: &[
110            [0x03, 0x00, 0x1e],
111            [0x73, 0x03, 0xc0],
112            [0xec, 0x38, 0xbc],
113            [0xfd, 0xef, 0xf9],
114        ],
115    },
116    Entry {
117        color: Color::JShine,
118        key: "j_shine",
119        name: "JShine",
120        stops: &[[0x12, 0xc2, 0xe9], [0xc4, 0x71, 0xed], [0xf6, 0x4f, 0x59]],
121    },
122    Entry {
123        color: Color::Flare,
124        key: "flare",
125        name: "Flare",
126        stops: &[[0xff, 0xf3, 0xb0], [0xf5, 0xaf, 0x19], [0xf1, 0x27, 0x11]],
127    },
128    Entry {
129        color: Color::UltraVoilet,
130        key: "ultra_voilet",
131        name: "Ultra Voilet",
132        stops: &[[0x65, 0x4e, 0xa3], [0xea, 0xaf, 0xc8]],
133    },
134    Entry {
135        color: Color::KyooPal,
136        key: "kyoo_pal",
137        name: "Kyoo Pal",
138        stops: &[[0xdd, 0x3e, 0x54], [0x6b, 0xe5, 0x85]],
139    },
140    Entry {
141        color: Color::KyeMeh,
142        key: "kye_meh",
143        name: "Kye Meh",
144        stops: &[[0x83, 0x60, 0xc3], [0x2e, 0xbf, 0x91]],
145    },
146    Entry {
147        color: Color::Wiretap,
148        key: "wiretap",
149        name: "Wiretap",
150        stops: &[[0x8a, 0x23, 0x87], [0xe9, 0x40, 0x57], [0xf2, 0x71, 0x21]],
151    },
152    Entry {
153        color: Color::Rastafari,
154        key: "rastafari",
155        name: "Rastafari",
156        stops: &[[0x1e, 0x96, 0x00], [0xff, 0xf2, 0x00], [0xff, 0x00, 0x00]],
157    },
158    Entry {
159        color: Color::SinCityRed,
160        key: "sin_city_red",
161        name: "Sin City Red",
162        stops: &[[0xed, 0x21, 0x3a], [0x93, 0x29, 0x1e]],
163    },
164    Entry {
165        color: Color::AzurLane,
166        key: "azur_lane",
167        name: "Azur Lane",
168        stops: &[[0x7f, 0x7f, 0xd5], [0x86, 0xa8, 0xe7], [0x91, 0xea, 0xe4]],
169    },
170    Entry {
171        color: Color::WitchingHour,
172        key: "witching_hour",
173        name: "Witching Hour",
174        stops: &[[0xc3, 0x14, 0x32], [0x24, 0x0b, 0x36]],
175    },
176];
177
178impl Color {
179    /// All supported colours in presentation order, with stable config values and names.
180    pub fn choices() -> impl Iterator<Item = (Self, &'static str, &'static str)> {
181        PALETTE
182            .iter()
183            .map(|entry| (entry.color, entry.key, entry.name))
184    }
185
186    /// Samples evenly spaced colour stops. Values outside the edge clamp to its ends.
187    #[must_use]
188    // Stop indexes and interpolated channels are bounded before conversion.
189    #[allow(
190        clippy::cast_possible_truncation,
191        clippy::cast_sign_loss,
192        clippy::cast_precision_loss
193    )]
194    pub fn sample(self, t: f32, dark: bool) -> [u8; 3] {
195        if self == Self::Accent && dark {
196            return [0x70, 0x88, 0xff];
197        }
198        let stops = PALETTE[self as usize].stops;
199        let position = if t.is_nan() { 0.0 } else { t.clamp(0.0, 1.0) } * (stops.len() - 1) as f32;
200        let index = (position as usize).min(stops.len() - 1);
201        let next = (index + 1).min(stops.len() - 1);
202        let fraction = position - index as f32;
203        std::array::from_fn(|channel| {
204            let start = f32::from(stops[index][channel]);
205            (start + (f32::from(stops[next][channel]) - start) * fraction).round() as u8
206        })
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn sample_multi_stop_gradient_returns_endpoints_and_interpolation() {
216        // Arrange
217        let color = Color::Lunada;
218
219        // Act
220        let samples = [0.0, 0.25, 0.5, 0.75, 1.0].map(|t| color.sample(t, false));
221
222        // Assert
223        assert_eq!(
224            samples,
225            [
226                [0x54, 0x33, 0xff],
227                [0x3a, 0x78, 0xff],
228                [0x20, 0xbd, 0xff],
229                [0x63, 0xde, 0xe5],
230                [0xa5, 0xfe, 0xcb]
231            ]
232        );
233    }
234}