grim_rs/lib.rs
1//! # grim-rs
2//!
3//! A pure Rust implementation of the grim screenshot utility for Wayland.
4//!
5//! This library provides a simple interface for taking screenshots on Wayland
6//! compositors that support the `wlr-screencopy` protocol.
7//!
8//! ## Features
9//!
10//! - Capture entire screen (all outputs)
11//! - Capture specific output by name
12//! - Capture specific region
13//! - Capture multiple outputs with different parameters
14//! - Save screenshots as PNG or JPEG
15//! - Get screenshot data as PNG or JPEG bytes
16//!
17//! ## Example
18//!
19//! ```rust,no_run
20//! use grim_rs::Grim;
21//! use chrono::Local;
22//!
23//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
24//! let mut grim = Grim::new()?;
25//! let result = grim.capture_all()?;
26//!
27//! // Generate timestamped filename (like grim-rs does by default)
28//! let filename = format!("{}_grim.png", Local::now().format("%Y%m%d_%Hh%Mm%Ss"));
29//! grim.save_png(result.data(), result.width(), result.height(), &filename)?;
30//! # Ok(())
31//! # }
32//! ```
33
34pub mod error;
35pub mod geometry;
36
37mod wayland_capture;
38
39pub use error::{Error, Result};
40pub use geometry::Box;
41
42use wayland_capture::WaylandCapture as PlatformCapture;
43
44/// Result of a screenshot capture operation.
45///
46/// Contains the raw image data and dimensions of the captured area.
47#[derive(Debug, Clone)]
48pub struct CaptureResult {
49 /// Raw RGBA image data.
50 ///
51 /// Each pixel is represented by 4 bytes in RGBA format (Red, Green, Blue, Alpha).
52 data: Vec<u8>,
53 /// Width of the captured image in pixels.
54 width: u32,
55 /// Height of the captured image in pixels.
56 height: u32,
57}
58
59impl CaptureResult {
60 pub fn new(data: Vec<u8>, width: u32, height: u32) -> Self {
61 Self {
62 data,
63 width,
64 height,
65 }
66 }
67
68 pub fn data(&self) -> &[u8] {
69 &self.data
70 }
71
72 pub fn width(&self) -> u32 {
73 self.width
74 }
75
76 pub fn height(&self) -> u32 {
77 self.height
78 }
79
80 pub fn into_data(self) -> Vec<u8> {
81 self.data
82 }
83}
84
85/// Information about a display output.
86#[derive(Debug, Clone)]
87pub struct Output {
88 /// Name of the output (e.g., "eDP-1", "HDMI-A-1").
89 name: String,
90 /// Geometry of the output (position and size).
91 geometry: Box,
92 /// Scale factor of the output (e.g., 1 for normal DPI, 2 for HiDPI).
93 scale: i32,
94 /// Description of the output (e.g., monitor model, manufacturer info).
95 description: Option<String>,
96}
97
98impl Output {
99 pub fn name(&self) -> &str {
100 &self.name
101 }
102
103 pub fn geometry(&self) -> &Box {
104 &self.geometry
105 }
106
107 pub fn scale(&self) -> i32 {
108 self.scale
109 }
110
111 pub fn description(&self) -> Option<&str> {
112 self.description.as_deref()
113 }
114}
115
116/// Parameters for capturing a specific output.
117///
118/// Allows specifying different capture parameters for each output when
119///
120/// capturing multiple outputs simultaneously.
121#[derive(Debug, Clone)]
122pub struct CaptureParameters {
123 /// Name of the output to capture.
124 ///
125 /// Must match one of the names returned by [`Grim::get_outputs`].
126 output_name: String,
127 /// Optional region within the output to capture.
128 ///
129 /// If `None`, the entire output will be captured.
130 ///
131 /// If `Some(region)`, only the specified region will be captured.
132 ///
133 /// The region must be within the bounds of the output.
134 region: Option<Box>,
135 /// Whether to include the cursor in the capture.
136 ///
137 /// If `true`, the cursor will be included in the screenshot.
138 ///
139 /// If `false`, the cursor will be excluded from the screenshot.
140 overlay_cursor: bool,
141 /// Scale factor for the output image.
142 ///
143 /// If `None`, uses the default scale (typically the highest output scale).
144 ///
145 /// If `Some(scale)`, the output image will be scaled accordingly.
146 scale: Option<f64>,
147}
148
149impl CaptureParameters {
150 /// Creates a new CaptureParameters with the specified output name.
151 ///
152 /// By default, captures the entire output without cursor and with default scale.
153 pub fn new(output_name: impl Into<String>) -> Self {
154 Self {
155 output_name: output_name.into(),
156 region: None,
157 overlay_cursor: false,
158 scale: None,
159 }
160 }
161
162 /// Sets the region to capture within the output.
163 pub fn region(mut self, region: Box) -> Self {
164 self.region = Some(region);
165 self
166 }
167
168 /// Sets whether to include the cursor in the capture.
169 pub fn overlay_cursor(mut self, overlay_cursor: bool) -> Self {
170 self.overlay_cursor = overlay_cursor;
171 self
172 }
173
174 /// Sets the scale factor for the output image.
175 pub fn scale(mut self, scale: f64) -> Self {
176 self.scale = Some(scale);
177 self
178 }
179
180 /// Returns the output name.
181 pub fn output_name(&self) -> &str {
182 &self.output_name
183 }
184
185 /// Returns the region, if set.
186 pub fn region_ref(&self) -> Option<&Box> {
187 self.region.as_ref()
188 }
189
190 /// Returns whether cursor overlay is enabled.
191 pub fn overlay_cursor_enabled(&self) -> bool {
192 self.overlay_cursor
193 }
194
195 /// Returns the scale factor, if set.
196 pub fn scale_factor(&self) -> Option<f64> {
197 self.scale
198 }
199}
200
201/// Result of capturing multiple outputs.
202///
203/// Contains a map of output names to their respective capture results.
204#[derive(Debug, Clone)]
205pub struct MultiOutputCaptureResult {
206 /// Map of output names to their capture results.
207 ///
208 /// The keys are output names, and the values are the corresponding
209 /// capture results for each output.
210 outputs: std::collections::HashMap<String, CaptureResult>,
211}
212
213impl MultiOutputCaptureResult {
214 /// Creates a new MultiOutputCaptureResult with the given outputs map.
215 pub fn new(outputs: std::collections::HashMap<String, CaptureResult>) -> Self {
216 Self { outputs }
217 }
218
219 /// Gets the capture result for the specified output name.
220 pub fn get(&self, output_name: &str) -> Option<&CaptureResult> {
221 self.outputs.get(output_name)
222 }
223
224 /// Returns a reference to the outputs map.
225 pub fn outputs(&self) -> &std::collections::HashMap<String, CaptureResult> {
226 &self.outputs
227 }
228
229 /// Consumes self and returns the outputs map.
230 pub fn into_outputs(self) -> std::collections::HashMap<String, CaptureResult> {
231 self.outputs
232 }
233}
234
235/// Main interface for taking screenshots.
236///
237/// Provides methods for capturing screenshots of the entire screen,
238/// specific outputs, regions, or multiple outputs with different parameters.
239pub struct Grim {
240 platform_capture: PlatformCapture,
241}
242
243impl Grim {
244 /// Create a new Grim instance.
245 ///
246 /// Establishes a connection to the Wayland compositor and initializes
247 /// the necessary protocols for screen capture.
248 ///
249 /// # Errors
250 ///
251 /// Returns an error if:
252 /// - Cannot connect to the Wayland compositor
253 /// - Required Wayland protocols are not available
254 /// - Other initialization errors occur
255 ///
256 /// # Example
257 ///
258 /// ```rust
259 /// use grim_rs::Grim;
260 ///
261 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
262 /// let grim = Grim::new()?;
263 /// # Ok(())
264 /// # }
265 /// ```
266 pub fn new() -> Result<Self> {
267 let platform_capture = PlatformCapture::new()?;
268 Ok(Self { platform_capture })
269 }
270
271 /// Get information about available display outputs.
272 ///
273 /// Returns a list of all connected display outputs with their names,
274 /// geometries, and scale factors.
275 ///
276 /// # Errors
277 ///
278 /// Returns an error if:
279 /// - No outputs are available
280 /// - Failed to retrieve output information
281 ///
282 /// # Example
283 ///
284 /// ```rust
285 /// use grim_rs::Grim;
286 ///
287 /// let mut grim = Grim::new()?;
288 /// let outputs = grim.get_outputs()?;
289 ///
290 /// for output in outputs {
291 /// println!("Output: {} ({}x{})", output.name(), output.geometry().width(), output.geometry().height());
292 /// }
293 /// # Ok::<(), grim_rs::Error>(())
294 /// ```
295 pub fn get_outputs(&mut self) -> Result<Vec<Output>> {
296 self.platform_capture.get_outputs()
297 }
298
299 /// Capture the entire screen (all outputs).
300 ///
301 /// Captures a screenshot that includes all connected display outputs,
302 /// arranged according to their physical positions.
303 ///
304 /// # Errors
305 ///
306 /// Returns an error if:
307 /// - No outputs are available
308 /// - Failed to capture the screen
309 /// - Buffer creation failed
310 ///
311 /// # Example
312 ///
313 /// ```rust
314 /// use grim_rs::Grim;
315 ///
316 /// let mut grim = Grim::new()?;
317 /// let result = grim.capture_all()?;
318 /// println!("Captured screen: {}x{}", result.width(), result.height());
319 /// # Ok::<(), grim_rs::Error>(())
320 /// ```
321 pub fn capture_all(&mut self) -> Result<CaptureResult> {
322 self.platform_capture.capture_all()
323 }
324
325 /// Capture the entire screen (all outputs) with specified scale factor.
326 ///
327 /// Captures a screenshot that includes all connected display outputs,
328 /// arranged according to their physical positions, with a specified scale factor.
329 ///
330 /// # Arguments
331 ///
332 /// * `scale` - Scale factor for the output image
333 ///
334 /// # Errors
335 ///
336 /// Returns an error if:
337 /// - No outputs are available
338 /// - Failed to capture the screen
339 /// - Buffer creation failed
340 ///
341 /// # Example
342 ///
343 /// ```rust
344 /// use grim_rs::Grim;
345 ///
346 /// let mut grim = Grim::new()?;
347 /// let result = grim.capture_all_with_scale(1.0)?;
348 /// println!("Captured screen: {}x{}", result.width(), result.height());
349 /// # Ok::<(), grim_rs::Error>(())
350 /// ```
351 pub fn capture_all_with_scale(&mut self, scale: f64) -> Result<CaptureResult> {
352 self.platform_capture.capture_all_with_scale(scale)
353 }
354
355 /// Capture a specific output by name.
356 ///
357 /// Captures a screenshot of the specified display output.
358 ///
359 /// # Arguments
360 ///
361 /// * `output_name` - Name of the output to capture (e.g., "eDP-1", "HDMI-A-1")
362 ///
363 /// # Errors
364 ///
365 /// Returns an error if:
366 /// - The specified output is not found
367 /// - Failed to capture the output
368 /// - Buffer creation failed
369 ///
370 /// # Example
371 ///
372 /// ```rust
373 /// use grim_rs::Grim;
374 ///
375 /// let mut grim = Grim::new()?;
376 /// // Get available outputs first
377 /// let outputs = grim.get_outputs()?;
378 /// if let Some(output) = outputs.first() {
379 /// let result = grim.capture_output(output.name())?;
380 /// println!("Captured output: {}x{}", result.width(), result.height());
381 /// }
382 /// # Ok::<(), grim_rs::Error>(())
383 /// ```
384 pub fn capture_output(&mut self, output_name: &str) -> Result<CaptureResult> {
385 self.platform_capture.capture_output(output_name)
386 }
387
388 /// Capture a specific output by name with specified scale factor.
389 ///
390 /// Captures a screenshot of the specified display output with a specified scale factor.
391 ///
392 /// # Arguments
393 ///
394 /// * `output_name` - Name of the output to capture (e.g., "eDP-1", "HDMI-A-1")
395 /// * `scale` - Scale factor for the output image
396 ///
397 /// # Errors
398 ///
399 /// Returns an error if:
400 /// - The specified output is not found
401 /// - Failed to capture the output
402 /// - Buffer creation failed
403 ///
404 /// # Example
405 ///
406 /// ```rust
407 /// use grim_rs::Grim;
408 ///
409 /// let mut grim = Grim::new()?;
410 /// // Get available outputs first
411 /// let outputs = grim.get_outputs()?;
412 /// if let Some(output) = outputs.first() {
413 /// let result = grim.capture_output_with_scale(output.name(), 0.5)?;
414 /// println!("Captured output at 50% scale: {}x{}", result.width(), result.height());
415 /// }
416 /// # Ok::<(), grim_rs::Error>(())
417 /// ```
418 pub fn capture_output_with_scale(
419 &mut self,
420 output_name: &str,
421 scale: f64,
422 ) -> Result<CaptureResult> {
423 self.platform_capture
424 .capture_output_with_scale(output_name, scale)
425 }
426
427 /// Capture a specific region.
428 ///
429 /// Captures a screenshot of the specified rectangular region.
430 ///
431 /// # Arguments
432 ///
433 /// * `region` - The region to capture, specified as a [`Box`]
434 ///
435 /// # Errors
436 ///
437 /// Returns an error if:
438 /// - No outputs are available
439 /// - Failed to capture the region
440 /// - Buffer creation failed
441 ///
442 /// # Example
443 ///
444 /// ```rust
445 /// use grim_rs::{Grim, Box};
446 ///
447 /// let mut grim = Grim::new()?;
448 /// // x=100, y=100, width=800, height=600
449 /// let region = Box::new(100, 100, 800, 600);
450 /// let result = grim.capture_region(region)?;
451 /// println!("Captured region: {}x{}", result.width(), result.height());
452 /// # Ok::<(), grim_rs::Error>(())
453 /// ```
454 pub fn capture_region(&mut self, region: Box) -> Result<CaptureResult> {
455 self.platform_capture.capture_region(region)
456 }
457
458 /// Capture a specific region with specified scale factor.
459 ///
460 /// Captures a screenshot of the specified rectangular region with a specified scale factor.
461 ///
462 /// # Arguments
463 ///
464 /// * `region` - The region to capture, specified as a [`Box`]
465 /// * `scale` - Scale factor for the output image
466 ///
467 /// # Errors
468 ///
469 /// Returns an error if:
470 /// - No outputs are available
471 /// - Failed to capture the region
472 /// - Buffer creation failed
473 ///
474 /// # Example
475 ///
476 /// ```rust
477 /// use grim_rs::{Grim, Box};
478 ///
479 /// let mut grim = Grim::new()?;
480 /// // x=100, y=100, width=800, height=600
481 /// let region = Box::new(100, 100, 800, 600);
482 /// let result = grim.capture_region_with_scale(region, 1.0)?;
483 /// println!("Captured region: {}x{}", result.width(), result.height());
484 /// # Ok::<(), grim_rs::Error>(())
485 /// ```
486 pub fn capture_region_with_scale(&mut self, region: Box, scale: f64) -> Result<CaptureResult> {
487 self.platform_capture
488 .capture_region_with_scale(region, scale)
489 }
490
491 /// Capture multiple outputs with different parameters.
492 ///
493 /// Captures screenshots of multiple outputs simultaneously, each with
494 /// potentially different parameters (region, cursor inclusion, etc.).
495 ///
496 /// # Arguments
497 ///
498 /// * `parameters` - Vector of [`CaptureParameters`] specifying what to capture
499 /// from each output
500 ///
501 /// # Errors
502 ///
503 /// Returns an error if:
504 /// - Any specified output is not found
505 /// - Any specified region is outside the bounds of its output
506 /// - Failed to capture any of the outputs
507 /// - Buffer creation failed
508 ///
509 /// # Example
510 ///
511 /// ```rust
512 /// use grim_rs::{Grim, CaptureParameters, Box};
513 ///
514 /// let mut grim = Grim::new()?;
515 ///
516 /// // Get available outputs
517 /// let outputs = grim.get_outputs()?;
518 ///
519 /// // Prepare capture parameters for multiple outputs
520 /// let mut parameters = vec![
521 /// CaptureParameters::new(outputs[0].name())
522 /// .overlay_cursor(true)
523 /// ];
524 ///
525 /// // If we have a second output, capture a region of it
526 /// if outputs.len() > 1 {
527 /// let region = Box::new(0, 0, 400, 300);
528 /// parameters.push(
529 /// CaptureParameters::new(outputs[1].name())
530 /// .region(region)
531 /// );
532 /// }
533 ///
534 /// // Capture all specified outputs
535 /// let results = grim.capture_outputs(parameters)?;
536 /// println!("Captured {} outputs", results.outputs().len());
537 /// # Ok::<(), grim_rs::Error>(())
538 /// ```
539 pub fn capture_outputs(
540 &mut self,
541 parameters: Vec<CaptureParameters>,
542 ) -> Result<MultiOutputCaptureResult> {
543 self.platform_capture.capture_outputs(parameters)
544 }
545
546 /// Capture outputs with scale factor.
547 ///
548 /// Captures screenshots of multiple outputs simultaneously with a specific scale factor.
549 ///
550 /// # Arguments
551 ///
552 /// * `parameters` - Vector of CaptureParameters with scale factors
553 /// * `default_scale` - Default scale factor to use when not specified in parameters
554 ///
555 /// # Errors
556 ///
557 /// Returns an error if any of the outputs can't be captured
558 pub fn capture_outputs_with_scale(
559 &mut self,
560 parameters: Vec<CaptureParameters>,
561 default_scale: f64,
562 ) -> Result<MultiOutputCaptureResult> {
563 self.platform_capture
564 .capture_outputs_with_scale(parameters, default_scale)
565 }
566
567 /// Save captured data as PNG.
568 ///
569 /// Saves the captured image data to a PNG file.
570 ///
571 /// # Arguments
572 ///
573 /// * `data` - Raw RGBA image data from a capture result
574 /// * `width` - Width of the image in pixels
575 /// * `height` - Height of the image in pixels
576 /// * `path` - Path where to save the PNG file
577 ///
578 /// # Errors
579 ///
580 /// Returns an error if:
581 /// - Failed to create or write to the file
582 /// - Image processing failed
583 ///
584 /// # Example
585 ///
586 /// ```rust,no_run
587 /// use grim_rs::Grim;
588 /// use chrono::Local;
589 ///
590 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
591 /// let mut grim = Grim::new()?;
592 /// let result = grim.capture_all()?;
593 ///
594 /// // Generate timestamped filename
595 /// let filename = format!("{}_grim.png", Local::now().format("%Y%m%d_%Hh%Mm%Ss"));
596 /// grim.save_png(result.data(), result.width(), result.height(), &filename)?;
597 /// # Ok(())
598 /// # }
599 /// ```
600 pub fn save_png<P: AsRef<std::path::Path>>(
601 &self,
602 data: &[u8],
603 width: u32,
604 height: u32,
605 path: P,
606 ) -> Result<()> {
607 self.save_png_with_compression(data, width, height, path, 6) // Default compression level of 6
608 }
609
610 /// Save captured data as PNG with compression level control.
611 ///
612 /// Saves the captured image data to a PNG file with specified compression level.
613 ///
614 /// # Arguments
615 ///
616 /// * `data` - Raw RGBA image data from a capture result
617 /// * `width` - Width of the image in pixels
618 /// * `height` - Height of the image in pixels
619 /// * `path` - Path where to save the PNG file
620 /// * `compression` - PNG compression level (0-9, where 9 is highest compression)
621 ///
622 /// # Errors
623 ///
624 /// Returns an error if:
625 /// - Failed to create or write to the file
626 /// - Image processing failed
627 ///
628 /// # Example
629 ///
630 /// ```rust,no_run
631 /// use grim_rs::Grim;
632 /// use chrono::Local;
633 ///
634 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
635 /// let mut grim = Grim::new()?;
636 /// let result = grim.capture_all()?;
637 ///
638 /// // Generate timestamped filename
639 /// let filename = format!("{}_grim.png", Local::now().format("%Y%m%d_%Hh%Mm%Ss"));
640 /// grim.save_png_with_compression(result.data(), result.width(), result.height(), &filename, 9)?;
641 /// # Ok(())
642 /// # }
643 /// ```
644 pub fn save_png_with_compression<P: AsRef<std::path::Path>>(
645 &self,
646 data: &[u8],
647 width: u32,
648 height: u32,
649 path: P,
650 compression: u8,
651 ) -> Result<()> {
652 use image::{ImageBuffer, Rgba};
653 use std::io::BufWriter;
654
655 let _img = ImageBuffer::<Rgba<u8>, _>::from_raw(width, height, data.to_vec()).ok_or(
656 Error::ImageProcessing(image::ImageError::Parameter(
657 image::error::ParameterError::from_kind(
658 image::error::ParameterErrorKind::DimensionMismatch,
659 ),
660 )),
661 )?;
662
663 let file = std::fs::File::create(&path).map_err(|e| Error::IoWithContext {
664 operation: format!("creating output file '{}'", path.as_ref().display()),
665 source: e,
666 })?;
667 let writer = BufWriter::new(file);
668 let mut encoder = png::Encoder::new(writer, width, height);
669
670 let compression_level = match compression {
671 0 => png::Compression::Fast,
672 1..=3 => png::Compression::Best,
673 4..=6 => png::Compression::Default,
674 7..=9 => png::Compression::Best,
675 _ => png::Compression::Default,
676 };
677 encoder.set_compression(compression_level);
678
679 encoder.set_color(png::ColorType::Rgba);
680 encoder.set_filter(png::FilterType::NoFilter);
681
682 let mut writer = encoder.write_header().map_err(|e| {
683 Error::Io(std::io::Error::new(
684 std::io::ErrorKind::Other,
685 format!("PNG encoding error: {}", e),
686 ))
687 })?;
688
689 writer.write_image_data(data).map_err(|e| {
690 Error::Io(std::io::Error::new(
691 std::io::ErrorKind::Other,
692 format!("PNG encoding error: {}", e),
693 ))
694 })?;
695 writer.finish().map_err(|e| {
696 Error::Io(std::io::Error::new(
697 std::io::ErrorKind::Other,
698 format!("PNG encoding error: {}", e),
699 ))
700 })?;
701
702 Ok(())
703 }
704
705 /// Save captured data as JPEG.
706 ///
707 /// Saves the captured image data to a JPEG file.
708 ///
709 /// This function is only available when the `jpeg` feature is enabled.
710 ///
711 /// # Arguments
712 ///
713 /// * `data` - Raw RGBA image data from a capture result
714 /// * `width` - Width of the image in pixels
715 /// * `height` - Height of the image in pixels
716 /// * `path` - Path where to save the JPEG file
717 ///
718 /// # Errors
719 ///
720 /// Returns an error if:
721 /// - Failed to create or write to the file
722 /// - Image processing failed
723 /// - JPEG support is not enabled (when feature is disabled)
724 ///
725 /// # Example
726 ///
727 /// ```rust,no_run
728 /// use grim_rs::Grim;
729 /// use chrono::Local;
730 ///
731 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
732 /// let mut grim = Grim::new()?;
733 /// let result = grim.capture_all()?;
734 ///
735 /// // Generate timestamped filename
736 /// let filename = format!("{}_grim.jpg", Local::now().format("%Y%m%d_%Hh%Mm%Ss"));
737 /// grim.save_jpeg(result.data(), result.width(), result.height(), &filename)?;
738 /// # Ok(())
739 /// # }
740 /// ```
741 #[cfg(feature = "jpeg")]
742 pub fn save_jpeg<P: AsRef<std::path::Path>>(
743 &self,
744 data: &[u8],
745 width: u32,
746 height: u32,
747 path: P,
748 ) -> Result<()> {
749 self.save_jpeg_with_quality(data, width, height, path, 80)
750 }
751
752 /// Save captured data as JPEG with quality control.
753 ///
754 /// Saves the captured image data to a JPEG file with specified quality.
755 ///
756 /// This function is only available when the `jpeg` feature is enabled.
757 ///
758 /// # Arguments
759 ///
760 /// * `data` - Raw RGBA image data from a capture result
761 /// * `width` - Width of the image in pixels
762 /// * `height` - Height of the image in pixels
763 /// * `path` - Path where to save the JPEG file
764 /// * `quality` - JPEG quality level (0-100, where 100 is highest quality)
765 ///
766 /// # Errors
767 ///
768 /// Returns an error if:
769 /// - Failed to create or write to the file
770 /// - Image processing failed
771 /// - JPEG support is not enabled (when feature is disabled)
772 ///
773 /// # Example
774 ///
775 /// ```rust,no_run
776 /// use grim_rs::Grim;
777 /// use chrono::Local;
778 ///
779 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
780 /// let mut grim = Grim::new()?;
781 /// let result = grim.capture_all()?;
782 ///
783 /// // Generate timestamped filename
784 /// let filename = format!("{}_grim.jpg", Local::now().format("%Y%m%d_%Hh%Mm%Ss"));
785 /// grim.save_jpeg_with_quality(result.data(), result.width(), result.height(), &filename, 90)?;
786 /// # Ok(())
787 /// # }
788 /// ```
789 #[cfg(feature = "jpeg")]
790 pub fn save_jpeg_with_quality<P: AsRef<std::path::Path>>(
791 &self,
792 data: &[u8],
793 width: u32,
794 height: u32,
795 path: P,
796 quality: u8,
797 ) -> Result<()> {
798 use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, Rgba};
799
800 let rgba_img = ImageBuffer::<Rgba<u8>, _>::from_raw(width, height, data.to_vec()).ok_or(
801 Error::ImageProcessing(image::ImageError::Parameter(
802 image::error::ParameterError::from_kind(
803 image::error::ParameterErrorKind::DimensionMismatch,
804 ),
805 )),
806 )?;
807
808 let rgb_img: ImageBuffer<Rgb<u8>, Vec<u8>> = rgba_img.convert();
809
810 let mut output_file = std::fs::File::create(&path).map_err(|e| Error::IoWithContext {
811 operation: format!("creating output file '{}'", path.as_ref().display()),
812 source: e,
813 })?;
814 let mut _encoder = jpeg_encoder::Encoder::new(&mut output_file, quality);
815 let rgb_data = rgb_img.as_raw();
816
817 _encoder
818 .encode(
819 rgb_data,
820 width as u16,
821 height as u16,
822 jpeg_encoder::ColorType::Rgb,
823 )
824 .map_err(|e| {
825 Error::Io(std::io::Error::new(
826 std::io::ErrorKind::Other,
827 format!("JPEG encoding error: {}", e),
828 ))
829 })?;
830
831 Ok(())
832 }
833
834 /// Save captured data as JPEG (stub when feature is disabled).
835 ///
836 /// This stub is used when the `jpeg` feature is disabled.
837 ///
838 /// # Errors
839 ///
840 /// Always returns an error indicating that JPEG support is not enabled.
841 #[cfg(not(feature = "jpeg"))]
842 pub fn save_jpeg<P: AsRef<std::path::Path>>(
843 &self,
844 _data: &[u8],
845 _width: u32,
846 _height: u32,
847 _path: P,
848 ) -> Result<()> {
849 Err(Error::ImageProcessing(image::ImageError::Unsupported(
850 image::error::UnsupportedError::from_format_and_kind(
851 image::error::ImageFormatHint::Name("JPEG".to_string()),
852 image::error::UnsupportedErrorKind::Format(image::ImageFormat::Jpeg.into()),
853 ),
854 )))
855 }
856
857 /// Save captured data as JPEG with quality control (stub when feature is disabled).
858 ///
859 /// This stub is used when the `jpeg` feature is disabled.
860 ///
861 /// # Errors
862 ///
863 /// Always returns an error indicating that JPEG support is not enabled.
864 #[cfg(not(feature = "jpeg"))]
865 pub fn save_jpeg_with_quality<P: AsRef<std::path::Path>>(
866 &self,
867 _data: &[u8],
868 _width: u32,
869 _height: u32,
870 _path: P,
871 _quality: u8,
872 ) -> Result<()> {
873 Err(Error::ImageProcessing(image::ImageError::Unsupported(
874 image::error::UnsupportedError::from_format_and_kind(
875 image::error::ImageFormatHint::Name("JPEG".to_string()),
876 image::error::UnsupportedErrorKind::Format(image::ImageFormat::Jpeg.into()),
877 ),
878 )))
879 }
880
881 /// Get image data as JPEG bytes.
882 ///
883 /// Converts the captured image data to JPEG format and returns the bytes.
884 ///
885 /// This function is only available when the `jpeg` feature is enabled.
886 ///
887 /// # Arguments
888 ///
889 /// * `data` - Raw RGBA image data from a capture result
890 /// * `width` - Width of the image in pixels
891 /// * `height` - Height of the image in pixels
892 ///
893 /// # Returns
894 ///
895 /// Returns the JPEG-encoded image data as a vector of bytes.
896 ///
897 /// # Errors
898 ///
899 /// Returns an error if:
900 /// - Image processing failed
901 /// - JPEG support is not enabled (when feature is disabled)
902 ///
903 /// # Example
904 ///
905 /// ```rust
906 /// use grim_rs::Grim;
907 ///
908 /// let mut grim = Grim::new()?;
909 /// let result = grim.capture_all()?;
910 /// let jpeg_bytes = grim.to_jpeg(result.data(), result.width(), result.height())?;
911 /// println!("JPEG data size: {} bytes", jpeg_bytes.len());
912 /// # Ok::<(), grim_rs::Error>(())
913 /// ```
914 #[cfg(feature = "jpeg")]
915 pub fn to_jpeg(&self, data: &[u8], width: u32, height: u32) -> Result<Vec<u8>> {
916 self.to_jpeg_with_quality(data, width, height, 80)
917 }
918
919 /// Get image data as JPEG bytes with quality control.
920 ///
921 /// Converts the captured image data to JPEG format with specified quality and returns the bytes.
922 ///
923 /// This function is only available when the `jpeg` feature is enabled.
924 ///
925 /// # Arguments
926 ///
927 /// * `data` - Raw RGBA image data from a capture result
928 /// * `width` - Width of the image in pixels
929 /// * `height` - Height of the image in pixels
930 /// * `quality` - JPEG quality level (0-100, where 100 is highest quality)
931 ///
932 /// # Returns
933 ///
934 /// Returns the JPEG-encoded image data as a vector of bytes.
935 ///
936 /// # Errors
937 ///
938 /// Returns an error if:
939 /// - Image processing failed
940 /// - JPEG support is not enabled (when feature is disabled)
941 ///
942 /// # Example
943 ///
944 /// ```rust
945 /// use grim_rs::Grim;
946 ///
947 /// let mut grim = Grim::new()?;
948 /// let result = grim.capture_all()?;
949 /// let jpeg_bytes = grim.to_jpeg_with_quality(result.data(), result.width(), result.height(), 90)?;
950 /// println!("JPEG data size: {} bytes", jpeg_bytes.len());
951 /// # Ok::<(), grim_rs::Error>(())
952 /// ```
953 #[cfg(feature = "jpeg")]
954 pub fn to_jpeg_with_quality(
955 &self,
956 data: &[u8],
957 width: u32,
958 height: u32,
959 quality: u8,
960 ) -> Result<Vec<u8>> {
961 use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, Rgba};
962
963 let rgba_img = ImageBuffer::<Rgba<u8>, _>::from_raw(width, height, data.to_vec()).ok_or(
964 Error::ImageProcessing(image::ImageError::Parameter(
965 image::error::ParameterError::from_kind(
966 image::error::ParameterErrorKind::DimensionMismatch,
967 ),
968 )),
969 )?;
970
971 let rgb_img: ImageBuffer<Rgb<u8>, Vec<u8>> = rgba_img.convert();
972
973 let mut jpeg_data = Vec::new();
974 let mut _encoder = jpeg_encoder::Encoder::new(&mut jpeg_data, quality);
975 let rgb_data = rgb_img.as_raw();
976
977 _encoder
978 .encode(
979 rgb_data,
980 width as u16,
981 height as u16,
982 jpeg_encoder::ColorType::Rgb,
983 )
984 .map_err(|e| {
985 Error::Io(std::io::Error::new(
986 std::io::ErrorKind::Other,
987 format!("JPEG encoding error: {}", e),
988 ))
989 })?;
990
991 Ok(jpeg_data)
992 }
993
994 /// Get image data as JPEG bytes (stub when feature is disabled).
995 ///
996 /// This stub is used when the `jpeg` feature is disabled.
997 ///
998 /// # Errors
999 ///
1000 /// Always returns an error indicating that JPEG support is not enabled.
1001 #[cfg(not(feature = "jpeg"))]
1002 pub fn to_jpeg(&self, _data: &[u8], _width: u32, _height: u32) -> Result<Vec<u8>> {
1003 Err(Error::ImageProcessing(image::ImageError::Unsupported(
1004 image::error::UnsupportedError::from_format_and_kind(
1005 image::error::ImageFormatHint::Name("JPEG".to_string()),
1006 image::error::UnsupportedErrorKind::Format(image::ImageFormat::Jpeg.into()),
1007 ),
1008 )))
1009 }
1010
1011 /// Get image data as JPEG bytes with quality control (stub when feature is disabled).
1012 ///
1013 /// This stub is used when the `jpeg` feature is disabled.
1014 ///
1015 /// # Errors
1016 ///
1017 /// Always returns an error indicating that JPEG support is not enabled.
1018 #[cfg(not(feature = "jpeg"))]
1019 pub fn to_jpeg_with_quality(
1020 &self,
1021 _data: &[u8],
1022 _width: u32,
1023 _height: u32,
1024 _quality: u8,
1025 ) -> Result<Vec<u8>> {
1026 Err(Error::ImageProcessing(image::ImageError::Unsupported(
1027 image::error::UnsupportedError::from_format_and_kind(
1028 image::error::ImageFormatHint::Name("JPEG".to_string()),
1029 image::error::UnsupportedErrorKind::Format(image::ImageFormat::Jpeg.into()),
1030 ),
1031 )))
1032 }
1033
1034 /// Get image data as PNG bytes.
1035 ///
1036 /// Converts the captured image data to PNG format and returns the bytes.
1037 ///
1038 /// # Arguments
1039 ///
1040 /// * `data` - Raw RGBA image data from a capture result
1041 /// * `width` - Width of the image in pixels
1042 /// * `height` - Height of the image in pixels
1043 ///
1044 /// # Returns
1045 ///
1046 /// Returns the PNG-encoded image data as a vector of bytes.
1047 ///
1048 /// # Errors
1049 ///
1050 /// Returns an error if:
1051 /// - Image processing failed
1052 ///
1053 /// # Example
1054 ///
1055 /// ```rust
1056 /// use grim_rs::Grim;
1057 ///
1058 /// let mut grim = Grim::new()?;
1059 /// let result = grim.capture_all()?;
1060 /// let png_bytes = grim.to_png(result.data(), result.width(), result.height())?;
1061 /// println!("PNG data size: {} bytes", png_bytes.len());
1062 /// # Ok::<(), grim_rs::Error>(())
1063 /// ```
1064 pub fn to_png(&self, data: &[u8], width: u32, height: u32) -> Result<Vec<u8>> {
1065 self.to_png_with_compression(data, width, height, 6)
1066 }
1067
1068 /// Get image data as PNG bytes with compression level control.
1069 ///
1070 /// Converts the captured image data to PNG format with specified compression level and returns the bytes.
1071 ///
1072 /// # Arguments
1073 ///
1074 /// * `data` - Raw RGBA image data from a capture result
1075 /// * `width` - Width of the image in pixels
1076 /// * `height` - Height of the image in pixels
1077 /// * `compression` - PNG compression level (0-9, where 9 is highest compression)
1078 ///
1079 /// # Returns
1080 ///
1081 /// Returns the PNG-encoded image data as a vector of bytes.
1082 ///
1083 /// # Errors
1084 ///
1085 /// Returns an error if:
1086 /// - Image processing failed
1087 ///
1088 /// # Example
1089 ///
1090 /// ```rust
1091 /// use grim_rs::Grim;
1092 ///
1093 /// let mut grim = Grim::new()?;
1094 /// let result = grim.capture_all()?;
1095 /// let png_bytes = grim.to_png_with_compression(result.data(), result.width(), result.height(), 9)?;
1096 /// println!("PNG data size: {} bytes", png_bytes.len());
1097 /// # Ok::<(), grim_rs::Error>(())
1098 /// ```
1099 pub fn to_png_with_compression(
1100 &self,
1101 data: &[u8],
1102 width: u32,
1103 height: u32,
1104 compression: u8,
1105 ) -> Result<Vec<u8>> {
1106 use image::{ImageBuffer, Rgba};
1107 use std::io::Cursor;
1108
1109 let _img = ImageBuffer::<Rgba<u8>, _>::from_raw(width, height, data.to_vec()).ok_or(
1110 Error::ImageProcessing(image::ImageError::Parameter(
1111 image::error::ParameterError::from_kind(
1112 image::error::ParameterErrorKind::DimensionMismatch,
1113 ),
1114 )),
1115 )?;
1116
1117 let mut output = Vec::new();
1118 {
1119 let writer = Cursor::new(&mut output);
1120 let mut encoder = png::Encoder::new(writer, width, height);
1121
1122 let compression_level = match compression {
1123 0 => png::Compression::Fast,
1124 1..=3 => png::Compression::Best,
1125 4..=6 => png::Compression::Default,
1126 7..=9 => png::Compression::Best,
1127 _ => png::Compression::Default,
1128 };
1129 encoder.set_compression(compression_level);
1130
1131 encoder.set_color(png::ColorType::Rgba);
1132 encoder.set_filter(png::FilterType::NoFilter);
1133
1134 let mut writer = encoder.write_header().map_err(|e| {
1135 Error::Io(std::io::Error::new(
1136 std::io::ErrorKind::Other,
1137 format!("PNG encoding error: {}", e),
1138 ))
1139 })?;
1140
1141 writer.write_image_data(data).map_err(|e| {
1142 Error::Io(std::io::Error::new(
1143 std::io::ErrorKind::Other,
1144 format!("PNG encoding error: {}", e),
1145 ))
1146 })?;
1147 writer.finish().map_err(|e| {
1148 Error::Io(std::io::Error::new(
1149 std::io::ErrorKind::Other,
1150 format!("PNG encoding error: {}", e),
1151 ))
1152 })?;
1153 }
1154
1155 Ok(output)
1156 }
1157
1158 /// Save captured data as PPM.
1159 ///
1160 /// Saves the captured image data to a PPM file.
1161 ///
1162 /// # Arguments
1163 ///
1164 /// * `data` - Raw RGBA image data from a capture result
1165 /// * `width` - Width of the image in pixels
1166 /// * `height` - Height of the image in pixels
1167 /// * `path` - Path where to save the PPM file
1168 ///
1169 /// # Errors
1170 ///
1171 /// Returns an error if:
1172 /// - Failed to create or write to the file
1173 /// - Image processing failed
1174 ///
1175 /// # Example
1176 ///
1177 /// ```rust,no_run
1178 /// use grim_rs::Grim;
1179 /// use chrono::Local;
1180 ///
1181 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1182 /// let mut grim = Grim::new()?;
1183 /// let result = grim.capture_all()?;
1184 ///
1185 /// // Generate timestamped filename
1186 /// let filename = format!("{}_grim.ppm", Local::now().format("%Y%m%d_%Hh%Mm%Ss"));
1187 /// grim.save_ppm(result.data(), result.width(), result.height(), &filename)?;
1188 /// # Ok(())
1189 /// # }
1190 /// ```
1191 pub fn save_ppm<P: AsRef<std::path::Path>>(
1192 &self,
1193 data: &[u8],
1194 width: u32,
1195 height: u32,
1196 path: P,
1197 ) -> Result<()> {
1198 let ppm_data = self.to_ppm(data, width, height)?;
1199 std::fs::write(&path, ppm_data).map_err(|e| Error::IoWithContext {
1200 operation: format!("writing PPM data to file '{}'", path.as_ref().display()),
1201 source: e,
1202 })?;
1203 Ok(())
1204 }
1205
1206 /// Get image data as PPM bytes.
1207 ///
1208 /// Converts the captured image data to PPM format and returns the bytes.
1209 ///
1210 /// # Arguments
1211 ///
1212 /// * `data` - Raw RGBA image data from a capture result
1213 /// * `width` - Width of the image in pixels
1214 /// * `height` - Height of the image in pixels
1215 ///
1216 /// # Returns
1217 ///
1218 /// Returns the PPM-encoded image data as a vector of bytes.
1219 ///
1220 /// # Errors
1221 ///
1222 /// Returns an error if:
1223 /// - Image processing failed
1224 ///
1225 /// # Example
1226 ///
1227 /// ```rust
1228 /// use grim_rs::Grim;
1229 ///
1230 /// let mut grim = Grim::new()?;
1231 /// let result = grim.capture_all()?;
1232 /// let ppm_bytes = grim.to_ppm(result.data(), result.width(), result.height())?;
1233 /// println!("PPM data size: {} bytes", ppm_bytes.len());
1234 /// # Ok::<(), grim_rs::Error>(())
1235 /// ```
1236 pub fn to_ppm(&self, data: &[u8], width: u32, height: u32) -> Result<Vec<u8>> {
1237 let header = format!("P6\n{} {}\n255\n", width, height);
1238 let mut ppm_data = header.into_bytes();
1239
1240 for chunk in data.chunks_exact(4) {
1241 ppm_data.push(chunk[0]); // R
1242 ppm_data.push(chunk[1]); // G
1243 ppm_data.push(chunk[2]); // B
1244 }
1245
1246 Ok(ppm_data)
1247 }
1248
1249 /// Read region from stdin.
1250 ///
1251 /// Reads a region specification from standard input in the format "x,y widthxheight".
1252 ///
1253 /// # Returns
1254 ///
1255 /// Returns a `Box` representing the region read from stdin.
1256 ///
1257 /// # Errors
1258 ///
1259 /// Returns an error if:
1260 /// - Failed to read from stdin
1261 /// - The input format is invalid
1262 ///
1263 /// # Example
1264 ///
1265 /// ```rust
1266 /// use grim_rs::{Grim, Box};
1267 ///
1268 /// // Parse region from string (same format as stdin would provide)
1269 /// let region = "100,100 800x600".parse::<Box>()?;
1270 /// println!("Region: {}", region);
1271 /// # Ok::<(), grim_rs::Error>(())
1272 /// ```
1273 pub fn read_region_from_stdin() -> Result<Box> {
1274 use std::io::{self, BufRead};
1275
1276 let stdin = io::stdin();
1277 let mut handle = stdin.lock();
1278 let mut line = String::new();
1279
1280 handle.read_line(&mut line)?;
1281
1282 // Remove newline characters
1283 line = line.trim_end().to_string();
1284
1285 line.parse()
1286 }
1287
1288 /// Write image data to stdout as PNG.
1289 ///
1290 /// Writes captured image data directly to standard output in PNG format.
1291 ///
1292 /// # Arguments
1293 ///
1294 /// * `data` - Raw RGBA image data from a capture result
1295 /// * `width` - Width of the image in pixels
1296 /// * `height` - Height of the image in pixels
1297 ///
1298 /// # Errors
1299 ///
1300 /// Returns an error if:
1301 /// - Failed to write to stdout
1302 /// - Image processing failed
1303 ///
1304 /// # Example
1305 ///
1306 /// ```rust
1307 /// use grim_rs::Grim;
1308 ///
1309 /// let mut grim = Grim::new()?;
1310 /// let result = grim.capture_all()?;
1311 /// grim.write_png_to_stdout(result.data(), result.width(), result.height())?;
1312 /// # Ok::<(), grim_rs::Error>(())
1313 /// ```
1314 pub fn write_png_to_stdout(&self, data: &[u8], width: u32, height: u32) -> Result<()> {
1315 let png_data = self.to_png(data, width, height)?;
1316 use std::io::Write;
1317 let stdout = std::io::stdout();
1318 let mut handle = stdout.lock();
1319 handle.write_all(&png_data)?;
1320 handle.flush()?;
1321 Ok(())
1322 }
1323
1324 /// Write image data to stdout as PNG with compression level.
1325 ///
1326 /// Writes captured image data directly to standard output in PNG format with specified compression.
1327 ///
1328 /// # Arguments
1329 ///
1330 /// * `data` - Raw RGBA image data from a capture result
1331 /// * `width` - Width of the image in pixels
1332 /// * `height` - Height of the image in pixels
1333 /// * `compression` - PNG compression level (0-9, where 9 is highest compression)
1334 ///
1335 /// # Errors
1336 ///
1337 /// Returns an error if:
1338 /// - Failed to write to stdout
1339 /// - Image processing failed
1340 ///
1341 /// # Example
1342 ///
1343 /// ```rust
1344 /// use grim_rs::Grim;
1345 ///
1346 /// let mut grim = Grim::new()?;
1347 /// let result = grim.capture_all()?;
1348 /// grim.write_png_to_stdout_with_compression(result.data(), result.width(), result.height(), 6)?;
1349 /// # Ok::<(), grim_rs::Error>(())
1350 /// ```
1351 pub fn write_png_to_stdout_with_compression(
1352 &self,
1353 data: &[u8],
1354 width: u32,
1355 height: u32,
1356 compression: u8,
1357 ) -> Result<()> {
1358 let png_data = self.to_png_with_compression(data, width, height, compression)?;
1359 use std::io::Write;
1360 let stdout = std::io::stdout();
1361 let mut handle = stdout.lock();
1362 handle.write_all(&png_data)?;
1363 handle.flush()?;
1364 Ok(())
1365 }
1366
1367 /// Write image data to stdout as JPEG.
1368 ///
1369 /// Writes captured image data directly to standard output in JPEG format.
1370 ///
1371 /// # Arguments
1372 ///
1373 /// * `data` - Raw RGBA image data from a capture result
1374 /// * `width` - Width of the image in pixels
1375 /// * `height` - Height of the image in pixels
1376 ///
1377 /// # Errors
1378 ///
1379 /// Returns an error if:
1380 /// - Failed to write to stdout
1381 /// - Image processing failed
1382 /// - JPEG support is not enabled
1383 ///
1384 /// # Example
1385 ///
1386 /// ```rust
1387 /// use grim_rs::Grim;
1388 ///
1389 /// let mut grim = Grim::new()?;
1390 /// let result = grim.capture_all()?;
1391 /// grim.write_jpeg_to_stdout(result.data(), result.width(), result.height())?;
1392 /// # Ok::<(), grim_rs::Error>(())
1393 /// ```
1394 #[cfg(feature = "jpeg")]
1395 pub fn write_jpeg_to_stdout(&self, data: &[u8], width: u32, height: u32) -> Result<()> {
1396 self.write_jpeg_to_stdout_with_quality(data, width, height, 80)
1397 }
1398
1399 /// Write image data to stdout as JPEG with quality control.
1400 ///
1401 /// Writes captured image data directly to standard output in JPEG format with specified quality.
1402 ///
1403 /// # Arguments
1404 ///
1405 /// * `data` - Raw RGBA image data from a capture result
1406 /// * `width` - Width of the image in pixels
1407 /// * `height` - Height of the image in pixels
1408 /// * `quality` - JPEG quality level (0-100, where 100 is highest quality)
1409 ///
1410 /// # Errors
1411 ///
1412 /// Returns an error if:
1413 /// - Failed to write to stdout
1414 /// - Image processing failed
1415 /// - JPEG support is not enabled
1416 ///
1417 /// # Example
1418 ///
1419 /// ```rust
1420 /// use grim_rs::Grim;
1421 ///
1422 /// let mut grim = Grim::new()?;
1423 /// let result = grim.capture_all()?;
1424 /// grim.write_jpeg_to_stdout_with_quality(result.data(), result.width(), result.height(), 90)?;
1425 /// # Ok::<(), grim_rs::Error>(())
1426 /// ```
1427 #[cfg(feature = "jpeg")]
1428 pub fn write_jpeg_to_stdout_with_quality(
1429 &self,
1430 data: &[u8],
1431 width: u32,
1432 height: u32,
1433 quality: u8,
1434 ) -> Result<()> {
1435 let jpeg_data = self.to_jpeg_with_quality(data, width, height, quality)?;
1436 use std::io::Write;
1437 let stdout = std::io::stdout();
1438 let mut handle = stdout.lock();
1439 handle.write_all(&jpeg_data)?;
1440 handle.flush()?;
1441 Ok(())
1442 }
1443
1444 /// Write image data to stdout as PPM.
1445 ///
1446 /// Writes captured image data directly to standard output in PPM format.
1447 ///
1448 /// # Arguments
1449 ///
1450 /// * `data` - Raw RGBA image data from a capture result
1451 /// * `width` - Width of the image in pixels
1452 /// * `height` - Height of the image in pixels
1453 ///
1454 /// # Errors
1455 ///
1456 /// Returns an error if:
1457 /// - Failed to write to stdout
1458 /// - Image processing failed
1459 ///
1460 /// # Example
1461 ///
1462 /// ```rust
1463 /// use grim_rs::Grim;
1464 ///
1465 /// let mut grim = Grim::new()?;
1466 /// let result = grim.capture_all()?;
1467 /// grim.write_ppm_to_stdout(result.data(), result.width(), result.height())?;
1468 /// # Ok::<(), grim_rs::Error>(())
1469 /// ```
1470 pub fn write_ppm_to_stdout(&self, data: &[u8], width: u32, height: u32) -> Result<()> {
1471 let ppm_data = self.to_ppm(data, width, height)?;
1472 use std::io::Write;
1473 let stdout = std::io::stdout();
1474 let mut handle = stdout.lock();
1475 handle.write_all(&ppm_data)?;
1476 handle.flush()?;
1477 Ok(())
1478 }
1479}
1480
1481#[cfg(test)]
1482mod tests {
1483 use super::*;
1484
1485 #[test]
1486 fn test_geometry_parsing() {
1487 let geometry: Box = "100,200 800x600".parse().unwrap();
1488 assert_eq!(geometry.x(), 100);
1489 assert_eq!(geometry.y(), 200);
1490 assert_eq!(geometry.width(), 800);
1491 assert_eq!(geometry.height(), 600);
1492 }
1493
1494 #[test]
1495 fn test_mock_capture() {
1496 let result = std::panic::catch_unwind(|| {
1497 let mut grim = Grim::new().unwrap();
1498 grim.capture_all()
1499 });
1500
1501 match result {
1502 Ok(capture_result) => {
1503 if let Ok(capture) = capture_result {
1504 assert_eq!(
1505 capture.data.len(),
1506 (capture.width * capture.height * 4) as usize
1507 );
1508 } else {
1509 assert!(matches!(capture_result, Err(Error::NoOutputs)));
1510 }
1511 }
1512 Err(_) => {
1513 panic!("Test panicked unexpectedly");
1514 }
1515 }
1516 }
1517
1518 #[test]
1519 #[cfg(feature = "png")]
1520 fn test_to_png() {
1521 let grim = Grim::new().unwrap();
1522 let test_data = vec![255u8; 64];
1523 let png_data = grim.to_png(&test_data, 4, 4).unwrap();
1524 assert!(!png_data.is_empty());
1525 }
1526
1527 #[test]
1528 #[cfg(feature = "jpeg")]
1529 fn test_to_jpeg() {
1530 let grim = Grim::new().unwrap();
1531 let test_data = vec![255u8; 64];
1532 let jpeg_data = grim.to_jpeg(&test_data, 4, 4).unwrap();
1533 assert!(!jpeg_data.is_empty());
1534 }
1535
1536 #[test]
1537 #[cfg(not(feature = "jpeg"))]
1538 fn test_jpeg_disabled() {
1539 let grim = Grim::new().unwrap();
1540 let test_data = vec![255u8; 16];
1541 let jpeg_result = grim.to_jpeg(&test_data, 4, 4);
1542 assert!(jpeg_result.is_err());
1543 }
1544
1545 #[test]
1546 fn test_ppm_format() {
1547 let grim = Grim::new().unwrap();
1548 let test_data = vec![255u8; 16];
1549 let ppm_result = grim.to_ppm(&test_data, 2, 2);
1550 assert!(ppm_result.is_ok());
1551 let ppm_data = ppm_result.unwrap();
1552 assert!(ppm_data.starts_with(b"P6\n2 2\n255\n"));
1553 assert!(ppm_data.len() >= 12);
1554 }
1555
1556 #[test]
1557 fn test_read_region_from_stdin() {
1558 let region_str = "10,20 300x400";
1559 let result: std::result::Result<Box, _> = region_str.parse();
1560 assert!(result.is_ok());
1561 let region = result.unwrap();
1562 assert_eq!(region.x(), 10);
1563 assert_eq!(region.y(), 20);
1564 assert_eq!(region.width(), 300);
1565 assert_eq!(region.height(), 400);
1566 }
1567
1568 #[test]
1569 fn test_scale_functionality() {
1570 let mut grim = Grim::new().unwrap();
1571 let test_capture = grim.capture_all_with_scale(1.0);
1572 match test_capture {
1573 Ok(_) => {}
1574 Err(Error::NoOutputs) => {}
1575 Err(e) => panic!("Unexpected error: {:?}", e),
1576 }
1577 }
1578}