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}