Skip to main content

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 std::io::BufWriter;
653
654        let pixels = u64::from(width) * u64::from(height);
655        let expected = pixels.checked_mul(4).ok_or_else(|| {
656            Error::ImageProcessing(image::ImageError::Parameter(
657                image::error::ParameterError::from_kind(
658                    image::error::ParameterErrorKind::DimensionMismatch,
659                ),
660            ))
661        })?;
662        if expected > usize::MAX as u64 {
663            return Err(Error::ImageProcessing(image::ImageError::Parameter(
664                image::error::ParameterError::from_kind(
665                    image::error::ParameterErrorKind::DimensionMismatch,
666                ),
667            )));
668        }
669        if data.len() != expected as usize {
670            return Err(Error::ImageProcessing(image::ImageError::Parameter(
671                image::error::ParameterError::from_kind(
672                    image::error::ParameterErrorKind::DimensionMismatch,
673                ),
674            )));
675        }
676
677        let file = std::fs::File::create(&path).map_err(|e| Error::IoWithContext {
678            operation: format!("creating output file '{}'", path.as_ref().display()),
679            source: e,
680        })?;
681        let writer = BufWriter::new(file);
682        let mut encoder = png::Encoder::new(writer, width, height);
683
684        let compression_level = match compression {
685            0 => png::Compression::Fast,
686            1..=3 => png::Compression::Best,
687            4..=6 => png::Compression::Default,
688            7..=9 => png::Compression::Best,
689            _ => png::Compression::Default,
690        };
691        encoder.set_compression(compression_level);
692
693        encoder.set_color(png::ColorType::Rgba);
694        encoder.set_filter(png::FilterType::NoFilter);
695
696        let mut writer = encoder.write_header().map_err(|e| {
697            Error::Io(std::io::Error::new(
698                std::io::ErrorKind::Other,
699                format!("PNG encoding error: {}", e),
700            ))
701        })?;
702
703        writer.write_image_data(data).map_err(|e| {
704            Error::Io(std::io::Error::new(
705                std::io::ErrorKind::Other,
706                format!("PNG encoding error: {}", e),
707            ))
708        })?;
709        writer.finish().map_err(|e| {
710            Error::Io(std::io::Error::new(
711                std::io::ErrorKind::Other,
712                format!("PNG encoding error: {}", e),
713            ))
714        })?;
715
716        Ok(())
717    }
718
719    /// Save captured data as JPEG.
720    ///
721    /// Saves the captured image data to a JPEG file.
722    ///
723    /// This function is only available when the `jpeg` feature is enabled.
724    ///
725    /// # Arguments
726    ///
727    /// * `data` - Raw RGBA image data from a capture result
728    /// * `width` - Width of the image in pixels
729    /// * `height` - Height of the image in pixels
730    /// * `path` - Path where to save the JPEG file
731    ///
732    /// # Errors
733    ///
734    /// Returns an error if:
735    /// - Failed to create or write to the file
736    /// - Image processing failed
737    /// - JPEG support is not enabled (when feature is disabled)
738    ///
739    /// # Example
740    ///
741    /// ```rust,no_run
742    /// use grim_rs::Grim;
743    /// use chrono::Local;
744    ///
745    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
746    /// let mut grim = Grim::new()?;
747    /// let result = grim.capture_all()?;
748    ///
749    /// // Generate timestamped filename
750    /// let filename = format!("{}_grim.jpg", Local::now().format("%Y%m%d_%Hh%Mm%Ss"));
751    /// grim.save_jpeg(result.data(), result.width(), result.height(), &filename)?;
752    /// # Ok(())
753    /// # }
754    /// ```
755    #[cfg(feature = "jpeg")]
756    pub fn save_jpeg<P: AsRef<std::path::Path>>(
757        &self,
758        data: &[u8],
759        width: u32,
760        height: u32,
761        path: P,
762    ) -> Result<()> {
763        self.save_jpeg_with_quality(data, width, height, path, 80)
764    }
765
766    /// Save captured data as JPEG with quality control.
767    ///
768    /// Saves the captured image data to a JPEG file with specified quality.
769    ///
770    /// This function is only available when the `jpeg` feature is enabled.
771    ///
772    /// # Arguments
773    ///
774    /// * `data` - Raw RGBA image data from a capture result
775    /// * `width` - Width of the image in pixels
776    /// * `height` - Height of the image in pixels
777    /// * `path` - Path where to save the JPEG file
778    /// * `quality` - JPEG quality level (0-100, where 100 is highest quality)
779    ///
780    /// # Errors
781    ///
782    /// Returns an error if:
783    /// - Failed to create or write to the file
784    /// - Image processing failed
785    /// - JPEG support is not enabled (when feature is disabled)
786    ///
787    /// # Example
788    ///
789    /// ```rust,no_run
790    /// use grim_rs::Grim;
791    /// use chrono::Local;
792    ///
793    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
794    /// let mut grim = Grim::new()?;
795    /// let result = grim.capture_all()?;
796    ///
797    /// // Generate timestamped filename
798    /// let filename = format!("{}_grim.jpg", Local::now().format("%Y%m%d_%Hh%Mm%Ss"));
799    /// grim.save_jpeg_with_quality(result.data(), result.width(), result.height(), &filename, 90)?;
800    /// # Ok(())
801    /// # }
802    /// ```
803    #[cfg(feature = "jpeg")]
804    pub fn save_jpeg_with_quality<P: AsRef<std::path::Path>>(
805        &self,
806        data: &[u8],
807        width: u32,
808        height: u32,
809        path: P,
810        quality: u8,
811    ) -> Result<()> {
812        let pixels = u64::from(width) * u64::from(height);
813        let expected = pixels.checked_mul(4).ok_or_else(|| {
814            Error::ImageProcessing(image::ImageError::Parameter(
815                image::error::ParameterError::from_kind(
816                    image::error::ParameterErrorKind::DimensionMismatch,
817                ),
818            ))
819        })?;
820        if expected > usize::MAX as u64 {
821            return Err(Error::ImageProcessing(image::ImageError::Parameter(
822                image::error::ParameterError::from_kind(
823                    image::error::ParameterErrorKind::DimensionMismatch,
824                ),
825            )));
826        }
827        if data.len() != expected as usize {
828            return Err(Error::ImageProcessing(image::ImageError::Parameter(
829                image::error::ParameterError::from_kind(
830                    image::error::ParameterErrorKind::DimensionMismatch,
831                ),
832            )));
833        }
834        let pixels = expected as usize / 4;
835        let rgb_len = pixels.checked_mul(3).ok_or_else(|| {
836            Error::ImageProcessing(image::ImageError::Parameter(
837                image::error::ParameterError::from_kind(
838                    image::error::ParameterErrorKind::DimensionMismatch,
839                ),
840            ))
841        })?;
842        let mut rgb_data = vec![0u8; rgb_len];
843        for (i, rgba) in data.chunks_exact(4).enumerate() {
844            let base = i * 3;
845            rgb_data[base] = rgba[0];
846            rgb_data[base + 1] = rgba[1];
847            rgb_data[base + 2] = rgba[2];
848        }
849
850        let mut output_file = std::fs::File::create(&path).map_err(|e| Error::IoWithContext {
851            operation: format!("creating output file '{}'", path.as_ref().display()),
852            source: e,
853        })?;
854        let mut _encoder = jpeg_encoder::Encoder::new(&mut output_file, quality);
855
856        _encoder
857            .encode(
858                &rgb_data,
859                width as u16,
860                height as u16,
861                jpeg_encoder::ColorType::Rgb,
862            )
863            .map_err(|e| {
864                Error::Io(std::io::Error::new(
865                    std::io::ErrorKind::Other,
866                    format!("JPEG encoding error: {}", e),
867                ))
868            })?;
869
870        Ok(())
871    }
872
873    /// Save captured data as JPEG (stub when feature is disabled).
874    ///
875    /// This stub is used when the `jpeg` feature is disabled.
876    ///
877    /// # Errors
878    ///
879    /// Always returns an error indicating that JPEG support is not enabled.
880    #[cfg(not(feature = "jpeg"))]
881    pub fn save_jpeg<P: AsRef<std::path::Path>>(
882        &self,
883        _data: &[u8],
884        _width: u32,
885        _height: u32,
886        _path: P,
887    ) -> Result<()> {
888        Err(Error::ImageProcessing(image::ImageError::Unsupported(
889            image::error::UnsupportedError::from_format_and_kind(
890                image::error::ImageFormatHint::Name("JPEG".to_string()),
891                image::error::UnsupportedErrorKind::Format(image::ImageFormat::Jpeg.into()),
892            ),
893        )))
894    }
895
896    /// Save captured data as JPEG with quality control (stub when feature is disabled).
897    ///
898    /// This stub is used when the `jpeg` feature is disabled.
899    ///
900    /// # Errors
901    ///
902    /// Always returns an error indicating that JPEG support is not enabled.
903    #[cfg(not(feature = "jpeg"))]
904    pub fn save_jpeg_with_quality<P: AsRef<std::path::Path>>(
905        &self,
906        _data: &[u8],
907        _width: u32,
908        _height: u32,
909        _path: P,
910        _quality: u8,
911    ) -> Result<()> {
912        Err(Error::ImageProcessing(image::ImageError::Unsupported(
913            image::error::UnsupportedError::from_format_and_kind(
914                image::error::ImageFormatHint::Name("JPEG".to_string()),
915                image::error::UnsupportedErrorKind::Format(image::ImageFormat::Jpeg.into()),
916            ),
917        )))
918    }
919
920    /// Get image data as JPEG bytes.
921    ///
922    /// Converts the captured image data to JPEG format and returns the bytes.
923    ///
924    /// This function is only available when the `jpeg` feature is enabled.
925    ///
926    /// # Arguments
927    ///
928    /// * `data` - Raw RGBA image data from a capture result
929    /// * `width` - Width of the image in pixels
930    /// * `height` - Height of the image in pixels
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(result.data(), result.width(), result.height())?;
950    /// println!("JPEG data size: {} bytes", jpeg_bytes.len());
951    /// # Ok::<(), grim_rs::Error>(())
952    /// ```
953    #[cfg(feature = "jpeg")]
954    pub fn to_jpeg(&self, data: &[u8], width: u32, height: u32) -> Result<Vec<u8>> {
955        self.to_jpeg_with_quality(data, width, height, 80)
956    }
957
958    /// Get image data as JPEG bytes with quality control.
959    ///
960    /// Converts the captured image data to JPEG format with specified quality and returns the bytes.
961    ///
962    /// This function is only available when the `jpeg` feature is enabled.
963    ///
964    /// # Arguments
965    ///
966    /// * `data` - Raw RGBA image data from a capture result
967    /// * `width` - Width of the image in pixels
968    /// * `height` - Height of the image in pixels
969    /// * `quality` - JPEG quality level (0-100, where 100 is highest quality)
970    ///
971    /// # Returns
972    ///
973    /// Returns the JPEG-encoded image data as a vector of bytes.
974    ///
975    /// # Errors
976    ///
977    /// Returns an error if:
978    /// - Image processing failed
979    /// - JPEG support is not enabled (when feature is disabled)
980    ///
981    /// # Example
982    ///
983    /// ```rust
984    /// use grim_rs::Grim;
985    ///
986    /// let mut grim = Grim::new()?;
987    /// let result = grim.capture_all()?;
988    /// let jpeg_bytes = grim.to_jpeg_with_quality(result.data(), result.width(), result.height(), 90)?;
989    /// println!("JPEG data size: {} bytes", jpeg_bytes.len());
990    /// # Ok::<(), grim_rs::Error>(())
991    /// ```
992    #[cfg(feature = "jpeg")]
993    pub fn to_jpeg_with_quality(
994        &self,
995        data: &[u8],
996        width: u32,
997        height: u32,
998        quality: u8,
999    ) -> Result<Vec<u8>> {
1000        let pixels = u64::from(width) * u64::from(height);
1001        let expected = pixels.checked_mul(4).ok_or_else(|| {
1002            Error::ImageProcessing(image::ImageError::Parameter(
1003                image::error::ParameterError::from_kind(
1004                    image::error::ParameterErrorKind::DimensionMismatch,
1005                ),
1006            ))
1007        })?;
1008        if expected > usize::MAX as u64 {
1009            return Err(Error::ImageProcessing(image::ImageError::Parameter(
1010                image::error::ParameterError::from_kind(
1011                    image::error::ParameterErrorKind::DimensionMismatch,
1012                ),
1013            )));
1014        }
1015        if data.len() != expected as usize {
1016            return Err(Error::ImageProcessing(image::ImageError::Parameter(
1017                image::error::ParameterError::from_kind(
1018                    image::error::ParameterErrorKind::DimensionMismatch,
1019                ),
1020            )));
1021        }
1022        let pixels = expected as usize / 4;
1023        let rgb_len = pixels.checked_mul(3).ok_or_else(|| {
1024            Error::ImageProcessing(image::ImageError::Parameter(
1025                image::error::ParameterError::from_kind(
1026                    image::error::ParameterErrorKind::DimensionMismatch,
1027                ),
1028            ))
1029        })?;
1030        let mut rgb_data = vec![0u8; rgb_len];
1031        for (i, rgba) in data.chunks_exact(4).enumerate() {
1032            let base = i * 3;
1033            rgb_data[base] = rgba[0];
1034            rgb_data[base + 1] = rgba[1];
1035            rgb_data[base + 2] = rgba[2];
1036        }
1037
1038        let mut jpeg_data = Vec::new();
1039        let mut _encoder = jpeg_encoder::Encoder::new(&mut jpeg_data, quality);
1040
1041        _encoder
1042            .encode(
1043                &rgb_data,
1044                width as u16,
1045                height as u16,
1046                jpeg_encoder::ColorType::Rgb,
1047            )
1048            .map_err(|e| {
1049                Error::Io(std::io::Error::new(
1050                    std::io::ErrorKind::Other,
1051                    format!("JPEG encoding error: {}", e),
1052                ))
1053            })?;
1054
1055        Ok(jpeg_data)
1056    }
1057
1058    /// Get image data as JPEG bytes (stub when feature is disabled).
1059    ///
1060    /// This stub is used when the `jpeg` feature is disabled.
1061    ///
1062    /// # Errors
1063    ///
1064    /// Always returns an error indicating that JPEG support is not enabled.
1065    #[cfg(not(feature = "jpeg"))]
1066    pub fn to_jpeg(&self, _data: &[u8], _width: u32, _height: u32) -> Result<Vec<u8>> {
1067        Err(Error::ImageProcessing(image::ImageError::Unsupported(
1068            image::error::UnsupportedError::from_format_and_kind(
1069                image::error::ImageFormatHint::Name("JPEG".to_string()),
1070                image::error::UnsupportedErrorKind::Format(image::ImageFormat::Jpeg.into()),
1071            ),
1072        )))
1073    }
1074
1075    /// Get image data as JPEG bytes with quality control (stub when feature is disabled).
1076    ///
1077    /// This stub is used when the `jpeg` feature is disabled.
1078    ///
1079    /// # Errors
1080    ///
1081    /// Always returns an error indicating that JPEG support is not enabled.
1082    #[cfg(not(feature = "jpeg"))]
1083    pub fn to_jpeg_with_quality(
1084        &self,
1085        _data: &[u8],
1086        _width: u32,
1087        _height: u32,
1088        _quality: u8,
1089    ) -> Result<Vec<u8>> {
1090        Err(Error::ImageProcessing(image::ImageError::Unsupported(
1091            image::error::UnsupportedError::from_format_and_kind(
1092                image::error::ImageFormatHint::Name("JPEG".to_string()),
1093                image::error::UnsupportedErrorKind::Format(image::ImageFormat::Jpeg.into()),
1094            ),
1095        )))
1096    }
1097
1098    /// Get image data as PNG bytes.
1099    ///
1100    /// Converts the captured image data to PNG format and returns the bytes.
1101    ///
1102    /// # Arguments
1103    ///
1104    /// * `data` - Raw RGBA image data from a capture result
1105    /// * `width` - Width of the image in pixels
1106    /// * `height` - Height of the image in pixels
1107    ///
1108    /// # Returns
1109    ///
1110    /// Returns the PNG-encoded image data as a vector of bytes.
1111    ///
1112    /// # Errors
1113    ///
1114    /// Returns an error if:
1115    /// - Image processing failed
1116    ///
1117    /// # Example
1118    ///
1119    /// ```rust
1120    /// use grim_rs::Grim;
1121    ///
1122    /// let mut grim = Grim::new()?;
1123    /// let result = grim.capture_all()?;
1124    /// let png_bytes = grim.to_png(result.data(), result.width(), result.height())?;
1125    /// println!("PNG data size: {} bytes", png_bytes.len());
1126    /// # Ok::<(), grim_rs::Error>(())
1127    /// ```
1128    pub fn to_png(&self, data: &[u8], width: u32, height: u32) -> Result<Vec<u8>> {
1129        self.to_png_with_compression(data, width, height, 6)
1130    }
1131
1132    /// Get image data as PNG bytes with compression level control.
1133    ///
1134    /// Converts the captured image data to PNG format with specified compression level and returns the bytes.
1135    ///
1136    /// # Arguments
1137    ///
1138    /// * `data` - Raw RGBA image data from a capture result
1139    /// * `width` - Width of the image in pixels
1140    /// * `height` - Height of the image in pixels
1141    /// * `compression` - PNG compression level (0-9, where 9 is highest compression)
1142    ///
1143    /// # Returns
1144    ///
1145    /// Returns the PNG-encoded image data as a vector of bytes.
1146    ///
1147    /// # Errors
1148    ///
1149    /// Returns an error if:
1150    /// - Image processing failed
1151    ///
1152    /// # Example
1153    ///
1154    /// ```rust
1155    /// use grim_rs::Grim;
1156    ///
1157    /// let mut grim = Grim::new()?;
1158    /// let result = grim.capture_all()?;
1159    /// let png_bytes = grim.to_png_with_compression(result.data(), result.width(), result.height(), 9)?;
1160    /// println!("PNG data size: {} bytes", png_bytes.len());
1161    /// # Ok::<(), grim_rs::Error>(())
1162    /// ```
1163    pub fn to_png_with_compression(
1164        &self,
1165        data: &[u8],
1166        width: u32,
1167        height: u32,
1168        compression: u8,
1169    ) -> Result<Vec<u8>> {
1170        use std::io::Cursor;
1171
1172        let pixels = u64::from(width) * u64::from(height);
1173        let expected = pixels.checked_mul(4).ok_or_else(|| {
1174            Error::ImageProcessing(image::ImageError::Parameter(
1175                image::error::ParameterError::from_kind(
1176                    image::error::ParameterErrorKind::DimensionMismatch,
1177                ),
1178            ))
1179        })?;
1180        if expected > usize::MAX as u64 {
1181            return Err(Error::ImageProcessing(image::ImageError::Parameter(
1182                image::error::ParameterError::from_kind(
1183                    image::error::ParameterErrorKind::DimensionMismatch,
1184                ),
1185            )));
1186        }
1187        if data.len() != expected as usize {
1188            return Err(Error::ImageProcessing(image::ImageError::Parameter(
1189                image::error::ParameterError::from_kind(
1190                    image::error::ParameterErrorKind::DimensionMismatch,
1191                ),
1192            )));
1193        }
1194
1195        let mut output = Vec::new();
1196        {
1197            let writer = Cursor::new(&mut output);
1198            let mut encoder = png::Encoder::new(writer, width, height);
1199
1200            let compression_level = match compression {
1201                0 => png::Compression::Fast,
1202                1..=3 => png::Compression::Best,
1203                4..=6 => png::Compression::Default,
1204                7..=9 => png::Compression::Best,
1205                _ => png::Compression::Default,
1206            };
1207            encoder.set_compression(compression_level);
1208
1209            encoder.set_color(png::ColorType::Rgba);
1210            encoder.set_filter(png::FilterType::NoFilter);
1211
1212            let mut writer = encoder.write_header().map_err(|e| {
1213                Error::Io(std::io::Error::new(
1214                    std::io::ErrorKind::Other,
1215                    format!("PNG encoding error: {}", e),
1216                ))
1217            })?;
1218
1219            writer.write_image_data(data).map_err(|e| {
1220                Error::Io(std::io::Error::new(
1221                    std::io::ErrorKind::Other,
1222                    format!("PNG encoding error: {}", e),
1223                ))
1224            })?;
1225            writer.finish().map_err(|e| {
1226                Error::Io(std::io::Error::new(
1227                    std::io::ErrorKind::Other,
1228                    format!("PNG encoding error: {}", e),
1229                ))
1230            })?;
1231        }
1232
1233        Ok(output)
1234    }
1235
1236    /// Save captured data as PPM.
1237    ///
1238    /// Saves the captured image data to a PPM file.
1239    ///
1240    /// # Arguments
1241    ///
1242    /// * `data` - Raw RGBA image data from a capture result
1243    /// * `width` - Width of the image in pixels
1244    /// * `height` - Height of the image in pixels
1245    /// * `path` - Path where to save the PPM file
1246    ///
1247    /// # Errors
1248    ///
1249    /// Returns an error if:
1250    /// - Failed to create or write to the file
1251    /// - Image processing failed
1252    ///
1253    /// # Example
1254    ///
1255    /// ```rust,no_run
1256    /// use grim_rs::Grim;
1257    /// use chrono::Local;
1258    ///
1259    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1260    /// let mut grim = Grim::new()?;
1261    /// let result = grim.capture_all()?;
1262    ///
1263    /// // Generate timestamped filename
1264    /// let filename = format!("{}_grim.ppm", Local::now().format("%Y%m%d_%Hh%Mm%Ss"));
1265    /// grim.save_ppm(result.data(), result.width(), result.height(), &filename)?;
1266    /// # Ok(())
1267    /// # }
1268    /// ```
1269    pub fn save_ppm<P: AsRef<std::path::Path>>(
1270        &self,
1271        data: &[u8],
1272        width: u32,
1273        height: u32,
1274        path: P,
1275    ) -> Result<()> {
1276        let ppm_data = self.to_ppm(data, width, height)?;
1277        std::fs::write(&path, ppm_data).map_err(|e| Error::IoWithContext {
1278            operation: format!("writing PPM data to file '{}'", path.as_ref().display()),
1279            source: e,
1280        })?;
1281        Ok(())
1282    }
1283
1284    /// Get image data as PPM bytes.
1285    ///
1286    /// Converts the captured image data to PPM format and returns the bytes.
1287    ///
1288    /// # Arguments
1289    ///
1290    /// * `data` - Raw RGBA image data from a capture result
1291    /// * `width` - Width of the image in pixels
1292    /// * `height` - Height of the image in pixels
1293    ///
1294    /// # Returns
1295    ///
1296    /// Returns the PPM-encoded image data as a vector of bytes.
1297    ///
1298    /// # Errors
1299    ///
1300    /// Returns an error if:
1301    /// - Image processing failed
1302    ///
1303    /// # Example
1304    ///
1305    /// ```rust
1306    /// use grim_rs::Grim;
1307    ///
1308    /// let mut grim = Grim::new()?;
1309    /// let result = grim.capture_all()?;
1310    /// let ppm_bytes = grim.to_ppm(result.data(), result.width(), result.height())?;
1311    /// println!("PPM data size: {} bytes", ppm_bytes.len());
1312    /// # Ok::<(), grim_rs::Error>(())
1313    /// ```
1314    pub fn to_ppm(&self, data: &[u8], width: u32, height: u32) -> Result<Vec<u8>> {
1315        let header = format!("P6\n{} {}\n255\n", width, height);
1316        let mut ppm_data = header.into_bytes();
1317
1318        for chunk in data.chunks_exact(4) {
1319            ppm_data.push(chunk[0]); // R
1320            ppm_data.push(chunk[1]); // G
1321            ppm_data.push(chunk[2]); // B
1322        }
1323
1324        Ok(ppm_data)
1325    }
1326
1327    /// Read region from stdin.
1328    ///
1329    /// Reads a region specification from standard input in the format "x,y widthxheight".
1330    ///
1331    /// # Returns
1332    ///
1333    /// Returns a `Box` representing the region read from stdin.
1334    ///
1335    /// # Errors
1336    ///
1337    /// Returns an error if:
1338    /// - Failed to read from stdin
1339    /// - The input format is invalid
1340    ///
1341    /// # Example
1342    ///
1343    /// ```rust
1344    /// use grim_rs::{Grim, Box};
1345    ///
1346    /// // Parse region from string (same format as stdin would provide)
1347    /// let region = "100,100 800x600".parse::<Box>()?;
1348    /// println!("Region: {}", region);
1349    /// # Ok::<(), grim_rs::Error>(())
1350    /// ```
1351    pub fn read_region_from_stdin() -> Result<Box> {
1352        use std::io::{self, BufRead};
1353
1354        let stdin = io::stdin();
1355        let mut handle = stdin.lock();
1356        let mut line = String::new();
1357
1358        handle.read_line(&mut line)?;
1359
1360        // Remove newline characters
1361        line = line.trim_end().to_string();
1362
1363        line.parse()
1364    }
1365
1366    /// Write image data to stdout as PNG.
1367    ///
1368    /// Writes captured image data directly to standard output in PNG format.
1369    ///
1370    /// # Arguments
1371    ///
1372    /// * `data` - Raw RGBA image data from a capture result
1373    /// * `width` - Width of the image in pixels
1374    /// * `height` - Height of the image in pixels
1375    ///
1376    /// # Errors
1377    ///
1378    /// Returns an error if:
1379    /// - Failed to write to stdout
1380    /// - Image processing failed
1381    ///
1382    /// # Example
1383    ///
1384    /// ```rust
1385    /// use grim_rs::Grim;
1386    ///
1387    /// let mut grim = Grim::new()?;
1388    /// let result = grim.capture_all()?;
1389    /// grim.write_png_to_stdout(result.data(), result.width(), result.height())?;
1390    /// # Ok::<(), grim_rs::Error>(())
1391    /// ```
1392    pub fn write_png_to_stdout(&self, data: &[u8], width: u32, height: u32) -> Result<()> {
1393        let png_data = self.to_png(data, width, height)?;
1394        use std::io::Write;
1395        let stdout = std::io::stdout();
1396        let mut handle = stdout.lock();
1397        handle.write_all(&png_data)?;
1398        handle.flush()?;
1399        Ok(())
1400    }
1401
1402    /// Write image data to stdout as PNG with compression level.
1403    ///
1404    /// Writes captured image data directly to standard output in PNG format with specified compression.
1405    ///
1406    /// # Arguments
1407    ///
1408    /// * `data` - Raw RGBA image data from a capture result
1409    /// * `width` - Width of the image in pixels
1410    /// * `height` - Height of the image in pixels
1411    /// * `compression` - PNG compression level (0-9, where 9 is highest compression)
1412    ///
1413    /// # Errors
1414    ///
1415    /// Returns an error if:
1416    /// - Failed to write to stdout
1417    /// - Image processing failed
1418    ///
1419    /// # Example
1420    ///
1421    /// ```rust
1422    /// use grim_rs::Grim;
1423    ///
1424    /// let mut grim = Grim::new()?;
1425    /// let result = grim.capture_all()?;
1426    /// grim.write_png_to_stdout_with_compression(result.data(), result.width(), result.height(), 6)?;
1427    /// # Ok::<(), grim_rs::Error>(())
1428    /// ```
1429    pub fn write_png_to_stdout_with_compression(
1430        &self,
1431        data: &[u8],
1432        width: u32,
1433        height: u32,
1434        compression: u8,
1435    ) -> Result<()> {
1436        let png_data = self.to_png_with_compression(data, width, height, compression)?;
1437        use std::io::Write;
1438        let stdout = std::io::stdout();
1439        let mut handle = stdout.lock();
1440        handle.write_all(&png_data)?;
1441        handle.flush()?;
1442        Ok(())
1443    }
1444
1445    /// Write image data to stdout as JPEG.
1446    ///
1447    /// Writes captured image data directly to standard output in JPEG format.
1448    ///
1449    /// # Arguments
1450    ///
1451    /// * `data` - Raw RGBA image data from a capture result
1452    /// * `width` - Width of the image in pixels
1453    /// * `height` - Height of the image in pixels
1454    ///
1455    /// # Errors
1456    ///
1457    /// Returns an error if:
1458    /// - Failed to write to stdout
1459    /// - Image processing failed
1460    /// - JPEG support is not enabled
1461    ///
1462    /// # Example
1463    ///
1464    /// ```rust
1465    /// use grim_rs::Grim;
1466    ///
1467    /// let mut grim = Grim::new()?;
1468    /// let result = grim.capture_all()?;
1469    /// grim.write_jpeg_to_stdout(result.data(), result.width(), result.height())?;
1470    /// # Ok::<(), grim_rs::Error>(())
1471    /// ```
1472    #[cfg(feature = "jpeg")]
1473    pub fn write_jpeg_to_stdout(&self, data: &[u8], width: u32, height: u32) -> Result<()> {
1474        self.write_jpeg_to_stdout_with_quality(data, width, height, 80)
1475    }
1476
1477    /// Write image data to stdout as JPEG with quality control.
1478    ///
1479    /// Writes captured image data directly to standard output in JPEG format with specified quality.
1480    ///
1481    /// # Arguments
1482    ///
1483    /// * `data` - Raw RGBA image data from a capture result
1484    /// * `width` - Width of the image in pixels
1485    /// * `height` - Height of the image in pixels
1486    /// * `quality` - JPEG quality level (0-100, where 100 is highest quality)
1487    ///
1488    /// # Errors
1489    ///
1490    /// Returns an error if:
1491    /// - Failed to write to stdout
1492    /// - Image processing failed
1493    /// - JPEG support is not enabledvidence: lib.rs Symbol: to_png_with_compression
1494    ///
1495    /// # Example
1496    ///
1497    /// ```rust
1498    /// use grim_rs::Grim;
1499    ///
1500    /// let mut grim = Grim::new()?;
1501    /// let result = grim.capture_all()?;
1502    /// grim.write_jpeg_to_stdout_with_quality(result.data(), result.width(), result.height(), 90)?;
1503    /// # Ok::<(), grim_rs::Error>(())
1504    /// ```
1505    #[cfg(feature = "jpeg")]
1506    pub fn write_jpeg_to_stdout_with_quality(
1507        &self,
1508        data: &[u8],
1509        width: u32,
1510        height: u32,
1511        quality: u8,
1512    ) -> Result<()> {
1513        let jpeg_data = self.to_jpeg_with_quality(data, width, height, quality)?;
1514        use std::io::Write;
1515        let stdout = std::io::stdout();
1516        let mut handle = stdout.lock();
1517        handle.write_all(&jpeg_data)?;
1518        handle.flush()?;
1519        Ok(())
1520    }
1521
1522    /// Write image data to stdout as PPM.
1523    ///
1524    /// Writes captured image data directly to standard output in PPM format.
1525    ///
1526    /// # Arguments
1527    ///
1528    /// * `data` - Raw RGBA image data from a capture result
1529    /// * `width` - Width of the image in pixels
1530    /// * `height` - Height of the image in pixels
1531    ///
1532    /// # Errors
1533    ///
1534    /// Returns an error if:
1535    /// - Failed to write to stdout
1536    /// - Image processing failed
1537    ///
1538    /// # Example
1539    ///
1540    /// ```rust
1541    /// use grim_rs::Grim;
1542    ///
1543    /// let mut grim = Grim::new()?;
1544    /// let result = grim.capture_all()?;
1545    /// grim.write_ppm_to_stdout(result.data(), result.width(), result.height())?;
1546    /// # Ok::<(), grim_rs::Error>(())
1547    /// ```
1548    pub fn write_ppm_to_stdout(&self, data: &[u8], width: u32, height: u32) -> Result<()> {
1549        let ppm_data = self.to_ppm(data, width, height)?;
1550        use std::io::Write;
1551        let stdout = std::io::stdout();
1552        let mut handle = stdout.lock();
1553        handle.write_all(&ppm_data)?;
1554        handle.flush()?;
1555        Ok(())
1556    }
1557}