ass_renderer/debug/analyzer/
mod.rs1use crate::Frame;
7
8#[cfg(feature = "nostd")]
9use alloc::vec::Vec;
10
11mod report;
12
13pub use report::{AnalysisReport, PixelHistogram, Region};
14
15pub struct FrameAnalyzer {
17 enable_pixel_histogram: bool,
18 enable_region_analysis: bool,
19 enable_text_detection: bool,
20}
21
22impl Default for FrameAnalyzer {
23 fn default() -> Self {
24 Self::new()
25 }
26}
27
28impl FrameAnalyzer {
29 pub fn new() -> Self {
31 Self {
32 enable_pixel_histogram: true,
33 enable_region_analysis: true,
34 enable_text_detection: true,
35 }
36 }
37
38 pub fn analyze(&self, frame: &Frame) -> AnalysisReport {
40 let mut report = AnalysisReport::new(frame.width(), frame.height());
41
42 if self.enable_pixel_histogram {
43 report.pixel_histogram = self.calculate_pixel_histogram(frame);
44 }
45
46 if self.enable_region_analysis {
47 report.regions = self.detect_regions(frame);
48 }
49
50 if self.enable_text_detection {
51 report.text_areas = self.detect_text_areas(frame);
52 }
53
54 report.calculate_statistics(frame);
55 report
56 }
57
58 fn calculate_pixel_histogram(&self, frame: &Frame) -> PixelHistogram {
59 let pixels = frame.pixels();
60 let mut histogram = PixelHistogram::default();
61
62 for chunk in pixels.chunks(4) {
63 if chunk.len() == 4 {
64 let r = chunk[0];
65 let g = chunk[1];
66 let b = chunk[2];
67 let a = chunk[3];
68
69 histogram.red[r as usize] += 1;
70 histogram.green[g as usize] += 1;
71 histogram.blue[b as usize] += 1;
72 histogram.alpha[a as usize] += 1;
73
74 if a > 0 {
75 histogram.non_transparent_count += 1;
76
77 if r == g && g == b {
79 if r > 200 {
80 histogram.white_pixels += 1;
81 } else if r < 50 {
82 histogram.black_pixels += 1;
83 } else {
84 histogram.gray_pixels += 1;
85 }
86 } else {
87 histogram.colored_pixels += 1;
88 }
89 }
90 }
91 }
92
93 histogram
94 }
95
96 fn detect_regions(&self, frame: &Frame) -> Vec<Region> {
97 let mut regions = Vec::new();
98 let pixels = frame.pixels();
99 let width = frame.width() as usize;
100 let height = frame.height() as usize;
101
102 let mut visited = vec![false; width * height];
104
105 for y in 0..height {
106 for x in 0..width {
107 let idx = y * width + x;
108 let pixel_idx = idx * 4;
109
110 if !visited[idx] && pixel_idx + 3 < pixels.len() && pixels[pixel_idx + 3] > 0 {
111 let region = self.flood_fill(frame, &mut visited, x, y);
113 if region.pixel_count > 10 {
114 regions.push(region);
116 }
117 }
118 }
119 }
120
121 regions
122 }
123
124 fn flood_fill(
125 &self,
126 frame: &Frame,
127 visited: &mut [bool],
128 start_x: usize,
129 start_y: usize,
130 ) -> Region {
131 let pixels = frame.pixels();
132 let width = frame.width() as usize;
133 let height = frame.height() as usize;
134
135 let mut region = Region {
136 min_x: start_x as u32,
137 min_y: start_y as u32,
138 max_x: start_x as u32,
139 max_y: start_y as u32,
140 pixel_count: 0,
141 avg_color: [0, 0, 0, 0],
142 };
143
144 let mut stack = vec![(start_x, start_y)];
145 let mut color_sum = [0u64; 4];
146
147 while let Some((x, y)) = stack.pop() {
148 if x >= width || y >= height {
149 continue;
150 }
151
152 let idx = y * width + x;
153 if visited[idx] {
154 continue;
155 }
156
157 let pixel_idx = idx * 4;
158 if pixel_idx + 3 >= pixels.len() || pixels[pixel_idx + 3] == 0 {
159 continue;
160 }
161
162 visited[idx] = true;
163 region.pixel_count += 1;
164
165 region.min_x = region.min_x.min(x as u32);
167 region.min_y = region.min_y.min(y as u32);
168 region.max_x = region.max_x.max(x as u32);
169 region.max_y = region.max_y.max(y as u32);
170
171 for i in 0..4 {
173 color_sum[i] += pixels[pixel_idx + i] as u64;
174 }
175
176 if x > 0 {
178 stack.push((x - 1, y));
179 }
180 if x + 1 < width {
181 stack.push((x + 1, y));
182 }
183 if y > 0 {
184 stack.push((x, y - 1));
185 }
186 if y + 1 < height {
187 stack.push((x, y + 1));
188 }
189 }
190
191 if region.pixel_count > 0 {
193 for (i, sum) in color_sum.iter().enumerate() {
194 region.avg_color[i] = (sum / region.pixel_count as u64) as u8;
195 }
196 }
197
198 region
199 }
200
201 fn detect_text_areas(&self, frame: &Frame) -> Vec<TextArea> {
202 let regions = self.detect_regions(frame);
203 let mut text_areas = Vec::new();
204
205 for region in regions {
206 let width = region.max_x - region.min_x + 1;
212 let height = region.max_y - region.min_y + 1;
213 let aspect_ratio = width as f32 / height as f32;
214
215 if aspect_ratio > 1.5 && (15..200).contains(&height) {
218 let is_bright = region.avg_color[0] > 200
220 || region.avg_color[1] > 200
221 || region.avg_color[2] > 200;
222
223 if is_bright {
224 text_areas.push(TextArea {
225 x: region.min_x,
226 y: region.min_y,
227 width,
228 height,
229 confidence: calculate_text_confidence(®ion),
230 estimated_font_size: estimate_font_size(height),
231 });
232 }
233 }
234 }
235
236 text_areas
237 }
238}
239
240#[derive(Debug, Clone)]
241pub struct TextArea {
242 pub x: u32,
243 pub y: u32,
244 pub width: u32,
245 pub height: u32,
246 pub confidence: f32,
247 pub estimated_font_size: u32,
248}
249
250fn calculate_text_confidence(region: &Region) -> f32 {
251 let width = region.max_x - region.min_x + 1;
253 let height = region.max_y - region.min_y + 1;
254 let aspect = width as f32 / height as f32;
255
256 let mut confidence: f32 = 0.0;
257
258 if (2.0..20.0).contains(&aspect) {
260 confidence += 0.3;
261 }
262
263 let brightness =
265 (region.avg_color[0] as f32 + region.avg_color[1] as f32 + region.avg_color[2] as f32)
266 / 3.0;
267 if brightness > 200.0 {
268 confidence += 0.4;
269 }
270
271 if (20..150).contains(&height) {
273 confidence += 0.3;
274 }
275
276 confidence.min(1.0)
277}
278
279fn estimate_font_size(height: u32) -> u32 {
280 (height as f32 * 0.75) as u32
282}