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}