Skip to main content

ass_renderer/debug/analyzer/
report.rs

1use crate::Frame;
2
3#[cfg(feature = "nostd")]
4use alloc::vec::Vec;
5
6use super::TextArea;
7
8/// Detailed analysis report for a rendered frame
9#[derive(Debug, Clone)]
10pub struct AnalysisReport {
11    /// Frame width in pixels
12    pub width: u32,
13    /// Frame height in pixels
14    pub height: u32,
15    /// Total number of pixels in the frame
16    pub total_pixels: usize,
17    /// Number of non-transparent pixels
18    pub non_transparent_pixels: usize,
19    /// Percentage of transparent pixels
20    pub transparency_percentage: f32,
21    /// Histogram of pixel color distribution
22    pub pixel_histogram: PixelHistogram,
23    /// Detected regions in the frame
24    pub regions: Vec<Region>,
25    /// Detected text areas in the frame
26    pub text_areas: Vec<TextArea>,
27    /// Dominant color in the frame [R, G, B, A]
28    pub dominant_color: [u8; 4],
29    /// Estimated contrast ratio
30    pub contrast_ratio: f32,
31}
32
33impl AnalysisReport {
34    pub(super) fn new(width: u32, height: u32) -> Self {
35        Self {
36            width,
37            height,
38            total_pixels: (width * height) as usize,
39            non_transparent_pixels: 0,
40            transparency_percentage: 0.0,
41            pixel_histogram: PixelHistogram::default(),
42            regions: Vec::new(),
43            text_areas: Vec::new(),
44            dominant_color: [0, 0, 0, 0],
45            contrast_ratio: 0.0,
46        }
47    }
48
49    pub(super) fn calculate_statistics(&mut self, frame: &Frame) {
50        self.non_transparent_pixels = self.pixel_histogram.non_transparent_count;
51        self.transparency_percentage = ((self.total_pixels - self.non_transparent_pixels) as f32
52            / self.total_pixels as f32)
53            * 100.0;
54
55        // Calculate dominant color
56        self.dominant_color = self.calculate_dominant_color();
57
58        // Calculate contrast ratio (simplified)
59        self.contrast_ratio = self.calculate_contrast_ratio(frame);
60    }
61
62    fn calculate_dominant_color(&self) -> [u8; 4] {
63        let hist = &self.pixel_histogram;
64
65        // Find the peak in each channel
66        let mut dominant = [0u8; 4];
67
68        for (i, channel) in [&hist.red, &hist.green, &hist.blue, &hist.alpha]
69            .iter()
70            .enumerate()
71        {
72            let mut max_count = 0;
73            let mut max_value = 0;
74
75            for (value, &count) in channel.iter().enumerate() {
76                if count > max_count {
77                    max_count = count;
78                    max_value = value;
79                }
80            }
81
82            dominant[i] = max_value as u8;
83        }
84
85        dominant
86    }
87
88    fn calculate_contrast_ratio(&self, _frame: &Frame) -> f32 {
89        // Simplified contrast calculation
90        // In a real implementation, this would calculate Weber contrast or Michelson contrast
91        if self.pixel_histogram.white_pixels > 0 && self.pixel_histogram.black_pixels > 0 {
92            let white_ratio =
93                self.pixel_histogram.white_pixels as f32 / self.non_transparent_pixels as f32;
94            let black_ratio =
95                self.pixel_histogram.black_pixels as f32 / self.non_transparent_pixels as f32;
96
97            // High contrast if we have both bright and dark pixels
98            (white_ratio * black_ratio * 100.0).min(1.0)
99        } else {
100            0.0
101        }
102    }
103
104    pub fn print_summary(&self) {
105        println!("\n╔════════════════════════════════════════╗");
106        println!("║         Frame Analysis Report          ║");
107        println!("╚════════════════════════════════════════╝");
108
109        println!("\n📊 Basic Statistics:");
110        println!(
111            "  • Resolution: {width}x{height}",
112            width = self.width,
113            height = self.height
114        );
115        println!(
116            "  • Total Pixels: {total_pixels}",
117            total_pixels = self.total_pixels
118        );
119        println!(
120            "  • Non-transparent: {} ({:.1}%)",
121            self.non_transparent_pixels,
122            (self.non_transparent_pixels as f32 / self.total_pixels as f32) * 100.0
123        );
124        println!(
125            "  • Transparency: {transparency:.1}%",
126            transparency = self.transparency_percentage
127        );
128
129        println!("\n🎨 Color Distribution:");
130        println!(
131            "  • White pixels: {white_pixels}",
132            white_pixels = self.pixel_histogram.white_pixels
133        );
134        println!(
135            "  • Black pixels: {black_pixels}",
136            black_pixels = self.pixel_histogram.black_pixels
137        );
138        println!(
139            "  • Gray pixels: {gray_pixels}",
140            gray_pixels = self.pixel_histogram.gray_pixels
141        );
142        println!(
143            "  • Colored pixels: {colored_pixels}",
144            colored_pixels = self.pixel_histogram.colored_pixels
145        );
146        println!(
147            "  • Dominant color: RGBA({r}, {g}, {b}, {a})",
148            r = self.dominant_color[0],
149            g = self.dominant_color[1],
150            b = self.dominant_color[2],
151            a = self.dominant_color[3]
152        );
153
154        println!("\n🔍 Region Detection:");
155        println!(
156            "  • Detected regions: {regions_count}",
157            regions_count = self.regions.len()
158        );
159        for (i, region) in self.regions.iter().enumerate() {
160            println!(
161                "    {}. Box: ({}, {}) to ({}, {}) | Pixels: {} | Color: RGBA({}, {}, {}, {})",
162                i + 1,
163                region.min_x,
164                region.min_y,
165                region.max_x,
166                region.max_y,
167                region.pixel_count,
168                region.avg_color[0],
169                region.avg_color[1],
170                region.avg_color[2],
171                region.avg_color[3]
172            );
173        }
174
175        println!("\n📝 Text Detection:");
176        if self.text_areas.is_empty() {
177            println!("  • No text areas detected");
178        } else {
179            println!(
180                "  • Detected text areas: {text_areas_count}",
181                text_areas_count = self.text_areas.len()
182            );
183            for (i, area) in self.text_areas.iter().enumerate() {
184                println!(
185                    "    {}. Position: ({}, {}) | Size: {}x{} | Font: ~{}px | Confidence: {:.1}%",
186                    i + 1,
187                    area.x,
188                    area.y,
189                    area.width,
190                    area.height,
191                    area.estimated_font_size,
192                    area.confidence * 100.0
193                );
194            }
195        }
196
197        println!("\n⚡ Performance Indicators:");
198        println!(
199            "  • Contrast ratio: {contrast_ratio:.2}",
200            contrast_ratio = self.contrast_ratio
201        );
202        println!(
203            "  • Memory usage: {memory_kb} KB",
204            memory_kb = (self.total_pixels * 4) / 1024
205        );
206
207        println!("\n═══════════════════════════════════════");
208    }
209
210    pub fn to_json(&self) -> String {
211        format!(
212            r#"{{
213    "resolution": {{ "width": {}, "height": {} }},
214    "pixels": {{
215        "total": {},
216        "non_transparent": {},
217        "transparency_percentage": {:.2}
218    }},
219    "color_distribution": {{
220        "white": {},
221        "black": {},
222        "gray": {},
223        "colored": {}
224    }},
225    "regions": {},
226    "text_areas": {},
227    "dominant_color": [{}, {}, {}, {}],
228    "contrast_ratio": {:.2}
229}}"#,
230            self.width,
231            self.height,
232            self.total_pixels,
233            self.non_transparent_pixels,
234            self.transparency_percentage,
235            self.pixel_histogram.white_pixels,
236            self.pixel_histogram.black_pixels,
237            self.pixel_histogram.gray_pixels,
238            self.pixel_histogram.colored_pixels,
239            self.regions.len(),
240            self.text_areas.len(),
241            self.dominant_color[0],
242            self.dominant_color[1],
243            self.dominant_color[2],
244            self.dominant_color[3],
245            self.contrast_ratio
246        )
247    }
248}
249
250#[derive(Debug, Clone)]
251pub struct PixelHistogram {
252    pub red: [usize; 256],
253    pub green: [usize; 256],
254    pub blue: [usize; 256],
255    pub alpha: [usize; 256],
256    pub non_transparent_count: usize,
257    pub white_pixels: usize,
258    pub black_pixels: usize,
259    pub gray_pixels: usize,
260    pub colored_pixels: usize,
261}
262
263impl Default for PixelHistogram {
264    fn default() -> Self {
265        Self {
266            red: [0; 256],
267            green: [0; 256],
268            blue: [0; 256],
269            alpha: [0; 256],
270            non_transparent_count: 0,
271            white_pixels: 0,
272            black_pixels: 0,
273            gray_pixels: 0,
274            colored_pixels: 0,
275        }
276    }
277}
278
279#[derive(Debug, Clone)]
280pub struct Region {
281    pub min_x: u32,
282    pub min_y: u32,
283    pub max_x: u32,
284    pub max_y: u32,
285    pub pixel_count: usize,
286    pub avg_color: [u8; 4],
287}