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