Skip to main content

ass_renderer/debug/inspector/
info.rs

1#[cfg(not(feature = "nostd"))]
2use std::collections::HashSet;
3#[cfg(not(feature = "nostd"))]
4use std::fmt;
5
6#[cfg(feature = "nostd")]
7use alloc::collections::BTreeSet as HashSet;
8#[cfg(feature = "nostd")]
9use alloc::vec::Vec;
10#[cfg(feature = "nostd")]
11use core::fmt;
12
13#[derive(Debug, Clone, Copy)]
14pub struct PixelInfo {
15    pub x: u32,
16    pub y: u32,
17    pub r: u8,
18    pub g: u8,
19    pub b: u8,
20    pub a: u8,
21}
22
23impl PixelInfo {
24    pub fn to_hex(&self) -> String {
25        format!(
26            "#{r:02X}{g:02X}{b:02X}{a:02X}",
27            r = self.r,
28            g = self.g,
29            b = self.b,
30            a = self.a
31        )
32    }
33
34    pub fn to_css(&self) -> String {
35        if self.a == 255 {
36            format!("rgb({r}, {g}, {b})", r = self.r, g = self.g, b = self.b)
37        } else {
38            format!(
39                "rgba({r}, {g}, {b}, {a:.2})",
40                r = self.r,
41                g = self.g,
42                b = self.b,
43                a = self.a as f32 / 255.0
44            )
45        }
46    }
47
48    pub fn luminance(&self) -> f32 {
49        // Relative luminance formula
50        0.2126 * (self.r as f32) + 0.7152 * (self.g as f32) + 0.0722 * (self.b as f32)
51    }
52
53    pub fn is_grayscale(&self) -> bool {
54        self.r == self.g && self.g == self.b
55    }
56
57    pub fn color_distance(&self, other: &PixelInfo) -> f32 {
58        let dr = (self.r as f32 - other.r as f32).powi(2);
59        let dg = (self.g as f32 - other.g as f32).powi(2);
60        let db = (self.b as f32 - other.b as f32).powi(2);
61        let da = (self.a as f32 - other.a as f32).powi(2);
62
63        (dr + dg + db + da).sqrt()
64    }
65
66    pub fn is_similar(&self, other: &PixelInfo, threshold: u8) -> bool {
67        let diff = |a: u8, b: u8| -> u8 { a.abs_diff(b) };
68
69        diff(self.r, other.r) <= threshold
70            && diff(self.g, other.g) <= threshold
71            && diff(self.b, other.b) <= threshold
72            && diff(self.a, other.a) <= threshold
73    }
74
75    pub fn to_ascii(&self) -> char {
76        if self.a == 0 {
77            ' '
78        } else if self.a > 200 {
79            '█'
80        } else if self.a > 150 {
81            '▓'
82        } else if self.a > 100 {
83            '▒'
84        } else if self.a > 50 {
85            '░'
86        } else {
87            '·'
88        }
89    }
90
91    pub fn print_detailed(&self) {
92        println!("╔════════════════════════════════════╗");
93        println!("║         Pixel Information          ║");
94        println!("╚════════════════════════════════════╝");
95        println!("Position: ({x}, {y})", x = self.x, y = self.y);
96        println!(
97            "RGBA: ({r}, {g}, {b}, {a})",
98            r = self.r,
99            g = self.g,
100            b = self.b,
101            a = self.a
102        );
103        println!("Hex: {hex}", hex = self.to_hex());
104        println!("CSS: {css}", css = self.to_css());
105        println!("Luminance: {luminance:.2}", luminance = self.luminance());
106        println!(
107            "Grayscale: {}",
108            if self.is_grayscale() { "Yes" } else { "No" }
109        );
110        println!(
111            "Opacity: {opacity:.1}%",
112            opacity = (self.a as f32 / 255.0) * 100.0
113        );
114
115        // Color classification
116        let color_name = if self.a == 0 {
117            "Transparent"
118        } else if self.is_grayscale() {
119            if self.r > 200 {
120                "White"
121            } else if self.r > 150 {
122                "Light Gray"
123            } else if self.r > 100 {
124                "Gray"
125            } else if self.r > 50 {
126                "Dark Gray"
127            } else {
128                "Black"
129            }
130        } else {
131            // Simple color classification
132            if self.r > self.g && self.r > self.b {
133                "Reddish"
134            } else if self.g > self.r && self.g > self.b {
135                "Greenish"
136            } else if self.b > self.r && self.b > self.g {
137                "Bluish"
138            } else {
139                "Mixed"
140            }
141        };
142
143        println!("Color: {color_name}");
144        println!("════════════════════════════════════");
145    }
146}
147
148#[derive(Debug)]
149pub struct RegionInfo {
150    pub x: u32,
151    pub y: u32,
152    pub width: u32,
153    pub height: u32,
154    pub pixels: Vec<PixelInfo>,
155    pub histogram: ColorHistogram,
156}
157
158impl RegionInfo {
159    pub(super) fn empty() -> Self {
160        Self {
161            x: 0,
162            y: 0,
163            width: 0,
164            height: 0,
165            pixels: Vec::new(),
166            histogram: ColorHistogram::new(),
167        }
168    }
169
170    pub fn print_summary(&self) {
171        println!(
172            "Region: {}x{} at ({}, {})",
173            self.width, self.height, self.x, self.y
174        );
175        let pixel_count = self.pixels.len();
176        println!("Total pixels: {pixel_count}");
177
178        if !self.pixels.is_empty() {
179            let non_transparent = self.pixels.iter().filter(|p| p.a > 0).count();
180            println!(
181                "Non-transparent: {} ({:.1}%)",
182                non_transparent,
183                (non_transparent as f32 / self.pixels.len() as f32) * 100.0
184            );
185
186            self.histogram.print_summary();
187        }
188    }
189}
190
191#[derive(Debug)]
192pub struct ColorHistogram {
193    pub red_sum: u64,
194    pub green_sum: u64,
195    pub blue_sum: u64,
196    pub alpha_sum: u64,
197    pub count: usize,
198    pub unique_colors: HashSet<(u8, u8, u8, u8)>,
199}
200
201impl ColorHistogram {
202    pub(super) fn new() -> Self {
203        Self {
204            red_sum: 0,
205            green_sum: 0,
206            blue_sum: 0,
207            alpha_sum: 0,
208            count: 0,
209            unique_colors: HashSet::new(),
210        }
211    }
212
213    pub(super) fn add_pixel(&mut self, pixel: &PixelInfo) {
214        self.red_sum += pixel.r as u64;
215        self.green_sum += pixel.g as u64;
216        self.blue_sum += pixel.b as u64;
217        self.alpha_sum += pixel.a as u64;
218        self.count += 1;
219        self.unique_colors
220            .insert((pixel.r, pixel.g, pixel.b, pixel.a));
221    }
222
223    pub fn average_color(&self) -> Option<[u8; 4]> {
224        if self.count == 0 {
225            return None;
226        }
227
228        Some([
229            (self.red_sum / self.count as u64) as u8,
230            (self.green_sum / self.count as u64) as u8,
231            (self.blue_sum / self.count as u64) as u8,
232            (self.alpha_sum / self.count as u64) as u8,
233        ])
234    }
235
236    fn print_summary(&self) {
237        if let Some(avg) = self.average_color() {
238            println!(
239                "Average color: RGBA({}, {}, {}, {})",
240                avg[0], avg[1], avg[2], avg[3]
241            );
242        }
243        let color_count = self.unique_colors.len();
244        println!("Unique colors: {color_count}");
245    }
246}
247
248#[derive(Debug)]
249pub struct BoundsInfo {
250    pub min_x: u32,
251    pub min_y: u32,
252    pub max_x: u32,
253    pub max_y: u32,
254    pub width: u32,
255    pub height: u32,
256}
257
258impl fmt::Display for BoundsInfo {
259    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
260        write!(
261            f,
262            "Bounds: ({}, {}) to ({}, {}) [{}x{}]",
263            self.min_x, self.min_y, self.max_x, self.max_y, self.width, self.height
264        )
265    }
266}
267
268#[derive(Debug)]
269pub struct LineScanSegment {
270    pub y: u32,
271    pub start_x: u32,
272    pub end_x: u32,
273    pub pixels: Vec<PixelInfo>,
274    pub avg_color: [u8; 4],
275}
276
277impl LineScanSegment {
278    pub(super) fn is_similar(&self, pixel: &PixelInfo) -> bool {
279        if self.pixels.is_empty() {
280            return false;
281        }
282
283        // Check if pixel is similar to average
284        let threshold = 30;
285        let diff = |a: u8, b: u8| -> u8 { a.abs_diff(b) };
286
287        diff(self.avg_color[0], pixel.r) <= threshold
288            && diff(self.avg_color[1], pixel.g) <= threshold
289            && diff(self.avg_color[2], pixel.b) <= threshold
290    }
291
292    pub(super) fn calculate_average(&mut self) {
293        if self.pixels.is_empty() {
294            return;
295        }
296
297        let sum: (u64, u64, u64, u64) = self.pixels.iter().fold((0, 0, 0, 0), |acc, p| {
298            (
299                acc.0 + p.r as u64,
300                acc.1 + p.g as u64,
301                acc.2 + p.b as u64,
302                acc.3 + p.a as u64,
303            )
304        });
305
306        let count = self.pixels.len() as u64;
307        self.avg_color = [
308            (sum.0 / count) as u8,
309            (sum.1 / count) as u8,
310            (sum.2 / count) as u8,
311            (sum.3 / count) as u8,
312        ];
313    }
314
315    pub fn width(&self) -> u32 {
316        self.end_x - self.start_x + 1
317    }
318}
319
320#[derive(Debug)]
321pub struct PixelComparison {
322    pub pixel1: PixelInfo,
323    pub pixel2: PixelInfo,
324    pub color_distance: f32,
325    pub is_similar: bool,
326}
327
328impl fmt::Display for PixelComparison {
329    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
330        writeln!(
331            f,
332            "Pixel 1 @ ({}, {}): {}",
333            self.pixel1.x,
334            self.pixel1.y,
335            self.pixel1.to_hex()
336        )?;
337        writeln!(
338            f,
339            "Pixel 2 @ ({}, {}): {}",
340            self.pixel2.x,
341            self.pixel2.y,
342            self.pixel2.to_hex()
343        )?;
344        writeln!(f, "Distance: {:.2}", self.color_distance)?;
345        writeln!(f, "Similar: {}", if self.is_similar { "Yes" } else { "No" })?;
346        Ok(())
347    }
348}