1pub const NES_PALETTE: [[u8; 3]; 64] = [
14 [0x6A, 0x6D, 0x6A],
15 [0x00, 0x13, 0x80],
16 [0x1E, 0x00, 0x8A],
17 [0x39, 0x00, 0x7A],
18 [0x55, 0x00, 0x56],
19 [0x5A, 0x00, 0x18],
20 [0x4F, 0x10, 0x00],
21 [0x3D, 0x1C, 0x00],
22 [0x25, 0x3A, 0x00],
23 [0x00, 0x4E, 0x00],
24 [0x00, 0x46, 0x00],
25 [0x00, 0x4A, 0x18],
26 [0x00, 0x40, 0x5A],
27 [0x00, 0x00, 0x00],
28 [0x00, 0x00, 0x00],
29 [0x00, 0x00, 0x00],
30 [0xB9, 0xBC, 0xB9],
31 [0x18, 0x4B, 0xCF],
32 [0x4B, 0x24, 0xE8],
33 [0x7C, 0x12, 0xE0],
34 [0xAB, 0x13, 0xB5],
35 [0xB7, 0x21, 0x64],
36 [0xAB, 0x37, 0x18],
37 [0x8B, 0x5A, 0x00],
38 [0x5C, 0x7A, 0x00],
39 [0x20, 0x90, 0x00],
40 [0x00, 0x8F, 0x00],
41 [0x00, 0x8C, 0x42],
42 [0x00, 0x7D, 0x8A],
43 [0x00, 0x00, 0x00],
44 [0x00, 0x00, 0x00],
45 [0x00, 0x00, 0x00],
46 [0xFF, 0xFF, 0xFF],
47 [0x64, 0xA0, 0xFF],
48 [0x84, 0x79, 0xFF],
49 [0xAC, 0x68, 0xFF],
50 [0xDA, 0x60, 0xFF],
51 [0xE2, 0x6B, 0xC5],
52 [0xDC, 0x83, 0x4C],
53 [0xC3, 0x9A, 0x18],
54 [0x9C, 0xB0, 0x00],
55 [0x60, 0xC0, 0x00],
56 [0x30, 0xC8, 0x3C],
57 [0x28, 0xC5, 0x8C],
58 [0x3C, 0xB7, 0xC9],
59 [0x4C, 0x4C, 0x4C],
60 [0x00, 0x00, 0x00],
61 [0x00, 0x00, 0x00],
62 [0xFF, 0xFF, 0xFF],
63 [0xC8, 0xDD, 0xFF],
64 [0xD5, 0xCC, 0xFF],
65 [0xE5, 0xC7, 0xFF],
66 [0xF5, 0xC5, 0xFF],
67 [0xFA, 0xC9, 0xE6],
68 [0xF8, 0xD2, 0xBD],
69 [0xEF, 0xDA, 0x99],
70 [0xE1, 0xE1, 0x88],
71 [0xC8, 0xE7, 0x88],
72 [0xB0, 0xEA, 0x9C],
73 [0xA4, 0xEB, 0xBD],
74 [0xAA, 0xE5, 0xE2],
75 [0xB0, 0xB0, 0xB0],
76 [0x00, 0x00, 0x00],
77 [0x00, 0x00, 0x00],
78];
79
80#[must_use]
82pub const fn nes_color_to_rgba(idx: u8) -> [u8; 4] {
83 let rgb = NES_PALETTE[(idx & 0x3F) as usize];
84 [rgb[0], rgb[1], rgb[2], 0xFF]
85}
86
87#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Default)]
100pub enum PpuPalette {
101 #[default]
103 Composite2C02,
104 Rgb2C03,
107 Rgb2C04_0001,
109 Rgb2C04_0002,
111 Rgb2C04_0003,
113 Rgb2C04_0004,
115 Rgb2C05,
118}
119
120impl PpuPalette {
121 const fn rgb_table(self) -> Option<&'static [[u8; 3]; 64]> {
124 match self {
125 Self::Composite2C02 => None,
126 Self::Rgb2C03 | Self::Rgb2C05 => Some(&RGB_2C03),
128 Self::Rgb2C04_0001 => Some(&RGB_2C04_0001),
129 Self::Rgb2C04_0002 => Some(&RGB_2C04_0002),
130 Self::Rgb2C04_0003 => Some(&RGB_2C04_0003),
131 Self::Rgb2C04_0004 => Some(&RGB_2C04_0004),
132 }
133 }
134
135 #[must_use]
137 pub const fn is_composite(self) -> bool {
138 matches!(self, Self::Composite2C02)
139 }
140}
141
142#[must_use]
156pub const fn palette_color_to_rgba(
157 palette: PpuPalette,
158 idx: u8,
159 emph_red: bool,
160 emph_green: bool,
161 emph_blue: bool,
162) -> [u8; 4] {
163 match palette.rgb_table() {
164 None => {
165 let rgba = nes_color_to_rgba(idx);
167 apply_documented_emphasis(rgba, idx, emph_red, emph_green, emph_blue)
168 }
169 Some(table) => {
170 let rgb = table[(idx & 0x3F) as usize];
171 let mut out = [rgb[0], rgb[1], rgb[2], 0xFF];
172 if emph_red {
174 out[0] = 0xFF;
175 }
176 if emph_green {
177 out[1] = 0xFF;
178 }
179 if emph_blue {
180 out[2] = 0xFF;
181 }
182 out
183 }
184 }
185}
186
187#[must_use]
195pub const fn build_rgba_lut(palette: PpuPalette) -> [[u8; 4]; 512] {
196 let mut lut = [[0u8; 4]; 512];
197 let mut e = 0usize;
198 while e < 8 {
199 let mut c = 0usize;
200 while c < 64 {
201 #[allow(clippy::cast_possible_truncation)] let idx = c as u8;
203 lut[(e << 6) | c] =
204 palette_color_to_rgba(palette, idx, e & 1 != 0, e & 2 != 0, e & 4 != 0);
205 c += 1;
206 }
207 e += 1;
208 }
209 lut
210}
211
212#[must_use]
224pub const fn build_rgba_lut_from_base(base: &[[u8; 3]; 64]) -> [[u8; 4]; 512] {
225 let mut lut = [[0u8; 4]; 512];
226 let mut e = 0usize;
227 while e < 8 {
228 let mut c = 0usize;
229 while c < 64 {
230 let rgb = base[c];
231 #[allow(clippy::cast_possible_truncation)] let idx = c as u8;
233 lut[(e << 6) | c] = apply_documented_emphasis(
234 [rgb[0], rgb[1], rgb[2], 0xFF],
235 idx,
236 e & 1 != 0,
237 e & 2 != 0,
238 e & 4 != 0,
239 );
240 c += 1;
241 }
242 e += 1;
243 }
244 lut
245}
246
247#[must_use]
253pub const fn apply_documented_emphasis(
254 rgba: [u8; 4],
255 idx: u8,
256 emph_red: bool,
257 emph_green: bool,
258 emph_blue: bool,
259) -> [u8; 4] {
260 let e = (emph_red as usize) | ((emph_green as usize) << 1) | ((emph_blue as usize) << 2);
261 let d = crate::emphasis::EMPHASIS_DELTA[(e << 6) | (idx & 0x3F) as usize];
262 [
263 crate::emphasis::apply_delta(rgba[0], d[0]),
264 crate::emphasis::apply_delta(rgba[1], d[1]),
265 crate::emphasis::apply_delta(rgba[2], d[2]),
266 rgba[3],
267 ]
268}
269
270const RGB_2C03: [[u8; 3]; 64] = [
274 [0x6D, 0x6D, 0x6D], [0x00, 0x24, 0x91], [0x00, 0x00, 0xDA], [0x6D, 0x48, 0xDA], [0x91, 0x00, 0x6D], [0xB6, 0x00, 0x6D], [0xB6, 0x24, 0x00], [0x91, 0x48, 0x00], [0x6D, 0x48, 0x00], [0x24, 0x48, 0x00], [0x00, 0x6D, 0x24], [0x00, 0x91, 0x00], [0x00, 0x48, 0x48], [0x00, 0x00, 0x00], [0x00, 0x00, 0x00], [0x00, 0x00, 0x00], [0xB6, 0xB6, 0xB6], [0x00, 0x6D, 0xDA], [0x00, 0x48, 0xFF], [0x91, 0x00, 0xFF], [0xB6, 0x00, 0xFF], [0xFF, 0x00, 0x91], [0xFF, 0x00, 0x00], [0xDA, 0x6D, 0x00], [0x91, 0x6D, 0x00], [0x24, 0x91, 0x00], [0x00, 0x91, 0x00], [0x00, 0xB6, 0x6D], [0x00, 0x91, 0x91], [0x00, 0x00, 0x00], [0x00, 0x00, 0x00], [0x00, 0x00, 0x00], [0xFF, 0xFF, 0xFF], [0x6D, 0xB6, 0xFF], [0x91, 0x91, 0xFF], [0xDA, 0x6D, 0xFF], [0xFF, 0x00, 0xFF], [0xFF, 0x6D, 0xFF], [0xFF, 0x91, 0x00], [0xFF, 0xB6, 0x00], [0xDA, 0xDA, 0x00], [0x6D, 0xDA, 0x00], [0x00, 0xFF, 0x00], [0x48, 0xFF, 0xDA], [0x00, 0xFF, 0xFF], [0x00, 0x00, 0x00], [0x00, 0x00, 0x00], [0x00, 0x00, 0x00], [0xFF, 0xFF, 0xFF], [0xB6, 0xDA, 0xFF], [0xDA, 0xB6, 0xFF], [0xFF, 0xB6, 0xFF], [0xFF, 0x91, 0xFF], [0xFF, 0xB6, 0xB6], [0xFF, 0xDA, 0x91], [0xFF, 0xFF, 0x48], [0xFF, 0xFF, 0x6D], [0xB6, 0xFF, 0x48], [0x91, 0xFF, 0x6D], [0x48, 0xFF, 0xDA], [0x91, 0xDA, 0xFF], [0x00, 0x00, 0x00], [0x00, 0x00, 0x00], [0x00, 0x00, 0x00], ];
339
340const RGB_2C04_0001: [[u8; 3]; 64] = [
342 [0xFF, 0xB6, 0xB6], [0xDA, 0x6D, 0xFF], [0xFF, 0x00, 0x00], [0x91, 0x91, 0xFF], [0x00, 0x91, 0x91], [0x24, 0x48, 0x00], [0x48, 0x48, 0x48], [0xFF, 0x00, 0x91], [0xFF, 0xFF, 0xFF], [0x6D, 0x6D, 0x6D], [0xFF, 0xB6, 0x00], [0xB6, 0x00, 0x6D], [0x91, 0x00, 0x6D], [0xDA, 0xDA, 0x00], [0x6D, 0x48, 0x00], [0xFF, 0xFF, 0xFF], [0x6D, 0xB6, 0xFF], [0xDA, 0xB6, 0x6D], [0x6D, 0x24, 0x00], [0x6D, 0xDA, 0x00], [0x91, 0xDA, 0xFF], [0xDA, 0xB6, 0xFF], [0xFF, 0xDA, 0x91], [0x00, 0x48, 0xFF], [0xFF, 0xDA, 0x00], [0x48, 0xFF, 0xDA], [0x00, 0x00, 0x00], [0x48, 0x00, 0x00], [0xDA, 0xDA, 0xDA], [0x91, 0x91, 0x91], [0xFF, 0x00, 0xFF], [0x00, 0x24, 0x91], [0x00, 0x00, 0x6D], [0xB6, 0xDA, 0xFF], [0xFF, 0xB6, 0xFF], [0x00, 0xFF, 0x00], [0x00, 0xFF, 0xFF], [0x00, 0x48, 0x48], [0x00, 0xB6, 0x6D], [0xB6, 0x00, 0xFF], [0x00, 0x00, 0x00], [0x91, 0x48, 0x00], [0xFF, 0x91, 0xFF], [0xB6, 0x24, 0x00], [0x91, 0x00, 0xFF], [0x00, 0x00, 0xDA], [0xFF, 0x91, 0x00], [0x00, 0x00, 0x00], [0x00, 0x00, 0x00], [0x24, 0x91, 0x00], [0xB6, 0xB6, 0xB6], [0x00, 0x6D, 0x24], [0xB6, 0xFF, 0x48], [0x6D, 0x48, 0xDA], [0xFF, 0xFF, 0x00], [0xDA, 0x6D, 0x00], [0x00, 0x48, 0x00], [0x00, 0x6D, 0xDA], [0x00, 0x91, 0x00], [0x24, 0x24, 0x24], [0xFF, 0xFF, 0x6D], [0xFF, 0x6D, 0xFF], [0x91, 0x6D, 0x00], [0x91, 0xFF, 0x6D], ];
407
408const RGB_2C04_0002: [[u8; 3]; 64] = [
410 [0x00, 0x00, 0x00], [0xFF, 0xB6, 0x00], [0x91, 0x6D, 0x00], [0xB6, 0xFF, 0x48], [0x91, 0xFF, 0x6D], [0xFF, 0x6D, 0xFF], [0x00, 0x91, 0x91], [0xB6, 0xDA, 0xFF], [0xFF, 0x00, 0x00], [0x91, 0x00, 0xFF], [0xFF, 0xFF, 0x6D], [0xFF, 0x91, 0xFF], [0xFF, 0xFF, 0xFF], [0xDA, 0x6D, 0xFF], [0x91, 0xDA, 0xFF], [0x00, 0x91, 0x00], [0x00, 0x48, 0x00], [0x6D, 0xB6, 0xFF], [0xB6, 0x24, 0x00], [0xDA, 0xDA, 0xDA], [0x00, 0xB6, 0x6D], [0x6D, 0xDA, 0x00], [0x48, 0x00, 0x00], [0x91, 0x91, 0xFF], [0x48, 0x48, 0x48], [0xFF, 0x00, 0xFF], [0x00, 0x00, 0x6D], [0x48, 0xFF, 0xDA], [0xDA, 0xB6, 0xFF], [0x6D, 0x48, 0x00], [0x00, 0x00, 0x00], [0x6D, 0x48, 0xDA], [0x91, 0x00, 0x6D], [0xFF, 0xDA, 0x91], [0xFF, 0x91, 0x00], [0xFF, 0xB6, 0xFF], [0x00, 0x6D, 0xDA], [0x6D, 0x24, 0x00], [0xB6, 0xB6, 0xB6], [0x00, 0x00, 0xDA], [0xB6, 0x00, 0xFF], [0xFF, 0xDA, 0x00], [0x6D, 0x6D, 0x6D], [0x24, 0x48, 0x00], [0x00, 0x48, 0xFF], [0x00, 0x00, 0x00], [0xDA, 0xDA, 0x00], [0xFF, 0xFF, 0xFF], [0xDA, 0xB6, 0x6D], [0x24, 0x24, 0x24], [0x00, 0xFF, 0x00], [0xDA, 0x6D, 0x00], [0x00, 0x48, 0x48], [0x00, 0x24, 0x91], [0xFF, 0x00, 0x91], [0x24, 0x91, 0x00], [0x00, 0x00, 0x00], [0x00, 0xFF, 0xFF], [0x91, 0x48, 0x00], [0xFF, 0xFF, 0x00], [0xFF, 0xB6, 0xB6], [0xB6, 0x00, 0x6D], [0x00, 0x6D, 0x24], [0x91, 0x91, 0x91], ];
475
476const RGB_2C04_0003: [[u8; 3]; 64] = [
478 [0xB6, 0x00, 0xFF], [0xFF, 0x6D, 0xFF], [0x91, 0xFF, 0x6D], [0xB6, 0xB6, 0xB6], [0x00, 0x91, 0x00], [0xFF, 0xFF, 0xFF], [0xB6, 0xDA, 0xFF], [0x24, 0x48, 0x00], [0x00, 0x24, 0x91], [0x00, 0x00, 0x00], [0xFF, 0xDA, 0x91], [0x6D, 0x48, 0x00], [0xFF, 0x00, 0x91], [0xDA, 0xDA, 0xDA], [0xDA, 0xB6, 0x6D], [0x91, 0xDA, 0xFF], [0x91, 0x91, 0xFF], [0x00, 0x91, 0x91], [0xB6, 0x00, 0x6D], [0x00, 0x48, 0xFF], [0x24, 0x91, 0x00], [0x91, 0x6D, 0x00], [0xDA, 0x6D, 0x00], [0x00, 0xB6, 0x6D], [0x6D, 0x6D, 0x6D], [0x6D, 0x48, 0xDA], [0x00, 0x00, 0x00], [0x00, 0x00, 0xDA], [0xFF, 0x00, 0x00], [0xB6, 0x24, 0x00], [0xFF, 0x91, 0xFF], [0xFF, 0xB6, 0xB6], [0xDA, 0x6D, 0xFF], [0x00, 0x48, 0x00], [0x00, 0x00, 0x6D], [0xFF, 0xFF, 0x00], [0x24, 0x24, 0x24], [0xFF, 0xB6, 0x00], [0xFF, 0x91, 0x00], [0xFF, 0xFF, 0xFF], [0x6D, 0xDA, 0x00], [0x91, 0x00, 0x6D], [0x6D, 0xB6, 0xFF], [0xFF, 0x00, 0xFF], [0x00, 0x6D, 0xDA], [0x91, 0x91, 0x91], [0x00, 0x00, 0x00], [0x6D, 0x24, 0x00], [0x00, 0xFF, 0xFF], [0x48, 0x00, 0x00], [0xB6, 0xFF, 0x48], [0xFF, 0xB6, 0xFF], [0x91, 0x48, 0x00], [0x00, 0xFF, 0x00], [0xDA, 0xDA, 0x00], [0x48, 0x48, 0x48], [0x00, 0x6D, 0x24], [0x00, 0x00, 0x00], [0xDA, 0xB6, 0xFF], [0xFF, 0xFF, 0x6D], [0x91, 0x00, 0xFF], [0x48, 0xFF, 0xDA], [0xFF, 0xDA, 0x00], [0x00, 0x48, 0x48], ];
543
544const RGB_2C04_0004: [[u8; 3]; 64] = [
546 [0x91, 0x6D, 0x00], [0x6D, 0x48, 0xDA], [0x00, 0x91, 0x91], [0xDA, 0xDA, 0x00], [0x00, 0x00, 0x00], [0xFF, 0xB6, 0xB6], [0x00, 0x24, 0x91], [0xDA, 0x6D, 0x00], [0xB6, 0xB6, 0xB6], [0x6D, 0x24, 0x00], [0x00, 0xFF, 0x00], [0x00, 0x00, 0x6D], [0xFF, 0xDA, 0x91], [0xFF, 0xFF, 0x00], [0x00, 0x91, 0x00], [0xB6, 0xFF, 0x48], [0xFF, 0x6D, 0xFF], [0x48, 0x00, 0x00], [0x00, 0x48, 0xFF], [0xFF, 0x91, 0xFF], [0x00, 0x00, 0x00], [0x48, 0x48, 0x48], [0xB6, 0x24, 0x00], [0xFF, 0x91, 0x00], [0xDA, 0xB6, 0x6D], [0x00, 0xB6, 0x6D], [0x91, 0x91, 0xFF], [0x24, 0x91, 0x00], [0x91, 0x00, 0x6D], [0x00, 0x00, 0x00], [0x91, 0xFF, 0x6D], [0x6D, 0xB6, 0xFF], [0xB6, 0x00, 0x6D], [0x00, 0x6D, 0x24], [0x91, 0x48, 0x00], [0x00, 0x00, 0xDA], [0x91, 0x00, 0xFF], [0xB6, 0x00, 0xFF], [0x6D, 0x6D, 0x6D], [0xFF, 0x00, 0x91], [0x00, 0x48, 0x48], [0xDA, 0xDA, 0xDA], [0x00, 0x6D, 0xDA], [0x00, 0x48, 0x00], [0x24, 0x24, 0x24], [0xFF, 0xFF, 0x6D], [0x91, 0x91, 0x91], [0xFF, 0x00, 0xFF], [0xFF, 0xB6, 0xFF], [0xFF, 0xFF, 0xFF], [0x6D, 0x48, 0x00], [0xFF, 0x00, 0x00], [0xFF, 0xDA, 0x00], [0x48, 0xFF, 0xDA], [0xFF, 0xFF, 0xFF], [0x91, 0xDA, 0xFF], [0x00, 0x00, 0x00], [0xFF, 0xB6, 0x00], [0xDA, 0x6D, 0xFF], [0xB6, 0xDA, 0xFF], [0x6D, 0xDA, 0x00], [0xDA, 0xB6, 0xFF], [0x00, 0xFF, 0xFF], [0x24, 0x48, 0x00], ];
611
612#[cfg(test)]
613mod tests {
614 use super::*;
615
616 #[test]
617 fn custom_base_lut_matches_builtin_composite() {
618 let from_base = build_rgba_lut_from_base(&NES_PALETTE);
623 let builtin = build_rgba_lut(PpuPalette::Composite2C02);
624 assert_eq!(from_base, builtin);
625 }
626
627 #[test]
628 fn nes_color_indices_round_trip() {
629 let [r, g, b, a] = nes_color_to_rgba(0x0F);
631 assert_eq!(a, 0xFF);
632 assert_eq!((r, g, b), (0, 0, 0));
633 }
634
635 #[test]
636 fn nes_color_index_30_is_white() {
637 let [r, g, b, _] = nes_color_to_rgba(0x30);
638 assert_eq!((r, g, b), (0xFF, 0xFF, 0xFF));
639 }
640
641 #[test]
642 fn emphasis_dims_other_channels() {
643 let base = nes_color_to_rgba(0x30);
644 let red = apply_documented_emphasis(base, 0x30, true, false, false);
645 assert_eq!(red[0], base[0]);
646 assert!(red[1] <= base[1]);
647 assert!(red[2] <= base[2]);
648 }
649
650 #[test]
651 fn composite_palette_is_fbx_plus_the_documented_emphasis() {
652 for idx in 0u8..64 {
656 for emask in 0u8..8 {
657 let (r, g, b) = (emask & 1 != 0, emask & 2 != 0, emask & 4 != 0);
658 let expected = apply_documented_emphasis(nes_color_to_rgba(idx), idx, r, g, b);
659 let routed = palette_color_to_rgba(PpuPalette::Composite2C02, idx, r, g, b);
660 assert_eq!(expected, routed, "idx={idx} emask={emask}");
661 if emask == 0 {
662 assert_eq!(routed, nes_color_to_rgba(idx), "idx={idx}");
663 }
664 }
665 }
666 }
667
668 #[test]
669 fn rgb_2c03_documented_entries() {
670 let none = (false, false, false);
674 assert_eq!(
675 palette_color_to_rgba(PpuPalette::Rgb2C03, 0x00, none.0, none.1, none.2),
676 [0x6D, 0x6D, 0x6D, 0xFF]
677 );
678 assert_eq!(
679 palette_color_to_rgba(PpuPalette::Rgb2C03, 0x01, none.0, none.1, none.2),
680 [0x00, 0x24, 0x91, 0xFF]
681 );
682 assert_eq!(
683 palette_color_to_rgba(PpuPalette::Rgb2C03, 0x0F, none.0, none.1, none.2),
684 [0x00, 0x00, 0x00, 0xFF]
685 );
686 assert_eq!(
687 palette_color_to_rgba(PpuPalette::Rgb2C03, 0x20, none.0, none.1, none.2),
688 [0xFF, 0xFF, 0xFF, 0xFF]
689 );
690 assert_eq!(
691 palette_color_to_rgba(PpuPalette::Rgb2C03, 0x16, none.0, none.1, none.2),
692 [0xFF, 0x00, 0x00, 0xFF]
693 );
694 }
695
696 #[test]
697 fn rgb_2c05_shares_2c03_table() {
698 for idx in 0u8..64 {
699 assert_eq!(
700 palette_color_to_rgba(PpuPalette::Rgb2C05, idx, false, false, false),
701 palette_color_to_rgba(PpuPalette::Rgb2C03, idx, false, false, false),
702 "idx={idx}"
703 );
704 }
705 }
706
707 #[test]
708 fn rgb_2c04_first_entries_match_wiki() {
709 let none = (false, false, false);
710 assert_eq!(
712 palette_color_to_rgba(PpuPalette::Rgb2C04_0001, 0x00, none.0, none.1, none.2),
713 [0xFF, 0xB6, 0xB6, 0xFF] );
715 assert_eq!(
716 palette_color_to_rgba(PpuPalette::Rgb2C04_0002, 0x00, none.0, none.1, none.2),
717 [0x00, 0x00, 0x00, 0xFF] );
719 assert_eq!(
720 palette_color_to_rgba(PpuPalette::Rgb2C04_0003, 0x00, none.0, none.1, none.2),
721 [0xB6, 0x00, 0xFF, 0xFF] );
723 assert_eq!(
724 palette_color_to_rgba(PpuPalette::Rgb2C04_0004, 0x00, none.0, none.1, none.2),
725 [0x91, 0x6D, 0x00, 0xFF] );
727 }
728
729 #[test]
730 fn rgb_emphasis_forces_channel_full_brightness() {
731 let base = palette_color_to_rgba(PpuPalette::Rgb2C03, 0x00, false, false, false);
734 assert_eq!(base, [0x6D, 0x6D, 0x6D, 0xFF]);
735 let red = palette_color_to_rgba(PpuPalette::Rgb2C03, 0x00, true, false, false);
736 assert_eq!(red, [0xFF, 0x6D, 0x6D, 0xFF]);
737 let green = palette_color_to_rgba(PpuPalette::Rgb2C03, 0x00, false, true, false);
738 assert_eq!(green, [0x6D, 0xFF, 0x6D, 0xFF]);
739 let blue = palette_color_to_rgba(PpuPalette::Rgb2C03, 0x00, false, false, true);
740 assert_eq!(blue, [0x6D, 0x6D, 0xFF, 0xFF]);
741 let all = palette_color_to_rgba(PpuPalette::Rgb2C03, 0x00, true, true, true);
742 assert_eq!(all, [0xFF, 0xFF, 0xFF, 0xFF]);
743 }
744
745 #[test]
746 fn composite_default_and_is_composite() {
747 assert_eq!(PpuPalette::default(), PpuPalette::Composite2C02);
748 assert!(PpuPalette::Composite2C02.is_composite());
749 assert!(!PpuPalette::Rgb2C03.is_composite());
750 assert!(!PpuPalette::Rgb2C05.is_composite());
751 }
752
753 #[test]
757 fn rgba_lut_matches_direct_computation_exhaustively() {
758 for palette in [
759 PpuPalette::Composite2C02,
760 PpuPalette::Rgb2C03,
761 PpuPalette::Rgb2C04_0001,
762 PpuPalette::Rgb2C04_0002,
763 PpuPalette::Rgb2C04_0003,
764 PpuPalette::Rgb2C04_0004,
765 PpuPalette::Rgb2C05,
766 ] {
767 let lut = build_rgba_lut(palette);
768 for e in 0..8usize {
769 for c in 0..64usize {
770 #[allow(clippy::cast_possible_truncation)]
771 let direct =
772 palette_color_to_rgba(palette, c as u8, e & 1 != 0, e & 2 != 0, e & 4 != 0);
773 assert_eq!(lut[(e << 6) | c], direct, "{palette:?} e={e} c={c}");
774 }
775 }
776 }
777 }
778}