Skip to main content

ass_renderer/debug/inspector/
mod.rs

1use crate::Frame;
2
3#[cfg(feature = "nostd")]
4use alloc::vec::Vec;
5
6mod info;
7
8pub use info::{
9    BoundsInfo, ColorHistogram, LineScanSegment, PixelComparison, PixelInfo, RegionInfo,
10};
11
12/// Frame inspector for detailed pixel-level debugging
13pub struct FrameInspector {
14    frame: Option<Frame>,
15    cursor_x: u32,
16    cursor_y: u32,
17}
18
19impl Default for FrameInspector {
20    fn default() -> Self {
21        Self::new()
22    }
23}
24
25impl FrameInspector {
26    pub fn new() -> Self {
27        Self {
28            frame: None,
29            cursor_x: 0,
30            cursor_y: 0,
31        }
32    }
33
34    pub fn load_frame(&mut self, frame: Frame) {
35        self.cursor_x = frame.width() / 2;
36        self.cursor_y = frame.height() / 2;
37        self.frame = Some(frame);
38    }
39
40    pub fn move_cursor(&mut self, dx: i32, dy: i32) {
41        if let Some(ref frame) = self.frame {
42            let new_x = (self.cursor_x as i32 + dx)
43                .max(0)
44                .min(frame.width() as i32 - 1) as u32;
45            let new_y = (self.cursor_y as i32 + dy)
46                .max(0)
47                .min(frame.height() as i32 - 1) as u32;
48            self.cursor_x = new_x;
49            self.cursor_y = new_y;
50        }
51    }
52
53    pub fn set_cursor(&mut self, x: u32, y: u32) {
54        if let Some(ref frame) = self.frame {
55            self.cursor_x = x.min(frame.width() - 1);
56            self.cursor_y = y.min(frame.height() - 1);
57        }
58    }
59
60    pub fn get_pixel_at_cursor(&self) -> Option<PixelInfo> {
61        self.get_pixel_at(self.cursor_x, self.cursor_y)
62    }
63
64    pub fn get_pixel_at(&self, x: u32, y: u32) -> Option<PixelInfo> {
65        let frame = self.frame.as_ref()?;
66
67        if x >= frame.width() || y >= frame.height() {
68            return None;
69        }
70
71        let idx = ((y * frame.width() + x) * 4) as usize;
72        let pixels = frame.pixels();
73
74        if idx + 3 >= pixels.len() {
75            return None;
76        }
77
78        Some(PixelInfo {
79            x,
80            y,
81            r: pixels[idx],
82            g: pixels[idx + 1],
83            b: pixels[idx + 2],
84            a: pixels[idx + 3],
85        })
86    }
87
88    pub fn get_region(&self, x: u32, y: u32, width: u32, height: u32) -> RegionInfo {
89        let frame = match self.frame.as_ref() {
90            Some(f) => f,
91            None => return RegionInfo::empty(),
92        };
93
94        let mut pixels = Vec::new();
95        let mut histogram = ColorHistogram::new();
96
97        let x_end = (x + width).min(frame.width());
98        let y_end = (y + height).min(frame.height());
99
100        for py in y..y_end {
101            for px in x..x_end {
102                if let Some(pixel) = self.get_pixel_at(px, py) {
103                    histogram.add_pixel(&pixel);
104                    pixels.push(pixel);
105                }
106            }
107        }
108
109        RegionInfo {
110            x,
111            y,
112            width: x_end - x,
113            height: y_end - y,
114            pixels,
115            histogram,
116        }
117    }
118
119    pub fn find_non_transparent_bounds(&self) -> Option<BoundsInfo> {
120        let frame = self.frame.as_ref()?;
121        let pixels = frame.pixels();
122
123        let mut min_x = frame.width();
124        let mut min_y = frame.height();
125        let mut max_x = 0u32;
126        let mut max_y = 0u32;
127        let mut found = false;
128
129        for y in 0..frame.height() {
130            for x in 0..frame.width() {
131                let idx = ((y * frame.width() + x) * 4) as usize;
132                if idx + 3 < pixels.len() && pixels[idx + 3] > 0 {
133                    found = true;
134                    min_x = min_x.min(x);
135                    min_y = min_y.min(y);
136                    max_x = max_x.max(x);
137                    max_y = max_y.max(y);
138                }
139            }
140        }
141
142        if !found {
143            return None;
144        }
145
146        Some(BoundsInfo {
147            min_x,
148            min_y,
149            max_x,
150            max_y,
151            width: max_x - min_x + 1,
152            height: max_y - min_y + 1,
153        })
154    }
155
156    pub fn scan_line(&self, y: u32) -> Vec<LineScanSegment> {
157        let frame = match self.frame.as_ref() {
158            Some(f) => f,
159            None => return Vec::new(),
160        };
161
162        if y >= frame.height() {
163            return Vec::new();
164        }
165
166        let mut segments = Vec::new();
167        let mut current_segment: Option<LineScanSegment> = None;
168
169        for x in 0..frame.width() {
170            if let Some(pixel) = self.get_pixel_at(x, y) {
171                if pixel.a > 0 {
172                    // Non-transparent pixel
173                    match current_segment.as_mut() {
174                        Some(seg) if seg.is_similar(&pixel) => {
175                            seg.end_x = x;
176                            seg.pixels.push(pixel);
177                        }
178                        _ => {
179                            if let Some(seg) = current_segment.take() {
180                                segments.push(seg);
181                            }
182                            current_segment = Some(LineScanSegment {
183                                y,
184                                start_x: x,
185                                end_x: x,
186                                pixels: vec![pixel],
187                                avg_color: [pixel.r, pixel.g, pixel.b, pixel.a],
188                            });
189                        }
190                    }
191                } else if let Some(seg) = current_segment.take() {
192                    segments.push(seg);
193                }
194            }
195        }
196
197        if let Some(mut seg) = current_segment {
198            seg.calculate_average();
199            segments.push(seg);
200        }
201
202        segments
203    }
204
205    pub fn compare_pixels(&self, x1: u32, y1: u32, x2: u32, y2: u32) -> Option<PixelComparison> {
206        let pixel1 = self.get_pixel_at(x1, y1)?;
207        let pixel2 = self.get_pixel_at(x2, y2)?;
208
209        Some(PixelComparison {
210            pixel1,
211            pixel2,
212            color_distance: pixel1.color_distance(&pixel2),
213            is_similar: pixel1.is_similar(&pixel2, 30),
214        })
215    }
216
217    pub fn print_cursor_info(&self) {
218        if let Some(pixel) = self.get_pixel_at_cursor() {
219            pixel.print_detailed();
220        } else {
221            println!("No frame loaded or cursor out of bounds");
222        }
223    }
224
225    pub fn print_ascii_preview(&self, width: u32, height: u32) {
226        let frame = match self.frame.as_ref() {
227            Some(f) => f,
228            None => {
229                println!("No frame loaded");
230                return;
231            }
232        };
233
234        let scale_x = frame.width() as f32 / width as f32;
235        let scale_y = frame.height() as f32 / height as f32;
236
237        println!(
238            "ASCII Preview ({}x{} -> {}x{}):",
239            frame.width(),
240            frame.height(),
241            width,
242            height
243        );
244        let border = "─".repeat(width as usize);
245        println!("┌{border}┐");
246
247        for y in 0..height {
248            print!("│");
249            for x in 0..width {
250                let sample_x = (x as f32 * scale_x) as u32;
251                let sample_y = (y as f32 * scale_y) as u32;
252
253                if let Some(pixel) = self.get_pixel_at(sample_x, sample_y) {
254                    print!("{}", pixel.to_ascii());
255                } else {
256                    print!(" ");
257                }
258            }
259            println!("│");
260        }
261
262        let border = "─".repeat(width as usize);
263        println!("└{border}┘");
264        println!("Legend: █=opaque ▓=semi ░=faint ·=trace  =transparent");
265    }
266}