bevy_simple_screenshot 0.1.2

A plug-and-play screenshot library for Bevy 0.17+ with ring-buffered capture and automatic saving
# bevy_simple_screenshot

A plug-and-play screenshot library for Bevy 0.17+ with ring-buffered capture and automatic saving.

## Quick Start

Add the dependency:

```toml
[dependencies]
bevy_simple_screenshot = "0.1"
```

Add the plugin and capture screenshots from anywhere in your game code:

```rust,no_run
use bevy::prelude::*;
use bevy_simple_screenshot::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(ScreenshotBufferPlugin::new())
        // Add your own game systems that use ScreenshotTrigger
        .add_systems(Update, your_game_system)
        .run();
}

// Example:
// Suppose you have a system that requires screenshot functionality.
// Here is the Update function of this system.
// You can integrate the screenshot functionality in other part of the system, too.
fn your_game_system(
    trigger: ScreenshotTrigger,
    // ... your other system parameters
) {
    // Call screenshot!() whenever you want to capture
    // For example, when a player dies:
    screenshot!(&trigger, "player_died");

    // Or with a category key for organization:
    screenshot!(&trigger, "combat", "boss_defeated");
}
```

The `ScreenshotTrigger` system parameter can be added to any of your game systems. Call `screenshot!()` whenever you want to capture - it's non-blocking and won't affect your game's performance (see [benchmarks](https://github.com/powergun/bevy_simple_screenshot/blob/main/docs/performance-impact.md)).

Screenshots are saved to `.screenshots/` by default.

## Entity-Focused Screenshots

Capture screenshots cropped to a specific entity (e.g., your game's hero during a special move). This is useful for:
- Character animation debugging
- Sprite sheet generation
- Isolated entity captures for thumbnails or documentation

```rust,ignore
use bevy::prelude::*;
use bevy_simple_screenshot::prelude::*;

// ─────────────────────────────────────────────────────────────
// Your existing game code (components, etc.)
// ─────────────────────────────────────────────────────────────

#[derive(Component)]
struct Hero;

#[derive(Component)]
struct SpecialMoveActive;

// ─────────────────────────────────────────────────────────────
// Add EntityScreenshotTrigger to your system parameters
// ─────────────────────────────────────────────────────────────

fn capture_hero_special_move(
    trigger: EntityScreenshotTrigger,  // <-- Use this instead of ScreenshotTrigger
    hero_query: Query<Entity, (With<Hero>, Added<SpecialMoveActive>)>,
) {
    for hero_entity in &hero_query {
        // Capture with default settings (20px padding)
        screenshot_entity!(&trigger, hero_entity, "hero", "special_move");

        // Or with custom padding
        let settings = EntityScreenshotSettings::default().with_padding(50);
        screenshot_entity!(&trigger, hero_entity, "hero", "special_move", settings);
    }
}
```

### How It Works

1. The macro reads the entity's position and size (from `Sprite` or `Aabb`)
2. Converts world coordinates to screen coordinates using the active 2D camera
3. Captures the full window, then crops to the entity bounds + padding
4. Handles HiDPI/Retina displays automatically (scales coordinates appropriately)

### `screenshot_entity!` Variants

```rust,ignore
// Entity only (default key "default", no description, 20px padding)
screenshot_entity!(&trigger, entity);

// With key
screenshot_entity!(&trigger, entity, "hero");

// With key and description
screenshot_entity!(&trigger, entity, "hero", "idle_animation");

// With key, description, and custom settings
let settings = EntityScreenshotSettings::default()
    .with_padding(30)
    .with_fallback_size(64, 64);  // Used if entity has no Sprite/Aabb
screenshot_entity!(&trigger, entity, "hero", "idle_animation", settings);
```

## Configuration

Create `screenshots.toml` in your project root:

```toml
output_dir = ".screenshots"
buffer_capacity = 10
format = "png"          # or "jpeg"
auto_save = true

[keys.combat]
buffer_capacity = 20
format = "jpeg"
jpeg_quality = "high"   # max, high, medium, low

# Optional: burn-in text overlay on screenshots
# NOTE: font_path is REQUIRED when burn-in is enabled - see "Important Notes" below
[burn_in]
enabled = true
font_path = "assets/fonts/MyFont.ttf"  # REQUIRED - must be a valid TTF from your project
show_frame = true       # show frame number (default when enabled)
show_key = true         # show screenshot key
show_description = true # show description text
position = "upper-right" # upper-left, upper-right, lower-left, lower-right
padding = 5             # pixels from edge
font_size = 18.0        # font size in pixels
```

Or configure programmatically:

```rust,ignore
ScreenshotBufferPlugin::with_config(
    ScreenshotConfig::default()
        .with_output_dir(".screenshots")
        .with_buffer_capacity(5)
        .with_format(ImageFormat::Jpeg)
        .with_burn_in(
            BurnInConfig::enabled()
                .with_font("assets/fonts/MyFont.ttf")  // required
                .with_show_frame(true)
                .with_show_key(true)
                .with_position(BurnInPosition::UpperRight)
        )
)
```

## Important Notes

### Font Requirement for Burn-In

When burn-in text overlay is enabled, **you must provide a valid TTF font file** from your game project. The library does not bundle any fonts to keep dependencies minimal and give you full control over the appearance.

```rust,ignore
// This will PANIC if the font file doesn't exist or is invalid
BurnInConfig::enabled()
    .with_font("assets/fonts/MyFont.ttf")  // <-- Required!
```

If you enable burn-in without specifying a font path, or if the font file cannot be loaded, the application will panic with a clear error message. Make sure to:

1. Use a font that is already part of your game's assets
2. Verify the path is correct relative to your project root
3. Use a standard TTF font file

### Description Text Sanitization

The `description` parameter in `screenshot!()` and `screenshot_entity!()` is used in the output filename. To ensure valid filenames across all platforms, the description is automatically sanitized:

- Only alphanumeric characters, underscores (`_`), and hyphens (`-`) are preserved
- All other characters (spaces, special characters, non-printable characters) are removed
- Path traversal attempts (e.g., `../`) are neutralized

```rust,ignore
// These descriptions will be sanitized:
screenshot!(&trigger, "combat", "boss defeated!");     // -> "boss_defeated"
screenshot!(&trigger, "debug", "frame 100 @ 60fps");   // -> "frame_100__60fps"
screenshot!(&trigger, "test", "../../etc/passwd");     // -> "etcpasswd"
```

This ensures screenshots are always saved with safe, predictable filenames regardless of the description content.

## Documentation

- [Technical Walkthrough]https://github.com/powergun/bevy_simple_screenshot/blob/main/docs/technical-walkthrough.md - Architecture, implementation details, and edge cases
- [Camera Support]https://github.com/powergun/bevy_simple_screenshot/blob/main/docs/camera.md - How 2D and 3D cameras are handled for entity screenshots
- [Performance Impact]https://github.com/powergun/bevy_simple_screenshot/blob/main/docs/performance-impact.md - Benchmarks showing zero FPS impact across all usage scenarios

## License

MIT