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