Skip to main content

libsurfer/translation/
color_translators.rs

1use ecolor::Color32;
2use eyre::Result;
3use num::{BigUint, One, ToPrimitive};
4use surfer_translation_types::{
5    TranslationPreference, VariableValue, kind_for_binary_representation,
6};
7
8use crate::translation::{BasicTranslator, ValueKind};
9use crate::wave_container::{ScopeId, VarId, VariableMeta};
10
11pub struct RGBTranslator {}
12
13impl BasicTranslator<VarId, ScopeId> for RGBTranslator {
14    fn name(&self) -> String {
15        String::from("RGB")
16    }
17
18    fn basic_translate(&self, num_bits: u32, value: &VariableValue) -> (String, ValueKind) {
19        match value {
20            VariableValue::BigUint(v) => {
21                let nibble_length = num_bits.div_ceil(3);
22                let b = v % (BigUint::one() << nibble_length);
23                let g = (v >> nibble_length) % (BigUint::one() << nibble_length);
24                let r = (v >> (2 * nibble_length)) % (BigUint::one() << nibble_length);
25                let (r_u8, g_u8, b_u8) = if nibble_length >= 8 {
26                    let scale = nibble_length - 8;
27                    (
28                        (r >> scale).to_u8().unwrap_or(255),
29                        (g >> scale).to_u8().unwrap_or(255),
30                        (b >> scale).to_u8().unwrap_or(255),
31                    )
32                } else {
33                    let scale = 8 - nibble_length;
34                    (
35                        (r << scale).to_u8().unwrap_or(255),
36                        (g << scale).to_u8().unwrap_or(255),
37                        (b << scale).to_u8().unwrap_or(255),
38                    )
39                };
40                let s = format!("#{r_u8:02x}{g_u8:02x}{b_u8:02x}");
41                (s, ValueKind::Custom(Color32::from_rgb(r_u8, g_u8, b_u8)))
42            }
43            VariableValue::String(s) => (s.clone(), kind_for_binary_representation(s)),
44        }
45    }
46
47    fn translates(&self, variable: &VariableMeta) -> Result<TranslationPreference> {
48        if let Some(num_bits) = variable.num_bits {
49            if num_bits.is_multiple_of(3u32) {
50                Ok(TranslationPreference::Yes)
51            } else {
52                Ok(TranslationPreference::No)
53            }
54        } else {
55            Ok(TranslationPreference::No)
56        }
57    }
58}
59
60pub struct YCbCrTranslator {}
61
62impl BasicTranslator<VarId, ScopeId> for YCbCrTranslator {
63    fn name(&self) -> String {
64        String::from("YCbCr")
65    }
66
67    fn basic_translate(&self, num_bits: u32, value: &VariableValue) -> (String, ValueKind) {
68        match value {
69            VariableValue::BigUint(v) => {
70                let nibble_length = num_bits.div_ceil(3);
71                let cr = v % (BigUint::one() << nibble_length);
72                let cb = (v >> nibble_length) % (BigUint::one() << nibble_length);
73                let y = (v >> (2 * nibble_length)) % (BigUint::one() << nibble_length);
74                let (y_u8, cb_u8, cr_u8) = if nibble_length >= 8 {
75                    let scale = nibble_length - 8;
76                    (
77                        (y >> scale).to_u8().unwrap_or(255),
78                        (cb >> scale).to_u8().unwrap_or(255),
79                        (cr >> scale).to_u8().unwrap_or(255),
80                    )
81                } else {
82                    let scale = 8 - nibble_length;
83                    (
84                        (y << scale).to_u8().unwrap_or(255),
85                        (cb << scale).to_u8().unwrap_or(255),
86                        (cr << scale).to_u8().unwrap_or(255),
87                    )
88                };
89                let s = format!("#{y_u8:02x}{cb_u8:02x}{cr_u8:02x}");
90                let (r_u8, g_u8, b_u8) = ycbcr_to_rgb(y_u8, cb_u8, cr_u8);
91                (s, ValueKind::Custom(Color32::from_rgb(r_u8, g_u8, b_u8)))
92            }
93            VariableValue::String(s) => (s.clone(), kind_for_binary_representation(s)),
94        }
95    }
96
97    fn translates(&self, variable: &VariableMeta) -> Result<TranslationPreference> {
98        if let Some(num_bits) = variable.num_bits {
99            if num_bits.is_multiple_of(3u32) {
100                Ok(TranslationPreference::Yes)
101            } else {
102                Ok(TranslationPreference::No)
103            }
104        } else {
105            Ok(TranslationPreference::No)
106        }
107    }
108}
109
110pub struct GrayScaleTranslator {}
111
112impl BasicTranslator<VarId, ScopeId> for GrayScaleTranslator {
113    fn name(&self) -> String {
114        String::from("Grayscale")
115    }
116
117    fn basic_translate(&self, num_bits: u32, value: &VariableValue) -> (String, ValueKind) {
118        match value {
119            VariableValue::BigUint(v) => {
120                let g = if num_bits >= 8 {
121                    let scale = num_bits - 8;
122                    (v >> scale).to_u8().unwrap_or(255)
123                } else {
124                    let scale = 8 - num_bits;
125                    (v << scale).to_u8().unwrap_or(255)
126                };
127                let s = format!("#{g:02x}");
128                (s, ValueKind::Custom(Color32::from_gray(g)))
129            }
130            VariableValue::String(s) => (s.clone(), kind_for_binary_representation(s)),
131        }
132    }
133}
134
135// Convert YCbCr (BT.601) to RGB. Inputs and outputs are 8-bit.
136// Uses floating-point coefficients with rounding and clamps to [0, 255].
137fn ycbcr_to_rgb(y: u8, cb: u8, cr: u8) -> (u8, u8, u8) {
138    let y_f = f32::from(y);
139    let cb_i = (i32::from(cb) - 128) as f32;
140    let cr_i = (i32::from(cr) - 128) as f32;
141
142    let r = (y_f + 1.402_f32 * cr_i).round() as i32;
143    let g = (y_f - 0.344136_f32 * cb_i - 0.714136_f32 * cr_i).round() as i32;
144    let b = (y_f + 1.772_f32 * cb_i).round() as i32;
145
146    fn clamp_u8(x: i32) -> u8 {
147        x.clamp(0, 255) as u8
148    }
149
150    (clamp_u8(r), clamp_u8(g), clamp_u8(b))
151}
152
153#[cfg(test)]
154mod test {
155    use super::*;
156    use num::BigUint;
157
158    fn translate_rgb(num_bits: u32, value: u32) -> (String, ValueKind) {
159        let translator = RGBTranslator {};
160        let biguint_value = VariableValue::BigUint(BigUint::from(value));
161        translator.basic_translate(num_bits, &biguint_value)
162    }
163
164    fn assert_color_value(result: &ValueKind, expected_r: u8, expected_g: u8, expected_b: u8) {
165        if let ValueKind::Custom(color) = result {
166            let actual_color = Color32::from_rgb(expected_r, expected_g, expected_b);
167            assert_eq!(color, &actual_color, "Color mismatch");
168        } else {
169            panic!("Expected Custom color value, got {result:?}");
170        }
171    }
172
173    #[test]
174    fn rgb_translator_name() {
175        let translator = RGBTranslator {};
176        assert_eq!(translator.name(), "RGB");
177    }
178
179    // === 24-bit Color Tests ===
180    #[test]
181    fn rgb_translator_24bit_pure_red() {
182        let (hex, kind) = translate_rgb(24, 0xFF0000);
183        assert_eq!(hex, "#ff0000");
184        assert_color_value(&kind, 255, 0, 0);
185    }
186
187    #[test]
188    fn rgb_translator_24bit_pure_green() {
189        let (hex, kind) = translate_rgb(24, 0x00FF00);
190        assert_eq!(hex, "#00ff00");
191        assert_color_value(&kind, 0, 255, 0);
192    }
193
194    #[test]
195    fn rgb_translator_24bit_pure_blue() {
196        let (hex, kind) = translate_rgb(24, 0x0000FF);
197        assert_eq!(hex, "#0000ff");
198        assert_color_value(&kind, 0, 0, 255);
199    }
200
201    #[test]
202    fn rgb_translator_24bit_white() {
203        let (hex, kind) = translate_rgb(24, 0xFFFFFF);
204        assert_eq!(hex, "#ffffff");
205        assert_color_value(&kind, 255, 255, 255);
206    }
207
208    #[test]
209    fn rgb_translator_24bit_black() {
210        let (hex, kind) = translate_rgb(24, 0x000000);
211        assert_eq!(hex, "#000000");
212        assert_color_value(&kind, 0, 0, 0);
213    }
214
215    #[test]
216    fn rgb_translator_24bit_mixed_colors() {
217        // 0xABCDEF: R=AB, G=CD, B=EF
218        let (hex, kind) = translate_rgb(24, 0xABCDEF);
219        assert_eq!(hex, "#abcdef");
220        assert_color_value(&kind, 0xAB, 0xCD, 0xEF);
221    }
222
223    #[test]
224    fn rgb_translator_24bit_low_values() {
225        // 0x010203: R=01, G=02, B=03
226        let (hex, kind) = translate_rgb(24, 0x010203);
227        assert_eq!(hex, "#010203");
228        assert_color_value(&kind, 0x01, 0x02, 0x03);
229    }
230
231    // === Edge Case: Different Bit Widths ===
232    #[test]
233    fn rgb_translator_3bit_max_value() {
234        // 3-bit RGB (1 bit per channel)
235        // Value: 0b111 = all channels at max
236        let (hex, kind) = translate_rgb(3, 0x7);
237        // 1-bit channel values scaled to 8-bit: 1 << 7 = 128
238        assert_eq!(hex, "#808080");
239        assert_color_value(&kind, 128, 128, 128);
240    }
241
242    #[test]
243    fn rgb_translator_6bit_all_ones() {
244        // 6-bit RGB (2 bits per channel)
245        // All channels: 11 (3) scaled to 8-bit: 3 << 6 = 192
246        let (hex, kind) = translate_rgb(6, 0x3F);
247        assert_eq!(hex, "#c0c0c0");
248        assert_color_value(&kind, 192, 192, 192);
249    }
250
251    #[test]
252    fn rgb_translator_9bit_full_range() {
253        // 9-bit RGB (3 bits per channel)
254        // Each channel: 111 (7) scaled to 8-bit: 7 << 5 = 224
255        let (hex, kind) = translate_rgb(9, 0x1FF);
256        assert_eq!(hex, "#e0e0e0");
257        assert_color_value(&kind, 224, 224, 224);
258    }
259
260    #[test]
261    fn rgb_translator_12bit_mixed() {
262        // 12-bit RGB (4 bits per channel)
263        // R: 1111 (15), G: 1010 (10), B: 0101 (5)
264        // Scaled: 15 << 4 = 240, 10 << 4 = 160, 5 << 4 = 80
265        let (hex, kind) = translate_rgb(12, 0xFA5);
266        assert_eq!(hex, "#f0a050");
267        assert_color_value(&kind, 240, 160, 80);
268    }
269
270    #[test]
271    fn rgb_translator_15bit_downscaling() {
272        // 15-bit RGB (5 bits per channel)
273        // All channels: 11111 (31) scaled down: 31 >> 0 = 31 << 3 = 248
274        let (hex, kind) = translate_rgb(15, 0x7FFF);
275        assert_eq!(hex, "#f8f8f8");
276        assert_color_value(&kind, 248, 248, 248);
277    }
278
279    #[test]
280    fn rgb_translator_18bit_downscaling() {
281        // 18-bit RGB (6 bits per channel)
282        // All channels: 111111 (63) scaled down: 63 >> 0 = 63 << 2 = 252
283        let (hex, kind) = translate_rgb(18, 0x3FFFF);
284        assert_eq!(hex, "#fcfcfc");
285        assert_color_value(&kind, 252, 252, 252);
286    }
287
288    #[test]
289    fn rgb_translator_21bit_downscaling() {
290        // 21-bit RGB (7 bits per channel)
291        // All channels: 1111111 (127) scaled down: 127 >> 0 = 127 << 1 = 254
292        let (hex, kind) = translate_rgb(21, 0x1FFFFF);
293        assert_eq!(hex, "#fefefe");
294        assert_color_value(&kind, 254, 254, 254);
295    }
296
297    // === Asymmetric Color Values ===
298    #[test]
299    fn rgb_translator_24bit_high_red_low_others() {
300        let (hex, kind) = translate_rgb(24, 0xFF0000);
301        assert_eq!(hex, "#ff0000");
302        assert_color_value(&kind, 255, 0, 0);
303    }
304
305    #[test]
306    fn rgb_translator_24bit_medium_values() {
307        // 0x808080: R=128, G=128, B=128 (mid-gray)
308        let (hex, kind) = translate_rgb(24, 0x808080);
309        assert_eq!(hex, "#808080");
310        assert_color_value(&kind, 128, 128, 128);
311    }
312
313    #[test]
314    fn rgb_translator_12bit_asymmetric() {
315        // 12-bit RGB (4 bits per channel)
316        // R: 1111 (15), G: 1000 (8), B: 0001 (1)
317        // Scaled: 15 << 4 = 240, 8 << 4 = 128, 1 << 4 = 16
318        let (hex, kind) = translate_rgb(12, 0xF81);
319        assert_eq!(hex, "#f08010");
320        assert_color_value(&kind, 240, 128, 16);
321    }
322
323    // === String Value Handling ===
324    #[test]
325    fn rgb_translator_string_value() {
326        let translator = RGBTranslator {};
327        let value = VariableValue::String("test_string".to_string());
328        let (result, _kind) = translator.basic_translate(24, &value);
329        assert_eq!(result, "test_string");
330    }
331
332    // === Grayscale Translator Tests ===
333    fn translate_gray(num_bits: u32, value: u32) -> (String, ValueKind) {
334        let translator = GrayScaleTranslator {};
335        let biguint_value = VariableValue::BigUint(BigUint::from(value));
336        translator.basic_translate(num_bits, &biguint_value)
337    }
338
339    fn assert_gray_value(result: &ValueKind, g: u8) {
340        if let ValueKind::Custom(color) = result {
341            let expected = Color32::from_gray(g);
342            assert_eq!(color, &expected, "Gray color mismatch");
343        } else {
344            panic!("Expected Custom color value, got {result:?}");
345        }
346    }
347
348    #[test]
349    fn grayscale_translator_name() {
350        let translator = GrayScaleTranslator {};
351        assert_eq!(translator.name(), "Grayscale");
352    }
353
354    // 8-bit grayscale: identity mapping
355    #[test]
356    fn grayscale_8bit_black() {
357        let (hex, kind) = translate_gray(8, 0x00);
358        assert_eq!(hex, "#00");
359        assert_gray_value(&kind, 0);
360    }
361
362    #[test]
363    fn grayscale_8bit_mid_gray() {
364        let (hex, kind) = translate_gray(8, 0x80);
365        assert_eq!(hex, "#80");
366        assert_gray_value(&kind, 128);
367    }
368
369    #[test]
370    fn grayscale_8bit_white() {
371        let (hex, kind) = translate_gray(8, 0xFF);
372        assert_eq!(hex, "#ff");
373        assert_gray_value(&kind, 255);
374    }
375
376    // < 8-bit: upscaling via left shift
377    #[test]
378    fn grayscale_1bit_max() {
379        let (hex, kind) = translate_gray(1, 0x1);
380        assert_eq!(hex, "#80");
381        assert_gray_value(&kind, 128);
382    }
383
384    #[test]
385    fn grayscale_4bit_max() {
386        let (hex, kind) = translate_gray(4, 0xF);
387        assert_eq!(hex, "#f0");
388        assert_gray_value(&kind, 240);
389    }
390
391    #[test]
392    fn grayscale_7bit_max() {
393        let (hex, kind) = translate_gray(7, 0x7F);
394        assert_eq!(hex, "#fe");
395        assert_gray_value(&kind, 254);
396    }
397
398    // > 8-bit: downscaling via right shift
399    #[test]
400    fn grayscale_12bit_high_nibble() {
401        let (hex, kind) = translate_gray(12, 0xF00);
402        assert_eq!(hex, "#f0");
403        assert_gray_value(&kind, 0xF0);
404    }
405
406    #[test]
407    fn grayscale_12bit_mid_nibble() {
408        let (hex, kind) = translate_gray(12, 0x0F0);
409        assert_eq!(hex, "#0f");
410        assert_gray_value(&kind, 0x0F);
411    }
412
413    #[test]
414    fn grayscale_12bit_low_nibble() {
415        let (hex, kind) = translate_gray(12, 0x00F);
416        assert_eq!(hex, "#00");
417        assert_gray_value(&kind, 0x00);
418    }
419
420    #[test]
421    fn grayscale_16bit_high_byte() {
422        let (hex, kind) = translate_gray(16, 0xFF00);
423        assert_eq!(hex, "#ff");
424        assert_gray_value(&kind, 0xFF);
425    }
426
427    #[test]
428    fn grayscale_16bit_mid_value() {
429        let (hex, kind) = translate_gray(16, 0x7F00);
430        assert_eq!(hex, "#7f");
431        assert_gray_value(&kind, 0x7F);
432    }
433
434    #[test]
435    fn grayscale_16bit_low_byte() {
436        let (hex, kind) = translate_gray(16, 0x00FF);
437        assert_eq!(hex, "#00");
438        assert_gray_value(&kind, 0x00);
439    }
440
441    // String value handling
442    #[test]
443    fn grayscale_string_value() {
444        let translator = GrayScaleTranslator {};
445        let value = VariableValue::String("gray_string".to_string());
446        let (result, _kind) = translator.basic_translate(8, &value);
447        assert_eq!(result, "gray_string");
448    }
449
450    // === YCbCr → RGB Helper Tests ===
451    fn assert_rgb_approx(actual: (u8, u8, u8), expected: (u8, u8, u8), tol: u8) {
452        let (ar, ag, ab) = actual;
453        let (er, eg, eb) = expected;
454        let dr = ar.abs_diff(er);
455        let dg = ag.abs_diff(eg);
456        let db = ab.abs_diff(eb);
457        assert!(
458            dr <= tol && dg <= tol && db <= tol,
459            "actual={actual:?} expected={expected:?} tol={tol} diffs=({dr}, {dg}, {db})"
460        );
461    }
462
463    #[test]
464    fn ycbcr_gray_identity_low_mid_high() {
465        // When Cb=Cr=128, RGB should equal Y for all channels
466        assert_eq!(ycbcr_to_rgb(0, 128, 128), (0, 0, 0));
467        assert_eq!(ycbcr_to_rgb(128, 128, 128), (128, 128, 128));
468        assert_eq!(ycbcr_to_rgb(255, 128, 128), (255, 255, 255));
469    }
470
471    #[test]
472    fn ycbcr_pure_red_sample_bt601() {
473        // Typical BT.601 sample for pure red: Y=76, Cb=85, Cr=255
474        let rgb = ycbcr_to_rgb(76, 85, 255);
475        // Rounding may yield 254; allow small tolerance
476        assert_rgb_approx(rgb, (255, 0, 0), 2);
477    }
478
479    #[test]
480    fn ycbcr_pure_green_sample_bt601() {
481        // Typical BT.601 sample for pure green: Y=150, Cb=44, Cr=21
482        let rgb = ycbcr_to_rgb(150, 44, 21);
483        assert_rgb_approx(rgb, (0, 255, 0), 2);
484    }
485
486    #[test]
487    fn ycbcr_pure_blue_sample_bt601() {
488        // Typical BT.601 sample for pure blue: Y=29, Cb=255, Cr=107
489        let rgb = ycbcr_to_rgb(29, 255, 107);
490        assert_rgb_approx(rgb, (0, 0, 255), 2);
491    }
492
493    // === YCbCr Translator Tests ===
494    fn translate_ycbcr(num_bits: u32, y: u8, cb: u8, cr: u8) -> (String, ValueKind) {
495        let translator = YCbCrTranslator {};
496        let nibble_length = num_bits.div_ceil(3) as u32;
497        let packed: u32 = (u32::from(y) << (2 * nibble_length))
498            | (u32::from(cb) << nibble_length)
499            | u32::from(cr);
500        let value = VariableValue::BigUint(BigUint::from(packed));
501        translator.basic_translate(num_bits, &value)
502    }
503
504    #[test]
505    fn ycbcr_translator_name() {
506        let translator = YCbCrTranslator {};
507        assert_eq!(translator.name(), "YCbCr");
508    }
509
510    #[test]
511    fn ycbcr_24bit_gray_identity_low_mid_high() {
512        let (hex0, kind0) = translate_ycbcr(24, 0, 128, 128);
513        assert_eq!(hex0, "#008080");
514        assert_color_value(&kind0, 0, 0, 0);
515
516        let (hexm, kindm) = translate_ycbcr(24, 128, 128, 128);
517        assert_eq!(hexm, "#808080");
518        assert_color_value(&kindm, 128, 128, 128);
519
520        let (hexw, kindw) = translate_ycbcr(24, 255, 128, 128);
521        assert_eq!(hexw, "#ff8080");
522        assert_color_value(&kindw, 255, 255, 255);
523    }
524
525    #[test]
526    fn ycbcr_24bit_pure_red_sample_bt601() {
527        // Typical BT.601 sample for pure red: Y=76, Cb=85, Cr=255
528        let (hex, kind) = translate_ycbcr(24, 76, 85, 255);
529        assert_eq!(hex, "#4c55ff");
530        let (er, eg, eb) = ycbcr_to_rgb(76, 85, 255);
531        assert_color_value(&kind, er, eg, eb);
532    }
533
534    #[test]
535    fn ycbcr_24bit_pure_green_sample_bt601() {
536        // Typical BT.601 sample for pure green: Y=150, Cb=44, Cr=21
537        let (hex, kind) = translate_ycbcr(24, 150, 44, 21);
538        assert_eq!(hex, "#962c15");
539        let (er, eg, eb) = ycbcr_to_rgb(150, 44, 21);
540        assert_color_value(&kind, er, eg, eb);
541    }
542
543    #[test]
544    fn ycbcr_24bit_pure_blue_sample_bt601() {
545        // Typical BT.601 sample for pure blue: Y=29, Cb=255, Cr=107
546        let (hex, kind) = translate_ycbcr(24, 29, 255, 107);
547        assert_eq!(hex, "#1dff6b");
548        let (er, eg, eb) = ycbcr_to_rgb(29, 255, 107);
549        assert_color_value(&kind, er, eg, eb);
550    }
551
552    #[test]
553    fn ycbcr_12bit_scaled_values() {
554        // 12-bit (4 bits per component): Y=0xF, Cb=0x8, Cr=0x1
555        // After scaling to 8-bit: Y=240, Cb=128, Cr=16
556        let (hex, kind) = translate_ycbcr(12, 0xF, 0x8, 0x1);
557        assert_eq!(hex, "#f08010");
558        let (er, eg, eb) = ycbcr_to_rgb(240, 128, 16);
559        assert_color_value(&kind, er, eg, eb);
560    }
561
562    #[test]
563    fn ycbcr_3bit_scaled_values() {
564        // 3-bit (1 bit per component): Y=1, Cb=1, Cr=1
565        // After scaling to 8-bit: 128, 128, 128
566        let (hex, kind) = translate_ycbcr(3, 1, 1, 1);
567        assert_eq!(hex, "#808080");
568        let (er, eg, eb) = ycbcr_to_rgb(128, 128, 128);
569        assert_color_value(&kind, er, eg, eb);
570    }
571}