grim-rs 0.1.4

Rust implementation of grim screenshot utility for Wayland
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
# grim-rs
[![Crates.io Version](https://img.shields.io/crates/v/grim-rs.svg)](https://crates.io/crates/grim-rs)
>if you like this project, then the best way to express gratitude is to give it a star ⭐, it doesn't cost you anything, but I understand that I'm moving the project in the right direction.

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]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`:

```toml
[dependencies]
grim-rs = "0.1.3"
```

**Upgrading from 0.1.2?** See [MIGRATION.md](MIGRATION.md) for breaking changes.

### Basic Capture Operations

```rust
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

```rust
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

```rust
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

```rust
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

```rust
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)

```rust
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)

```rust
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

```rust
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:**
```bash
-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:**

```bash
# 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:

```bash
# 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 buffers
- `zwlr_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 scaling
- `capture_output(output_name: &str)` - Capture specific output by name
- `capture_output_with_scale(output_name: &str, scale: f64)` - Capture output with scaling
- `capture_region(region: Box)` - Capture specific rectangular region
- `capture_region_with_scale(region: Box, scale: f64)` - Capture region with scaling
- `capture_outputs(parameters: Vec<CaptureParameters>)` - Capture multiple outputs with different parameters
- `capture_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) [requires `jpeg` feature]
- `save_jpeg_with_quality(&data, width, height, path, quality: u8)` - Save as JPEG with custom quality (0-100) [requires `jpeg` feature]
- `save_ppm(&data, width, height, path)` - Save as PPM (uncompressed)

#### Converting to Bytes
- `to_png(&data, width, height)` - Convert to PNG bytes with default compression
- `to_png_with_compression(&data, width, height, compression: u8)` - Convert to PNG bytes with custom compression
- `to_jpeg(&data, width, height)` - Convert to JPEG bytes with default quality [requires `jpeg` feature]
- `to_jpeg_with_quality(&data, width, height, quality: u8)` - Convert to JPEG bytes with custom quality [requires `jpeg` feature]
- `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 compression
- `write_png_to_stdout_with_compression(&data, width, height, compression: u8)` - Write PNG to stdout with custom compression
- `write_jpeg_to_stdout(&data, width, height)` - Write JPEG to stdout with default quality [requires `jpeg` feature]
- `write_jpeg_to_stdout_with_quality(&data, width, height, quality: u8)` - Write JPEG to stdout with custom quality [requires `jpeg` feature]
- `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 pixels
- `height: u32` - Image height in pixels

#### `CaptureParameters`
Parameters for capturing specific outputs:
- `output_name: String` - Name of the output to capture
- `region: Option<Box>` - Optional region within the output
- `overlay_cursor: bool` - Whether to include cursor in capture
- `scale: 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 size
- `scale: i32` - Scale factor (1 for normal DPI, 2 for HiDPI)
- `description: Option<String>` - Monitor model and manufacturer information

#### `Box`
Rectangular region:
- `x: i32` - X coordinate
- `y: i32` - Y coordinate
- `width: i32` - Width
- `height: i32` - Height
- Can be parsed from string: "x,y widthxheight"

### Feature Flags

- **`jpeg`** - Enable JPEG support (enabled by default)
  - Adds `save_jpeg*`, `to_jpeg*`, and `write_jpeg_to_stdout*` methods
  
To disable JPEG support:
```toml
[dependencies]
grim-rs = { version = "0.1.0", default-features = false }
```

## Full API Documentation

Comprehensive API documentation is available at [docs.rs](https://docs.rs/grim-rs) or can be generated locally:

```bash
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

1. **Grim** - Main interface for taking screenshots
2. **CaptureResult** - Contains screenshot data and dimensions
3. **CaptureParameters** - Parameters for multi-output capture
4. **Buffer** - Shared memory buffer management
5. **Box** - Region and coordinate handling
6. **Output** - Monitor information with transform support
7. **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_v1` protocol support
- Linux-only (due to shared memory implementation)
- Cursor overlay depends on compositor support

## Building

```bash
cd grim-rs
cargo build --release
```

## Testing

```bash
# Run tests
cargo test

# Run examples
cargo run --example simple all screenshot.png
cargo run --example multi_output
```

## Contributing

1. Fork the repository
2. Create a feature branch
3. Make changes
4. Add tests
5. Submit a pull request

## License

MIT License - see [LICENSE](./LICENSE) file for details.