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