ass_renderer/debug/inspector/
info.rs1#[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 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 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 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 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}