4 releases
| 0.1.3 | Oct 10, 2025 |
|---|---|
| 0.1.2 | Oct 4, 2025 |
| 0.1.1 | Oct 4, 2025 |
| 0.1.0 | Sep 23, 2025 |
#451 in Images
502 downloads per month
Used in hyprshot-rs
140KB
2.5K
SLoC
grim-rs
Rust implementation of grim-rs screenshot utility for Wayland compositors.
⚠️ Breaking Changes in v0.1.3
Version 0.1.3 introduces breaking changes related to struct field encapsulation. See MIGRATION.md for upgrade guide.
Features
- Pure Rust implementation - no external dependencies on C libraries
- Native Wayland protocol support via
wayland-client - Multi-monitor support with automatic compositing across monitor boundaries
- Output transforms - full support for rotated/flipped displays (all 8 Wayland transform types)
- High-quality image scaling - 4-tier adaptive algorithm selection:
- Upscaling (>1.0): Triangle filter for smooth interpolation
- Mild downscaling (0.75-1.0): Triangle for fast, high-quality results
- Moderate downscaling (0.5-0.75): CatmullRom for sharp results with good performance
- Heavy downscaling (<0.5): Lanczos3 for best quality at extreme reduction
- Region-based screenshot capture with pixel-perfect accuracy
- Multiple output formats:
- PNG with configurable compression (0-9)
- JPEG with quality control (0-100)
- PPM (uncompressed)
- XDG Pictures directory support - automatic file placement in
~/Pictures - Y-invert flag handling - correct screenshot orientation on all compositors
- Cursor overlay support (compositor-dependent)
- Zero external tool dependencies
- Comprehensive API documentation with examples
Usage
As a Library
Add to your Cargo.toml:
[dependencies]
grim-rs = "0.1.3"
Upgrading from 0.1.2? See MIGRATION.md for breaking changes.
Basic Capture Operations
use grim_rs::{Grim, Box};
fn main() -> grim_rs::Result<()> {
let mut grim = Grim::new()?;
// Capture entire screen (all outputs)
let result = grim.capture_all()?;
grim.save_png(result.data(), result.width(), result.height(), "screenshot.png")?;
// Capture specific region (automatically composites across monitors)
let region = Box::new(100, 100, 800, 600);
let result = grim.capture_region(region)?;
grim.save_png(result.data(), result.width(), result.height(), "region.png")?;
// Capture specific output by name (handles transforms/rotation automatically)
let result = grim.capture_output("DP-1")?;
grim.save_png(result.data(), result.width(), result.height(), "output.png")?;
Ok(())
}
Getting Output Information
use grim_rs::Grim;
fn main() -> grim_rs::Result<()> {
let mut grim = Grim::new()?;
// Get list of available outputs with their properties
let outputs = grim.get_outputs()?;
for output in outputs {
println!("Output: {}", output.name());
println!(" Position: ({}, {})", output.geometry().x(), output.geometry().y());
println!(" Size: {}x{}", output.geometry().width(), output.geometry().height());
println!(" Scale: {}", output.scale());
if let Some(desc) = output.description() {
println!(" Description: {}", desc);
}
}
Ok(())
}
Capture with Scaling
use grim_rs::{Grim, Box};
fn main() -> grim_rs::Result<()> {
let mut grim = Grim::new()?;
// Capture entire screen with scaling (high-quality downscaling)
let result = grim.capture_all_with_scale(0.5)?; // 50% size, uses Lanczos3 filter
grim.save_png(result.data(), result.width(), result.height(), "thumbnail.png")?;
// Capture region with scaling
let region = Box::new(0, 0, 1920, 1080);
let result = grim.capture_region_with_scale(region, 0.8)?; // 80% size, uses Triangle filter
grim.save_png(result.data(), result.width(), result.height(), "scaled.png")?;
// Capture specific output with scaling
let result = grim.capture_output_with_scale("DP-1", 0.5)?;
grim.save_png(result.data(), result.width(), result.height(), "output_scaled.png")?;
Ok(())
}
Multiple Output Capture
use grim_rs::{Grim, Box, CaptureParameters};
fn main() -> grim_rs::Result<()> {
let mut grim = Grim::new()?;
// Capture multiple outputs with different parameters
let parameters = vec![
CaptureParameters::new("DP-1")
.overlay_cursor(true),
CaptureParameters::new("HDMI-A-1")
.region(Box::new(0, 0, 1920, 1080))
.scale(0.5)
];
let results = grim.capture_outputs(parameters)?;
for (output_name, result) in results.into_outputs() {
let filename = format!("{}.png", output_name);
grim.save_png(result.data(), result.width(), result.height(), &filename)?;
}
Ok(())
}
Saving to Different Formats
use grim_rs::Grim;
fn main() -> grim_rs::Result<()> {
let mut grim = Grim::new()?;
let result = grim.capture_all()?;
// Save as PNG with default compression (level 6)
grim.save_png(result.data(), result.width(), result.height(), "screenshot.png")?;
// Save as PNG with custom compression (0-9, where 9 is highest)
grim.save_png_with_compression(result.data(), result.width(), result.height(), "compressed.png", 9)?;
// Save as JPEG with default quality (80)
grim.save_jpeg(result.data(), result.width(), result.height(), "screenshot.jpg")?;
// Save as JPEG with custom quality (0-100, where 100 is highest)
grim.save_jpeg_with_quality(result.data(), result.width(), result.height(), "quality.jpg", 95)?;
// Save as PPM (uncompressed)
grim.save_ppm(result.data(), result.width(), result.height(), "screenshot.ppm")?;
Ok(())
}
Converting to Bytes (without saving to file)
use grim_rs::Grim;
fn main() -> grim_rs::Result<()> {
let mut grim = Grim::new()?;
let result = grim.capture_all()?;
// Convert to PNG bytes
let png_bytes = grim.to_png(result.data(), result.width(), result.height())?;
println!("PNG size: {} bytes", png_bytes.len());
// Convert to PNG bytes with custom compression
let png_bytes = grim.to_png_with_compression(result.data(), result.width(), result.height(), 9)?;
// Convert to JPEG bytes
let jpeg_bytes = grim.to_jpeg(result.data(), result.width(), result.height())?;
println!("JPEG size: {} bytes", jpeg_bytes.len());
// Convert to JPEG bytes with custom quality
let jpeg_bytes = grim.to_jpeg_with_quality(result.data(), result.width(), result.height(), 85)?;
// Convert to PPM bytes
let ppm_bytes = grim.to_ppm(result.data(), result.width(), result.height())?;
println!("PPM size: {} bytes", ppm_bytes.len());
Ok(())
}
Writing to Stdout (for piping)
use grim_rs::Grim;
fn main() -> grim_rs::Result<()> {
let mut grim = Grim::new()?;
let result = grim.capture_all()?;
// Write PNG to stdout
grim.write_png_to_stdout(result.data(), result.width(), result.height())?;
// Write PNG to stdout with custom compression
grim.write_png_to_stdout_with_compression(result.data(), result.width(), result.height(), 6)?;
// Write JPEG to stdout
grim.write_jpeg_to_stdout(result.data(), result.width(), result.height())?;
// Write JPEG to stdout with custom quality
grim.write_jpeg_to_stdout_with_quality(result.data(), result.width(), result.height(), 90)?;
// Write PPM to stdout
grim.write_ppm_to_stdout(result.data(), result.width(), result.height())?;
Ok(())
}
Reading Region from Stdin
use grim_rs::Grim;
fn main() -> grim_rs::Result<()> {
let mut grim = Grim::new()?;
// Read region specification from stdin (format: "x,y widthxheight")
let region = Grim::read_region_from_stdin()?;
let result = grim.capture_region(region)?;
grim.save_png(result.data(), result.width(), result.height(), "region.png")?;
Ok(())
}
Command Line Usage
The grim-rs binary supports the same functionality as the library API. By default, saves to ~/Pictures (XDG Pictures directory) with timestamped filenames.
Available Options:
-h Show help message and quit
-s <factor> Set the output image's scale factor (e.g., 0.5 for 50%)
-g <geometry> Set the region to capture (format: "x,y widthxheight")
-t png|ppm|jpeg Set the output filetype (default: png)
-q <quality> Set the JPEG compression quality (0-100, default: 80)
-l <level> Set the PNG compression level (0-9, default: 6)
-o <output> Set the output name to capture (e.g., "DP-1", "HDMI-A-1")
-c Include cursor in the screenshot
Usage Examples:
# Build the binary first
cargo build --release
# Capture entire screen (saves to ~/Pictures/<timestamp>.png)
cargo run --bin grim-rs
# Capture with specific filename
cargo run --bin grim-rs -- screenshot.png
# Capture specific region
cargo run --bin grim-rs -- -g "100,100 800x600" region.png
# Capture with scaling (50% size, creates thumbnail)
cargo run --bin grim-rs -- -s 0.5 thumbnail.png
# Capture specific output by name
cargo run --bin grim-rs -- -o DP-1 monitor.png
# Capture with cursor included
cargo run --bin grim-rs -- -c -o DP-1 with_cursor.png
# Save as JPEG with custom quality
cargo run --bin grim-rs -- -t jpeg -q 90 screenshot.jpg
# Save as PNG with maximum compression
cargo run --bin grim-rs -- -l 9 compressed.png
# Save as PPM (uncompressed)
cargo run --bin grim-rs -- -t ppm screenshot.ppm
# Combine options: region + scaling + cursor
cargo run --bin grim-rs -- -g "0,0 1920x1080" -s 0.8 -c scaled_region.png
# Capture to stdout and pipe to another program
cargo run --bin grim-rs -- - > screenshot.png
# Save to custom directory via environment variable
GRIM_DEFAULT_DIR=/tmp cargo run --bin grim-rs
# Read region from stdin
echo "100,100 800x600" | cargo run --bin grim-rs -- -g -
Using the installed binary:
After installation with cargo install grim-rs, you can use it directly:
# Capture entire screen
grim-rs
# All the same options work without 'cargo run'
grim-rs -g "100,100 800x600" -s 0.5 thumbnail.png
grim-rs -o DP-1 -c monitor.png
grim-rs - | wl-copy # Pipe to clipboard
Note: The binary is named grim-rs to avoid conflicts with the original C implementation of grim.
Supported Wayland Protocols
wl_shm- Shared memory bufferszwlr_screencopy_manager_v1- Screenshot capture (wlroots extension)wl_output- Output information
API Reference
Core Methods
Initialization
Grim::new()- Create new Grim instance and connect to Wayland compositor
Getting Display Information
get_outputs()- Get list of available outputs with their properties (name, geometry, scale)
Capture Methods
capture_all()- Capture entire screen (all outputs)capture_all_with_scale(scale: f64)- Capture entire screen with scalingcapture_output(output_name: &str)- Capture specific output by namecapture_output_with_scale(output_name: &str, scale: f64)- Capture output with scalingcapture_region(region: Box)- Capture specific rectangular regioncapture_region_with_scale(region: Box, scale: f64)- Capture region with scalingcapture_outputs(parameters: Vec<CaptureParameters>)- Capture multiple outputs with different parameterscapture_outputs_with_scale(parameters: Vec<CaptureParameters>, default_scale: f64)- Capture multiple outputs with scaling
Saving to Files
save_png(&data, width, height, path)- Save as PNG with default compression (level 6)save_png_with_compression(&data, width, height, path, compression: u8)- Save as PNG with custom compression (0-9)save_jpeg(&data, width, height, path)- Save as JPEG with default quality (80) [requiresjpegfeature]save_jpeg_with_quality(&data, width, height, path, quality: u8)- Save as JPEG with custom quality (0-100) [requiresjpegfeature]save_ppm(&data, width, height, path)- Save as PPM (uncompressed)
Converting to Bytes
to_png(&data, width, height)- Convert to PNG bytes with default compressionto_png_with_compression(&data, width, height, compression: u8)- Convert to PNG bytes with custom compressionto_jpeg(&data, width, height)- Convert to JPEG bytes with default quality [requiresjpegfeature]to_jpeg_with_quality(&data, width, height, quality: u8)- Convert to JPEG bytes with custom quality [requiresjpegfeature]to_ppm(&data, width, height)- Convert to PPM bytes
Writing to Stdout
write_png_to_stdout(&data, width, height)- Write PNG to stdout with default compressionwrite_png_to_stdout_with_compression(&data, width, height, compression: u8)- Write PNG to stdout with custom compressionwrite_jpeg_to_stdout(&data, width, height)- Write JPEG to stdout with default quality [requiresjpegfeature]write_jpeg_to_stdout_with_quality(&data, width, height, quality: u8)- Write JPEG to stdout with custom quality [requiresjpegfeature]write_ppm_to_stdout(&data, width, height)- Write PPM to stdout
Stdin Input
Grim::read_region_from_stdin()- Read region specification from stdin (format: "x,y widthxheight")
Data Structures
CaptureResult
Contains captured image data:
data: Vec<u8>- Raw RGBA image data (4 bytes per pixel)width: u32- Image width in pixelsheight: u32- Image height in pixels
CaptureParameters
Parameters for capturing specific outputs:
output_name: String- Name of the output to captureregion: Option<Box>- Optional region within the outputoverlay_cursor: bool- Whether to include cursor in capturescale: Option<f64>- Optional scale factor for the output
MultiOutputCaptureResult
Result of capturing multiple outputs:
outputs: HashMap<String, CaptureResult>- Map of output names to their capture results
Output
Information about a display output:
name: String- Output name (e.g., "eDP-1", "HDMI-A-1")geometry: Box- Output position and sizescale: i32- Scale factor (1 for normal DPI, 2 for HiDPI)description: Option<String>- Monitor model and manufacturer information
Box
Rectangular region:
x: i32- X coordinatey: i32- Y coordinatewidth: i32- Widthheight: i32- Height- Can be parsed from string: "x,y widthxheight"
Feature Flags
jpeg- Enable JPEG support (enabled by default)- Adds
save_jpeg*,to_jpeg*, andwrite_jpeg_to_stdout*methods
- Adds
To disable JPEG support:
[dependencies]
grim-rs = { version = "0.1.0", default-features = false }
Full API Documentation
Comprehensive API documentation is available at docs.rs or can be generated locally:
cargo doc --open
Comparison with Original grim
| Feature | Original grim | grim-rs |
|---|---|---|
| Language | C | Rust |
| Dependencies | libpng, pixman, wayland, libjpeg | Pure Rust crates |
| Output formats | PNG, JPEG, PPM | PNG, JPEG, PPM |
| Installation | System package | Rust crate |
| Integration | External process | Library + Binary |
| Memory safety | Manual | Guaranteed by Rust |
| Output transforms | ✅ | ✅ |
| Y-invert handling | ✅ | ✅ |
| Multi-monitor compositing | ✅ | ✅ |
| Image scaling | Nearest-neighbor | 4-tier adaptive (Triangle/CatmullRom/Lanczos3) |
| XDG Pictures support | ✅ | ✅ |
| Output descriptions | ✅ | ✅ |
| Color accuracy | ✅ | ✅ |
| Real capture | ✅ | ✅ |
Architecture
┌─────────────────┐
│ Application │
├─────────────────┤
│ grim-rs │
├─────────────────┤
│ wayland-client │
├─────────────────┤
│ Wayland │
│ Compositor │
└─────────────────┘
Key Components
- Grim - Main interface for taking screenshots
- CaptureResult - Contains screenshot data and dimensions
- CaptureParameters - Parameters for multi-output capture
- Buffer - Shared memory buffer management
- Box - Region and coordinate handling
- Output - Monitor information with transform support
- Error - Comprehensive error handling
Image Processing Pipeline
Wayland Screencopy → Buffer → Output Transform → Y-invert → Scaling → Format Conversion → Save
↓ ↓ ↓
(rotation/flip) (vertical) (Bilinear/Lanczos3)
Scaling Quality
Adaptive 4-tier algorithm selection ensures optimal quality/performance balance:
-
Upscaling (scale > 1.0): Triangle filter
- Smooth interpolation for enlarging images
- Avoids pixelation when scaling up
- Example: 1920×1080 → 2560×1440 (1.33×)
-
Mild downscaling (0.75 ≤ scale ≤ 1.0): Triangle filter
- Fast, high-quality for small size reductions
- Perfect for minor adjustments: 1920×1080 → 1536×864 (0.8×)
-
Moderate downscaling (0.5 ≤ scale < 0.75): CatmullRom filter
- Sharper results than Triangle
- Better performance than Lanczos3
- Ideal for medium reduction: 1920×1080 → 1280×720 (0.67×)
-
Heavy downscaling (scale < 0.5): Lanczos3 convolution
- Best quality for significant reduction
- Ideal for thumbnails: 3840×2160 → 960×540 (0.25×)
- Superior detail preservation at extreme scales
Environment Variables
GRIM_DEFAULT_DIR- Override default screenshot directory (highest priority)XDG_PICTURES_DIR- XDG Pictures directory (from env or~/.config/user-dirs.dirs)
Priority order: GRIM_DEFAULT_DIR → XDG_PICTURES_DIR → current directory
Supported Compositors
- ✅ Hyprland
- ✅ Sway
- ✅ River
- ✅ Wayfire
- ✅ Any wlroots-based compositor with
zwlr_screencopy_manager_v1
Limitations
- Requires compositor with
zwlr_screencopy_manager_v1protocol support - Linux-only (due to shared memory implementation)
- Cursor overlay depends on compositor support
Building
cd grim-rs
cargo build --release
Testing
# Run tests
cargo test
# Run examples
cargo run --example simple all screenshot.png
cargo run --example multi_output
Contributing
- Fork the repository
- Create a feature branch
- Make changes
- Add tests
- Submit a pull request
License
MIT License - see LICENSE file for details.
Dependencies
~16–24MB
~461K SLoC