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