1use std::fmt;
4use std::time::{Duration, SystemTime};
5
6#[derive(Debug, Clone, PartialEq)]
8pub struct DisplayInfo {
9 pub index: usize,
11 pub name: String,
13 pub width: u32,
15 pub height: u32,
17 pub x: i32,
19 pub y: i32,
21 pub scale_factor: f32,
23 pub is_primary: bool,
25 pub refresh_rate: u32,
27 pub color_depth: u8,
29}
30
31impl Default for DisplayInfo {
32 fn default() -> Self {
33 Self {
34 index: 0,
35 name: "Primary Display".to_string(),
36 width: 1920,
37 height: 1080,
38 x: 0,
39 y: 0,
40 scale_factor: 1.0,
41 is_primary: true,
42 refresh_rate: 60,
43 color_depth: 32,
44 }
45 }
46}
47
48impl DisplayInfo {
49 pub fn pixel_count(&self) -> u32 {
51 self.width * self.height
52 }
53
54 pub fn bounds(&self) -> Rectangle {
56 Rectangle {
57 x: self.x,
58 y: self.y,
59 width: self.width,
60 height: self.height,
61 }
62 }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum PixelFormat {
68 RGBA8,
70 BGRA8,
72 RGB8,
74 BGR8,
76 Gray8,
78 GrayA8,
80}
81
82impl PixelFormat {
83 pub fn bytes_per_pixel(&self) -> usize {
85 match self {
86 PixelFormat::RGBA8 | PixelFormat::BGRA8 => 4,
87 PixelFormat::RGB8 | PixelFormat::BGR8 => 3,
88 PixelFormat::GrayA8 => 2,
89 PixelFormat::Gray8 => 1,
90 }
91 }
92
93 pub fn has_alpha(&self) -> bool {
95 matches!(self, PixelFormat::RGBA8 | PixelFormat::BGRA8 | PixelFormat::GrayA8)
96 }
97
98 pub fn channel_count(&self) -> usize {
100 match self {
101 PixelFormat::RGBA8 | PixelFormat::BGRA8 => 4,
102 PixelFormat::RGB8 | PixelFormat::BGR8 => 3,
103 PixelFormat::GrayA8 => 2,
104 PixelFormat::Gray8 => 1,
105 }
106 }
107}
108
109impl fmt::Display for PixelFormat {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 match self {
112 PixelFormat::RGBA8 => write!(f, "RGBA8"),
113 PixelFormat::BGRA8 => write!(f, "BGRA8"),
114 PixelFormat::RGB8 => write!(f, "RGB8"),
115 PixelFormat::BGR8 => write!(f, "BGR8"),
116 PixelFormat::Gray8 => write!(f, "Gray8"),
117 PixelFormat::GrayA8 => write!(f, "GrayA8"),
118 }
119 }
120}
121
122#[derive(Debug, Clone)]
124pub struct RawImage {
125 pub data: Vec<u8>,
127 pub width: u32,
129 pub height: u32,
131 pub format: PixelFormat,
133 pub stride: usize,
135}
136
137impl RawImage {
138 pub fn new(data: Vec<u8>, width: u32, height: u32, format: PixelFormat) -> Self {
140 let stride = (width as usize) * format.bytes_per_pixel();
141 Self {
142 data,
143 width,
144 height,
145 format,
146 stride,
147 }
148 }
149
150 pub fn with_stride(
152 data: Vec<u8>,
153 width: u32,
154 height: u32,
155 format: PixelFormat,
156 stride: usize,
157 ) -> Self {
158 Self {
159 data,
160 width,
161 height,
162 format,
163 stride,
164 }
165 }
166
167 pub fn size(&self) -> usize {
169 self.data.len()
170 }
171
172 pub fn pixel_count(&self) -> usize {
174 (self.width * self.height) as usize
175 }
176
177 pub fn is_valid(&self) -> bool {
179 let expected_size = self.stride * (self.height as usize);
180 self.data.len() >= expected_size
181 }
182
183 pub fn get_pixel(&self, x: u32, y: u32) -> Option<&[u8]> {
185 if x >= self.width || y >= self.height {
186 return None;
187 }
188
189 let offset = (y as usize) * self.stride + (x as usize) * self.format.bytes_per_pixel();
190 let pixel_size = self.format.bytes_per_pixel();
191
192 self.data.get(offset..offset + pixel_size)
193 }
194}
195
196#[derive(Debug, Clone)]
198pub struct WebPConfig {
199 pub quality: u8,
201 pub method: u8,
203 pub lossless: bool,
205 pub near_lossless: u8,
207 pub segments: u8,
209 pub sns_strength: u8,
211 pub filter_strength: u8,
213 pub filter_sharpness: u8,
215 pub auto_filter: bool,
217 pub alpha_compression: bool,
219 pub alpha_filtering: u8,
221 pub alpha_quality: u8,
223 pub pass: u8,
225 pub thread_count: usize,
227 pub low_memory: bool,
229 pub exact: bool,
231}
232
233impl Default for WebPConfig {
234 fn default() -> Self {
235 Self {
236 quality: 80,
237 method: 4,
238 lossless: false,
239 near_lossless: 100,
240 segments: 4,
241 sns_strength: 50,
242 filter_strength: 60,
243 filter_sharpness: 0,
244 auto_filter: false,
245 alpha_compression: true,
246 alpha_filtering: 1,
247 alpha_quality: 100,
248 pass: 1,
249 thread_count: 0,
250 low_memory: false,
251 exact: false,
252 }
253 }
254}
255
256impl WebPConfig {
257 pub fn high_quality() -> Self {
259 Self {
260 quality: 95,
261 method: 6,
262 pass: 10,
263 ..Default::default()
264 }
265 }
266
267 pub fn fast() -> Self {
269 Self {
270 quality: 75,
271 method: 0,
272 pass: 1,
273 ..Default::default()
274 }
275 }
276
277 pub fn lossless() -> Self {
279 Self {
280 lossless: true,
281 quality: 100,
282 method: 6,
283 ..Default::default()
284 }
285 }
286
287 pub fn balanced() -> Self {
289 Self {
290 quality: 85,
291 method: 4,
292 pass: 6,
293 ..Default::default()
294 }
295 }
296
297 pub fn validate(&self) -> Result<(), String> {
299 if self.quality > 100 {
300 return Err(format!("Quality must be 0-100, got {}", self.quality));
301 }
302 if self.method > 6 {
303 return Err(format!("Method must be 0-6, got {}", self.method));
304 }
305 if self.segments < 1 || self.segments > 4 {
306 return Err(format!("Segments must be 1-4, got {}", self.segments));
307 }
308 if self.filter_sharpness > 7 {
309 return Err(format!(
310 "Filter sharpness must be 0-7, got {}",
311 self.filter_sharpness
312 ));
313 }
314 if self.alpha_filtering > 2 {
315 return Err(format!(
316 "Alpha filtering must be 0-2, got {}",
317 self.alpha_filtering
318 ));
319 }
320 if self.pass < 1 || self.pass > 10 {
321 return Err(format!("Pass must be 1-10, got {}", self.pass));
322 }
323 Ok(())
324 }
325}
326
327#[derive(Debug, Clone)]
329pub struct CaptureConfig {
330 pub webp_config: WebPConfig,
332 pub include_cursor: bool,
334 pub region: Option<CaptureRegion>,
336 pub use_hardware_acceleration: bool,
338 pub max_retries: u32,
340 pub retry_delay: Duration,
342 pub timeout: Duration,
344}
345
346impl Default for CaptureConfig {
347 fn default() -> Self {
348 Self {
349 webp_config: WebPConfig::default(),
350 include_cursor: false,
351 region: None,
352 use_hardware_acceleration: true,
353 max_retries: 3,
354 retry_delay: Duration::from_millis(100),
355 timeout: Duration::from_secs(5),
356 }
357 }
358}
359
360#[derive(Debug, Clone, Copy, PartialEq, Eq)]
362pub struct CaptureRegion {
363 pub x: i32,
364 pub y: i32,
365 pub width: u32,
366 pub height: u32,
367}
368
369impl CaptureRegion {
370 pub fn new(x: i32, y: i32, width: u32, height: u32) -> Self {
371 Self { x, y, width, height }
372 }
373
374 pub fn from_rect(rect: Rectangle) -> Self {
375 Self {
376 x: rect.x,
377 y: rect.y,
378 width: rect.width,
379 height: rect.height,
380 }
381 }
382}
383
384#[derive(Debug, Clone, Copy, PartialEq, Eq)]
386pub struct Rectangle {
387 pub x: i32,
388 pub y: i32,
389 pub width: u32,
390 pub height: u32,
391}
392
393impl Rectangle {
394 pub fn new(x: i32, y: i32, width: u32, height: u32) -> Self {
395 Self { x, y, width, height }
396 }
397
398 pub fn contains_point(&self, px: i32, py: i32) -> bool {
399 px >= self.x
400 && py >= self.y
401 && px < self.x + self.width as i32
402 && py < self.y + self.height as i32
403 }
404
405 pub fn area(&self) -> u32 {
406 self.width * self.height
407 }
408}
409
410#[derive(Debug, Clone)]
412pub struct Screenshot {
413 pub data: Vec<u8>,
415 pub width: u32,
417 pub height: u32,
419 pub display_index: usize,
421 pub metadata: CaptureMetadata,
423}
424
425impl Screenshot {
426 pub fn size(&self) -> usize {
428 self.data.len()
429 }
430
431 pub fn save(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
433 use std::fs::File;
434 use std::io::Write;
435
436 let mut file = File::create(path)?;
437 file.write_all(&self.data)?;
438 Ok(())
439 }
440}
441
442#[derive(Debug, Clone)]
444pub struct CaptureMetadata {
445 pub timestamp: SystemTime,
447 pub capture_duration: Duration,
449 pub encoding_duration: Duration,
451 pub original_size: usize,
453 pub compressed_size: usize,
455 pub implementation: String,
457}
458
459impl CaptureMetadata {
460 pub fn compression_ratio(&self) -> f64 {
462 if self.original_size == 0 {
463 0.0
464 } else {
465 self.compressed_size as f64 / self.original_size as f64
466 }
467 }
468
469 pub fn total_duration(&self) -> Duration {
471 self.capture_duration + self.encoding_duration
472 }
473
474 pub fn space_savings_percent(&self) -> f64 {
476 if self.original_size == 0 {
477 0.0
478 } else {
479 (1.0 - self.compression_ratio()) * 100.0
480 }
481 }
482}
483
484#[derive(Debug, Clone, Default)]
486pub struct PerformanceStats {
487 pub total_captures: u64,
488 pub successful_captures: u64,
489 pub failed_captures: u64,
490 pub total_bytes_captured: u64,
491 pub total_bytes_encoded: u64,
492 pub total_capture_time: Duration,
493 pub total_encoding_time: Duration,
494 pub fastest_capture: Duration,
495 pub slowest_capture: Duration,
496}
497
498impl PerformanceStats {
499 pub fn success_rate(&self) -> f64 {
501 if self.total_captures == 0 {
502 0.0
503 } else {
504 (self.successful_captures as f64 / self.total_captures as f64) * 100.0
505 }
506 }
507
508 pub fn average_capture_time(&self) -> Duration {
510 if self.successful_captures == 0 {
511 Duration::ZERO
512 } else {
513 self.total_capture_time / self.successful_captures as u32
514 }
515 }
516
517 pub fn average_compression_ratio(&self) -> f64 {
519 if self.total_bytes_captured == 0 {
520 0.0
521 } else {
522 self.total_bytes_encoded as f64 / self.total_bytes_captured as f64
523 }
524 }
525}
526
527#[cfg(test)]
528mod tests {
529 use super::*;
530
531 #[test]
532 fn test_pixel_format_bytes_per_pixel() {
533 assert_eq!(PixelFormat::RGBA8.bytes_per_pixel(), 4);
534 assert_eq!(PixelFormat::RGB8.bytes_per_pixel(), 3);
535 assert_eq!(PixelFormat::Gray8.bytes_per_pixel(), 1);
536 }
537
538 #[test]
539 fn test_webp_config_validation() {
540 let mut config = WebPConfig::default();
541 assert!(config.validate().is_ok());
542
543 config.quality = 101;
544 assert!(config.validate().is_err());
545 }
546
547 #[test]
548 fn test_raw_image_pixel_access() {
549 let data = vec![255; 1920 * 1080 * 4];
550 let image = RawImage::new(data, 1920, 1080, PixelFormat::RGBA8);
551
552 let pixel = image.get_pixel(0, 0).unwrap();
553 assert_eq!(pixel, &[255, 255, 255, 255]);
554
555 assert!(image.get_pixel(1920, 0).is_none());
556 }
557}