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