Skip to main content

libsurfer/
frame_buffer.rs

1use ecolor::Color32;
2use egui::{CornerRadius, DragValue, Pos2, Rect, Sense, Stroke};
3use serde::{Deserialize, Serialize};
4use surfer_translation_types::VariableValue;
5
6use crate::translation::ycbcr_to_rgb;
7use crate::wave_container::{ScopeRef, ScopeRefExt, VariableRef, VariableRefExt, WaveContainer};
8use crate::{Message, system_state::SystemState};
9
10#[derive(Serialize, Deserialize, Debug, Clone)]
11#[serde(default)]
12pub(crate) struct FrameBufferSettings {
13    pub pixels_per_row: usize,
14    pub square_pixels: bool,
15    #[serde(flatten)]
16    pub color_settings: PixelColorSettings,
17}
18
19#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
20pub enum FrameBufferColorMode {
21    #[default]
22    Grayscale,
23    Rgb,
24    YCbCr,
25}
26
27impl std::fmt::Display for FrameBufferColorMode {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        write!(f, "{}", self.string())
30    }
31}
32
33impl FrameBufferColorMode {
34    fn string(&self) -> &'static str {
35        match self {
36            FrameBufferColorMode::Grayscale => "Grayscale",
37            FrameBufferColorMode::Rgb => "RGB",
38            FrameBufferColorMode::YCbCr => "YCbCr",
39        }
40    }
41}
42
43#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
44#[serde(default)]
45pub(crate) struct PixelColorSettings {
46    #[serde(default)]
47    pub color_mode: FrameBufferColorMode,
48    pub grayscale_bits: u8,
49    pub r_bits: u8,
50    pub g_bits: u8,
51    pub b_bits: u8,
52    #[serde(default = "default_y_bits")]
53    pub y_bits: u8,
54    #[serde(default = "default_cb_bits")]
55    pub cb_bits: u8,
56    #[serde(default = "default_cr_bits")]
57    pub cr_bits: u8,
58}
59
60fn default_y_bits() -> u8 {
61    8
62}
63
64fn default_cb_bits() -> u8 {
65    8
66}
67
68fn default_cr_bits() -> u8 {
69    8
70}
71
72impl Default for PixelColorSettings {
73    fn default() -> Self {
74        Self {
75            color_mode: FrameBufferColorMode::Grayscale,
76            grayscale_bits: 1,
77            r_bits: 3,
78            g_bits: 3,
79            b_bits: 2,
80            y_bits: default_y_bits(),
81            cb_bits: default_cb_bits(),
82            cr_bits: default_cr_bits(),
83        }
84    }
85}
86
87impl Default for FrameBufferSettings {
88    fn default() -> Self {
89        Self {
90            pixels_per_row: 16,
91            square_pixels: true,
92            color_settings: PixelColorSettings::default(),
93        }
94    }
95}
96
97#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
98pub(crate) struct ArrayLevel {
99    pub min_index: i64,
100    pub max_index: i64,
101    pub first_index: i64,
102    pub last_index: i64,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
106pub(crate) struct FrameBufferContentCacheKey {
107    pub content: FrameBufferContent,
108    pub cursor_position: num::BigUint,
109}
110
111#[derive(Debug, Clone)]
112pub(crate) struct FrameBufferArrayCache {
113    pub key: FrameBufferContentCacheKey,
114    pub cached_value: Option<std::sync::Arc<[bool]>>,
115}
116
117#[derive(Debug, Clone, PartialEq, Eq)]
118pub(crate) struct FrameBufferPixelCacheKey {
119    pub array_key: FrameBufferContentCacheKey,
120    pub settings: PixelColorSettings,
121}
122
123#[derive(Debug, Clone)]
124pub(crate) struct FrameBufferPixelCache {
125    pub key: FrameBufferPixelCacheKey,
126    pub pixel_colors: std::sync::Arc<[Color32]>,
127}
128
129#[derive(Debug, Clone, PartialEq, Eq)]
130pub(crate) enum FrameBufferContent {
131    Array {
132        scope_ref: ScopeRef,
133        /// One range-selector per level of array nesting.
134        /// The last level always applies to variables.
135        levels: Vec<ArrayLevel>,
136    },
137    Variable(VariableRef),
138}
139
140impl SystemState {
141    pub(crate) fn draw_frame_buffer_window(
142        &mut self,
143        ctx: &egui::Context,
144        msgs: &mut Vec<Message>,
145    ) {
146        let mut open = true;
147        egui::Window::new("Frame Buffer")
148            .open(&mut open)
149            .resizable(true)
150            .show(ctx, |ui| {
151                let frame_buffer_value = self.selected_variable_for_frame_buffer();
152                let Some((bits, array_cache_key, variable_name)) = frame_buffer_value.as_ref()
153                else {
154                    ui.label("Place the cursor.");
155                    return;
156                };
157
158                let color_settings_key = {
159                    let settings = &mut self.user.frame_buffer;
160                    let color_settings = &mut settings.color_settings;
161
162                    ui.checkbox(&mut settings.square_pixels, "Square pixels");
163
164                    ui.horizontal(|ui| {
165                        ui.label("Color mode");
166                        egui::ComboBox::from_id_salt("frame_buffer_color_mode")
167                            .selected_text(color_settings.color_mode.string())
168                            .show_ui(ui, |ui| {
169                                ui.selectable_value(
170                                    &mut color_settings.color_mode,
171                                    FrameBufferColorMode::Grayscale,
172                                    FrameBufferColorMode::Grayscale.string(),
173                                );
174                                ui.selectable_value(
175                                    &mut color_settings.color_mode,
176                                    FrameBufferColorMode::Rgb,
177                                    FrameBufferColorMode::Rgb.string(),
178                                );
179                                ui.selectable_value(
180                                    &mut color_settings.color_mode,
181                                    FrameBufferColorMode::YCbCr,
182                                    FrameBufferColorMode::YCbCr.string(),
183                                );
184                            });
185                    });
186
187                    match color_settings.color_mode {
188                        FrameBufferColorMode::Grayscale => {
189                            ui.horizontal(|ui| {
190                                ui.label("Grayscale bits");
191                                ui.add(
192                                    DragValue::new(&mut color_settings.grayscale_bits).range(1..=8),
193                                );
194                            });
195                        }
196                        FrameBufferColorMode::Rgb => {
197                            ui.horizontal(|ui| {
198                                ui.label("R bits");
199                                ui.add(DragValue::new(&mut color_settings.r_bits).range(0..=8));
200                                ui.label("G bits");
201                                ui.add(DragValue::new(&mut color_settings.g_bits).range(0..=8));
202                                ui.label("B bits");
203                                ui.add(DragValue::new(&mut color_settings.b_bits).range(0..=8));
204                            });
205                        }
206                        FrameBufferColorMode::YCbCr => {
207                            ui.horizontal(|ui| {
208                                ui.label("Y bits");
209                                ui.add(DragValue::new(&mut color_settings.y_bits).range(0..=8));
210                                ui.label("Cb bits");
211                                ui.add(DragValue::new(&mut color_settings.cb_bits).range(0..=8));
212                                ui.label("Cr bits");
213                                ui.add(DragValue::new(&mut color_settings.cr_bits).range(0..=8));
214                            });
215                        }
216                    }
217
218                    color_settings.clone()
219                };
220
221                ui.separator();
222
223                if bits.is_empty() {
224                    ui.label("No bits available");
225                    return;
226                }
227
228                let pixel_cache_key = FrameBufferPixelCacheKey {
229                    array_key: array_cache_key.clone(),
230                    settings: color_settings_key.clone(),
231                };
232
233                let pixel_colors = if let Some(cache) = self
234                    .frame_buffer_pixel_cache
235                    .as_ref()
236                    .filter(|cache| cache.key == pixel_cache_key)
237                {
238                    cache.pixel_colors.clone()
239                } else {
240                    let decoded = match color_settings_key.color_mode {
241                        FrameBufferColorMode::Rgb => {
242                            let r_bits = color_settings_key.r_bits as usize;
243                            let g_bits = color_settings_key.g_bits as usize;
244                            let b_bits = color_settings_key.b_bits as usize;
245                            let bits_per_pixel = r_bits + g_bits + b_bits;
246                            if bits_per_pixel == 0 {
247                                ui.label("Set at least one RGB channel bit count above zero.");
248                                return;
249                            }
250                            decode_rgb_pixels(bits, r_bits, g_bits, b_bits)
251                        }
252                        FrameBufferColorMode::YCbCr => {
253                            let y_bits = color_settings_key.y_bits as usize;
254                            let cb_bits = color_settings_key.cb_bits as usize;
255                            let cr_bits = color_settings_key.cr_bits as usize;
256                            let bits_per_pixel = y_bits + cb_bits + cr_bits;
257                            if bits_per_pixel == 0 {
258                                ui.label("Set at least one YCbCr channel bit count above zero.");
259                                return;
260                            }
261                            decode_ycbcr_pixels(bits, y_bits, cb_bits, cr_bits)
262                        }
263                        FrameBufferColorMode::Grayscale => {
264                            let gray_bits = color_settings_key.grayscale_bits as usize;
265                            decode_grayscale_pixels(bits, gray_bits)
266                        }
267                    };
268
269                    let decoded: std::sync::Arc<[Color32]> = decoded.into();
270
271                    self.frame_buffer_pixel_cache = Some(FrameBufferPixelCache {
272                        key: pixel_cache_key,
273                        pixel_colors: decoded.clone(),
274                    });
275                    decoded
276                };
277
278                if pixel_colors.is_empty() {
279                    ui.label("No pixels to draw with current bit settings.");
280                    return;
281                }
282
283                let settings = &mut self.user.frame_buffer;
284                let columns = settings.pixels_per_row.min(pixel_colors.len()).max(1);
285                let rows = pixel_colors.len().div_ceil(columns);
286                ui.horizontal(|ui| {
287                    ui.label(format!("Var: {variable_name} | {columns}×{rows}"));
288
289                    if ui.button("Copy image").clicked() {
290                        let total = columns * rows;
291                        let mut padded = pixel_colors.to_vec();
292                        padded.resize(total, Color32::BLACK);
293                        ui.ctx().copy_image(egui::ColorImage {
294                            size: [columns, rows],
295                            pixels: padded,
296                            source_size: egui::vec2(columns as f32, rows as f32),
297                        });
298                    }
299                });
300                self.draw_array_index_range(ui);
301
302                let settings = &mut self.user.frame_buffer;
303                let max_columns = pixel_colors.len().max(1);
304                settings.pixels_per_row = settings.pixels_per_row.clamp(1, max_columns);
305
306                ui.horizontal(|ui| {
307                    ui.label("Pixels in x-direction");
308                    ui.add(
309                        egui::Slider::new(&mut settings.pixels_per_row, 1..=max_columns).integer(),
310                    );
311                });
312
313                ui.separator();
314
315                let available = ui.available_size_before_wrap();
316
317                if available.x <= 0.0 || available.y <= 0.0 {
318                    return;
319                }
320
321                let (pixel_width, pixel_height) = if settings.square_pixels {
322                    let side = (available.x / columns as f32).min(available.y / rows as f32);
323                    (side, side)
324                } else {
325                    (available.x / columns as f32, available.y / rows as f32)
326                };
327
328                let image_size =
329                    egui::vec2(pixel_width * columns as f32, pixel_height * rows as f32);
330                let (rect, _) = ui.allocate_exact_size(image_size, Sense::hover());
331                let painter = ui.painter_at(rect);
332
333                for (index, color) in pixel_colors.iter().copied().enumerate() {
334                    let x = index % columns;
335                    let y = index / columns;
336
337                    let min = Pos2 {
338                        x: rect.min.x + x as f32 * pixel_width,
339                        y: rect.min.y + y as f32 * pixel_height,
340                    };
341                    let max = Pos2 {
342                        x: min.x + pixel_width,
343                        y: min.y + pixel_height,
344                    };
345
346                    painter.rect_filled(Rect { min, max }, CornerRadius::ZERO, color);
347                }
348
349                painter.rect_stroke(
350                    rect,
351                    CornerRadius::ZERO,
352                    Stroke::new(1.0, ui.visuals().weak_text_color()),
353                    egui::StrokeKind::Inside,
354                );
355            });
356
357        if !open {
358            msgs.push(Message::SetFrameBufferVisibleVariable(None));
359        }
360    }
361
362    fn draw_array_index_range(&mut self, ui: &mut egui::Ui) {
363        let Some(FrameBufferContent::Array {
364            scope_ref: _,
365            levels,
366        }) = self.frame_buffer_content.as_mut()
367        else {
368            return;
369        };
370
371        if levels.is_empty() {
372            return;
373        }
374
375        let total_levels = levels.len();
376
377        for (i, level) in levels.iter_mut().enumerate() {
378            let (min, max) = (level.min_index, level.max_index);
379            level.first_index = level.first_index.clamp(min, max);
380            level.last_index = level.last_index.clamp(min, max);
381            if level.first_index > level.last_index {
382                level.last_index = level.first_index;
383            }
384            ui.horizontal(|ui| {
385                if total_levels == 1 {
386                    ui.label("First array index");
387                } else {
388                    ui.label(format!("Level {} first index", i + 1));
389                }
390                ui.add(DragValue::new(&mut level.first_index).range(min..=max));
391                if total_levels == 1 {
392                    ui.label("Last array index");
393                } else {
394                    ui.label(format!("Level {} last index", i + 1));
395                }
396                ui.add(DragValue::new(&mut level.last_index).range(min..=max));
397            });
398            if level.first_index > level.last_index {
399                level.first_index = level.last_index;
400            }
401        }
402    }
403
404    fn selected_variable_for_frame_buffer(
405        &mut self,
406    ) -> Option<(std::sync::Arc<[bool]>, FrameBufferContentCacheKey, String)> {
407        let waves = self.user.waves.as_ref()?;
408        let cursor = waves.cursor.as_ref()?.to_biguint()?;
409        let wave_container = waves.inner.as_waves()?;
410        let content = self.frame_buffer_content.clone()?;
411        let cache_key = FrameBufferContentCacheKey {
412            content: content.clone(),
413            cursor_position: cursor.clone(),
414        };
415        let cached = self
416            .frame_buffer_array_cache
417            .as_ref()
418            .filter(|cache| cache.key == cache_key)
419            .cloned();
420
421        let cached = if let Some(cached) = cached {
422            cached
423        } else {
424            let cached = match &content {
425                FrameBufferContent::Variable(variable_ref) => build_variable_frame_buffer_cache(
426                    wave_container,
427                    variable_ref,
428                    &cursor,
429                    cache_key,
430                )?,
431                FrameBufferContent::Array { scope_ref, levels } => {
432                    if levels.is_empty() {
433                        return None;
434                    }
435
436                    let sorted_variables =
437                        resolve_leaf_scopes_and_variables(wave_container, scope_ref, levels)?;
438                    let cached_value =
439                        build_cached_variable_value(wave_container, &sorted_variables, &cursor);
440                    FrameBufferArrayCache {
441                        key: cache_key,
442                        cached_value,
443                    }
444                }
445            };
446            self.frame_buffer_array_cache = Some(cached.clone());
447            cached
448        };
449
450        let bits = cached.cached_value.as_ref()?.clone();
451
452        let variable_name = match &content {
453            FrameBufferContent::Variable(variable_ref) => variable_ref.full_path_string_no_index(),
454            FrameBufferContent::Array { scope_ref, .. } => scope_ref.full_name(),
455        };
456
457        Some((bits, cached.key.clone(), variable_name))
458    }
459}
460
461fn build_cached_variable_value(
462    wave_container: &WaveContainer,
463    sorted_variables: &[VariableRef],
464    cursor: &num::BigUint,
465) -> Option<std::sync::Arc<[bool]>> {
466    // First pass: sum bit widths for pre-allocation.
467    let capacity: usize = sorted_variables
468        .iter()
469        .filter_map(|v| wave_container.variable_meta(v).ok()?.num_bits)
470        .map(|b| b as usize)
471        .sum();
472
473    if capacity == 0 {
474        return None;
475    }
476
477    let mut concat_bits: Vec<bool> = Vec::with_capacity(capacity);
478
479    for var_ref in sorted_variables {
480        let Ok(meta) = wave_container.variable_meta(var_ref) else {
481            continue;
482        };
483        let Some(bits) = meta.num_bits else {
484            continue;
485        };
486        let bits = bits as usize;
487
488        // On missing or unavailable signal, pad with zeros to preserve alignment.
489        let value = wave_container
490            .query_variable(var_ref, cursor)
491            .ok()
492            .flatten()
493            .and_then(|q| q.current)
494            .map(|(_, v)| v);
495
496        match value {
497            Some(VariableValue::BigUint(v)) => {
498                append_biguint_lower_bits_with_left_zero_pad(&v, bits, &mut concat_bits);
499            }
500            Some(VariableValue::String(s)) => {
501                append_str_lower_bits_with_left_zero_pad(&s, bits, &mut concat_bits);
502            }
503            None => {
504                concat_bits.extend(std::iter::repeat_n(false, bits));
505            }
506        }
507    }
508
509    if concat_bits.is_empty() {
510        None
511    } else {
512        Some(concat_bits.into())
513    }
514}
515
516fn build_variable_frame_buffer_cache(
517    wave_container: &WaveContainer,
518    variable_ref: &VariableRef,
519    cursor: &num::BigUint,
520    key: FrameBufferContentCacheKey,
521) -> Option<FrameBufferArrayCache> {
522    let meta = wave_container.variable_meta(variable_ref).ok()?;
523    let word_length = meta.num_bits? as usize;
524    let query_result = wave_container
525        .query_variable(variable_ref, cursor)
526        .ok()
527        .flatten()?;
528    let (_, value) = query_result.current?;
529    let padded: std::sync::Arc<[bool]> = frame_buffer_bits(&value, word_length).into();
530
531    Some(FrameBufferArrayCache {
532        key,
533        cached_value: Some(padded),
534    })
535}
536
537fn resolve_leaf_scopes_and_variables(
538    wave_container: &WaveContainer,
539    scope_ref: &ScopeRef,
540    levels: &[ArrayLevel],
541) -> Option<Vec<VariableRef>> {
542    let (scope_levels, var_level) = levels.split_at(levels.len() - 1);
543    let var_level = &var_level[0];
544
545    let mut current_scopes = vec![scope_ref.clone()];
546    for level in scope_levels {
547        let clamped_first = level.first_index.clamp(level.min_index, level.max_index);
548        let clamped_last = level.last_index.clamp(level.min_index, level.max_index);
549        let mut next_scopes = Vec::new();
550        for scope in &current_scopes {
551            let mut selected: Vec<ScopeRef> = wave_container
552                .child_scopes(scope)
553                .unwrap_or_default()
554                .into_iter()
555                .filter(|s| {
556                    let idx = scope_array_index(s);
557                    idx >= clamped_first && idx <= clamped_last
558                })
559                .collect();
560            selected.sort_by_key(scope_array_index);
561            next_scopes.extend(selected);
562        }
563        current_scopes = next_scopes;
564    }
565
566    if current_scopes.is_empty() {
567        return None;
568    }
569
570    let clamped_first = var_level
571        .first_index
572        .clamp(var_level.min_index, var_level.max_index);
573    let clamped_last = var_level
574        .last_index
575        .clamp(var_level.min_index, var_level.max_index);
576    if clamped_first > clamped_last {
577        return None;
578    }
579
580    let mut sorted_variables = Vec::new();
581    for leaf_scope in &current_scopes {
582        let mut variables = wave_container.variables_in_scope(leaf_scope);
583        variables.sort_by_key(variable_array_index);
584        sorted_variables.extend(variables.into_iter().filter(|var_ref| {
585            let idx = variable_array_index(var_ref);
586            idx >= clamped_first && idx <= clamped_last
587        }));
588    }
589
590    Some(sorted_variables)
591}
592
593/// Analyses the scope hierarchy rooted at `scope_ref` and returns:
594/// - `levels`: one `ArrayLevel` per nesting level, where the last level is for variables
595/// - `all_leaf_vars`: every variable reachable from the root (for pre-loading)
596///
597/// Returns `None` when `scope_ref` is not found in the hierarchy.
598pub(crate) fn build_frame_buffer_content(
599    wave_container: &WaveContainer,
600    scope_ref: &ScopeRef,
601) -> Option<(Vec<ArrayLevel>, Vec<VariableRef>)> {
602    // Probe the hierarchy by following the min-index child at each level.
603    // Stop when we reach a leaf scope that has no child scopes.
604    let mut levels: Vec<ArrayLevel> = Vec::new();
605    let mut probe = scope_ref.clone();
606    loop {
607        let children = wave_container.child_scopes(&probe).unwrap_or_default();
608        if children.is_empty() {
609            break;
610        }
611        let indices: Vec<i64> = children.iter().map(scope_array_index).collect();
612        let min_idx = *indices.iter().min().unwrap_or(&0);
613        let max_idx = *indices.iter().max().unwrap_or(&0);
614        levels.push(ArrayLevel {
615            min_index: min_idx,
616            max_index: max_idx,
617            first_index: min_idx,
618            last_index: max_idx,
619        });
620        probe = children.into_iter().min_by_key(scope_array_index).unwrap();
621    }
622
623    // Determine the variable index range from the representative leaf scope.
624    let leaf_vars = wave_container.variables_in_scope(&probe);
625    let var_indices: Vec<i64> = leaf_vars
626        .iter()
627        .map(variable_array_index)
628        .filter(|&i| i != i64::MAX)
629        .collect();
630    let (var_min, var_max) = if var_indices.is_empty() {
631        (0, 0)
632    } else {
633        (
634            *var_indices.iter().min().unwrap(),
635            *var_indices.iter().max().unwrap(),
636        )
637    };
638    levels.push(ArrayLevel {
639        min_index: var_min,
640        max_index: var_max,
641        first_index: var_min,
642        last_index: var_max,
643    });
644
645    // Walk every path to collect all leaf variables for pre-loading.
646    let depth = levels.len().saturating_sub(1);
647    let mut leaf_scopes = vec![scope_ref.clone()];
648    for _ in 0..depth {
649        leaf_scopes = leaf_scopes
650            .iter()
651            .flat_map(|s| wave_container.child_scopes(s).unwrap_or_default())
652            .collect();
653    }
654    let all_leaf_vars: Vec<VariableRef> = leaf_scopes
655        .iter()
656        .flat_map(|s| wave_container.variables_in_scope(s))
657        .collect();
658
659    Some((levels, all_leaf_vars))
660}
661
662fn scope_array_index(scope_ref: &ScopeRef) -> i64 {
663    let name = scope_ref.name();
664    name.parse::<i64>()
665        .ok()
666        .or_else(|| {
667            name.strip_prefix('[')
668                .and_then(|s| s.strip_suffix(']'))
669                .and_then(|s| s.parse::<i64>().ok())
670        })
671        .unwrap_or(i64::MAX)
672}
673
674fn variable_array_index(var_ref: &VariableRef) -> i64 {
675    fn parse_index_name(name: &str) -> Option<i64> {
676        name.parse::<i64>().ok().or_else(|| {
677            name.strip_prefix('[')
678                .and_then(|s| s.strip_suffix(']'))
679                .and_then(|s| s.parse::<i64>().ok())
680        })
681    }
682
683    var_ref
684        .index
685        .or_else(|| parse_index_name(&var_ref.name))
686        .unwrap_or(i64::MAX)
687}
688
689fn frame_buffer_bits(value: &VariableValue, word_length: usize) -> Vec<bool> {
690    let mut out = Vec::with_capacity(word_length);
691    match value {
692        VariableValue::BigUint(v) => {
693            append_biguint_lower_bits_with_left_zero_pad(v, word_length, &mut out);
694        }
695        VariableValue::String(v) => {
696            append_str_lower_bits_with_left_zero_pad(v, word_length, &mut out);
697        }
698    }
699    out
700}
701
702fn append_str_lower_bits_with_left_zero_pad(src: &str, width: usize, out: &mut Vec<bool>) {
703    if width == 0 {
704        return;
705    }
706
707    let start = src.len().saturating_sub(width);
708    let suffix = &src.as_bytes()[start..];
709
710    out.extend(std::iter::repeat_n(false, width - suffix.len()));
711    out.extend(suffix.iter().map(|b| *b == b'1'));
712}
713
714fn append_biguint_lower_bits_with_left_zero_pad(
715    value: &num::BigUint,
716    width: usize,
717    out: &mut Vec<bool>,
718) {
719    use num::ToPrimitive as _;
720    if width == 0 {
721        return;
722    }
723
724    // Fast path for small values that fit in a u128.
725    if let Some(v) = value.to_u128() {
726        let value_bits = 128usize.saturating_sub(v.leading_zeros() as usize);
727        let actual = width.min(value_bits);
728        out.extend(std::iter::repeat_n(false, width - actual));
729        if actual > 0 {
730            let mut v = v << (127 - (actual - 1));
731            for _ in 0..actual {
732                out.push(v >> 127 != 0);
733                v <<= 1;
734            }
735        }
736        return;
737    }
738
739    let digits = value.to_u32_digits();
740    let value_bits = value.bits() as usize;
741    let actual = width.min(value_bits);
742    out.extend(std::iter::repeat_n(false, width - actual));
743    let top_limb = actual.saturating_sub(1) / 32;
744    // Top limb first: may be partial, so align its MSB before consuming.
745    let hi = (actual - 1) % 32;
746    let mut limb = digits.get(top_limb).copied().unwrap_or(0) << (31 - hi);
747    for _ in 0..=hi {
748        out.push(limb & 0x8000_0000 != 0);
749        limb <<= 1;
750    }
751    // Remaining limbs are always full 32-bit words; no conditional needed.
752    for limb_idx in (0..top_limb).rev() {
753        let mut limb = digits.get(limb_idx).copied().unwrap_or(0);
754        for _ in 0..32 {
755            out.push(limb & 0x8000_0000 != 0);
756            limb <<= 1;
757        }
758    }
759}
760
761fn decode_grayscale_pixels(bits: &[bool], grayscale_bits: usize) -> Vec<Color32> {
762    // Default mode: every bool maps directly to black or white.
763    if grayscale_bits == 1 {
764        return bits
765            .iter()
766            .map(|&b| {
767                let v = u8::from(b) * 255;
768                Color32::from_rgb(v, v, v)
769            })
770            .collect();
771    }
772    let step = grayscale_bits.max(1);
773    let scale = channel_scaler(step);
774    let mut chunks = bits.chunks_exact(step);
775    let mut out = Vec::with_capacity(bits.len().div_ceil(step));
776    for chunk in chunks.by_ref() {
777        let gray = apply_scale(bits_to_u16(chunk), scale);
778        out.push(Color32::from_rgb(gray, gray, gray));
779    }
780    let rem = chunks.remainder();
781    if !rem.is_empty() {
782        let gray = apply_scale(bits_to_u16_padded(rem, 0, step), scale);
783        out.push(Color32::from_rgb(gray, gray, gray));
784    }
785    out
786}
787
788fn decode_rgb_pixels(bits: &[bool], r_bits: usize, g_bits: usize, b_bits: usize) -> Vec<Color32> {
789    let bits_per_pixel = r_bits + g_bits + b_bits;
790    let step = bits_per_pixel.max(1);
791    let (r_scale, g_scale, b_scale) = (
792        channel_scaler(r_bits),
793        channel_scaler(g_bits),
794        channel_scaler(b_bits),
795    );
796    let mut chunks = bits.chunks_exact(step);
797    let mut out = Vec::with_capacity(bits.len().div_ceil(step));
798    for chunk in chunks.by_ref() {
799        let red = apply_scale(bits_to_u16(&chunk[..r_bits]), r_scale);
800        let green = apply_scale(bits_to_u16(&chunk[r_bits..r_bits + g_bits]), g_scale);
801        let blue = apply_scale(bits_to_u16(&chunk[r_bits + g_bits..]), b_scale);
802        out.push(Color32::from_rgb(red, green, blue));
803    }
804    let rem = chunks.remainder();
805    if !rem.is_empty() {
806        let red = apply_scale(bits_to_u16_padded(rem, 0, r_bits), r_scale);
807        let green = apply_scale(bits_to_u16_padded(rem, r_bits, g_bits), g_scale);
808        let blue = apply_scale(bits_to_u16_padded(rem, r_bits + g_bits, b_bits), b_scale);
809        out.push(Color32::from_rgb(red, green, blue));
810    }
811    out
812}
813
814fn decode_ycbcr_pixels(
815    bits: &[bool],
816    y_bits: usize,
817    cb_bits: usize,
818    cr_bits: usize,
819) -> Vec<Color32> {
820    let bits_per_pixel = y_bits + cb_bits + cr_bits;
821    let step = bits_per_pixel.max(1);
822    let (y_scale, cb_scale, cr_scale) = (
823        channel_scaler(y_bits),
824        channel_scaler(cb_bits),
825        channel_scaler(cr_bits),
826    );
827    let mut chunks = bits.chunks_exact(step);
828    let mut out = Vec::with_capacity(bits.len().div_ceil(step));
829    for chunk in chunks.by_ref() {
830        let y = apply_scale(bits_to_u16(&chunk[..y_bits]), y_scale);
831        let cb = apply_scale(bits_to_u16(&chunk[y_bits..y_bits + cb_bits]), cb_scale);
832        let cr = apply_scale(bits_to_u16(&chunk[y_bits + cb_bits..]), cr_scale);
833        let (red, green, blue) = ycbcr_to_rgb(y, cb, cr);
834        out.push(Color32::from_rgb(red, green, blue));
835    }
836    let rem = chunks.remainder();
837    if !rem.is_empty() {
838        let y = apply_scale(bits_to_u16_padded(rem, 0, y_bits), y_scale);
839        let cb = apply_scale(bits_to_u16_padded(rem, y_bits, cb_bits), cb_scale);
840        let cr = apply_scale(bits_to_u16_padded(rem, y_bits + cb_bits, cr_bits), cr_scale);
841        let (red, green, blue) = ycbcr_to_rgb(y, cb, cr);
842        out.push(Color32::from_rgb(red, green, blue));
843    }
844    out
845}
846
847/// Reads up to `len` bits starting at `start`, zero-padding if out of bounds.
848fn bits_to_u16_padded(bits: &[bool], start: usize, len: usize) -> u16 {
849    let mut value = 0u16;
850    for offset in 0..len {
851        value = (value << 1) | u16::from(bits.get(start + offset).copied().unwrap_or(false));
852    }
853    value
854}
855
856/// Reads exactly `bits.len()` bits from a known in-bounds slice — no bounds checks.
857fn bits_to_u16(bits: &[bool]) -> u16 {
858    let mut value = 0u16;
859    for &b in bits {
860        value = (value << 1) | u16::from(b);
861    }
862    value
863}
864
865// Precompute a fixed-point reciprocal: (255 << 8) / max_in.
866fn channel_scaler(bits: usize) -> u32 {
867    if bits == 0 {
868        return 0;
869    }
870    (255u32 << 8) / ((1u32 << bits) - 1)
871}
872
873#[inline(always)]
874fn apply_scale(value: u16, multiplier: u32) -> u8 {
875    ((value as u32 * multiplier) >> 8) as u8
876}
877
878#[cfg(test)]
879mod tests {
880    use super::*;
881    use num::BigUint;
882
883    #[test]
884    fn frame_buffer_bits_pads_to_word_length() {
885        let bits = frame_buffer_bits(&VariableValue::BigUint(BigUint::from(0b101u8)), 5);
886        assert_eq!(bits, vec![false, false, true, false, true]);
887    }
888
889    #[test]
890    fn frame_buffer_bits_truncates_to_word_length() {
891        let bits = frame_buffer_bits(&VariableValue::String("101101".to_string()), 4);
892        assert_eq!(bits, vec![true, true, false, true]);
893    }
894
895    #[test]
896    fn bits_to_u16_padded_reads_and_zero_pads() {
897        let bits = vec![true, false, true];
898        assert_eq!(bits_to_u16_padded(&bits, 0, 3), 0b101);
899        assert_eq!(bits_to_u16_padded(&bits, 1, 4), 0b0100);
900    }
901
902    #[test]
903    fn decode_grayscale_pixels_uses_bit_groups() {
904        let bits = vec![false, false, true, true];
905        let pixels = decode_grayscale_pixels(&bits, 2);
906        assert_eq!(pixels.len(), 2);
907        assert_eq!(pixels[0], Color32::from_rgb(0, 0, 0));
908        assert_eq!(pixels[1], Color32::from_rgb(255, 255, 255));
909    }
910
911    #[test]
912    fn decode_rgb_pixels_supports_different_channel_widths() {
913        let bits = vec![
914            true, false, false, true, true, false, // R=10 G=01 B=10 with r=2,g=2,b=2
915        ];
916        let pixels = decode_rgb_pixels(&bits, 2, 2, 2);
917        assert_eq!(pixels.len(), 1);
918        assert_eq!(pixels[0], Color32::from_rgb(170, 85, 170));
919    }
920
921    #[test]
922    fn decode_ycbcr_pixels_supports_8bit_channels() {
923        let bits = vec![
924            true, false, false, false, false, false, false, false, // Y=128
925            true, false, false, false, false, false, false, false, // Cb=128
926            true, false, false, false, false, false, false, false, // Cr=128
927        ];
928        let pixels = decode_ycbcr_pixels(&bits, 8, 8, 8);
929        assert_eq!(pixels.len(), 1);
930        assert_eq!(pixels[0], Color32::from_rgb(128, 128, 128));
931    }
932
933    #[test]
934    fn variable_array_index_parses_bracketed_name() {
935        let var_ref = VariableRef::new(ScopeRef::empty(), "[2]".to_string());
936        assert_eq!(variable_array_index(&var_ref), 2);
937    }
938
939    #[test]
940    fn variable_array_index_parses_plain_numeric_name() {
941        let var_ref = VariableRef::new(ScopeRef::empty(), "7".to_string());
942        assert_eq!(variable_array_index(&var_ref), 7);
943    }
944
945    #[test]
946    fn variable_array_index_prefers_explicit_index() {
947        let var_ref = VariableRef::new_with_id_and_index(
948            ScopeRef::empty(),
949            "[2]".to_string(),
950            Default::default(),
951            Some(9),
952        );
953        assert_eq!(variable_array_index(&var_ref), 9);
954    }
955
956    #[test]
957    fn variable_array_index_falls_back_to_max_for_non_numeric_names() {
958        let var_ref = VariableRef::new(ScopeRef::empty(), "data".to_string());
959        assert_eq!(variable_array_index(&var_ref), i64::MAX);
960    }
961}