Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Welcome to the wgc tutorial!

wgc is a simple, ergonomic, and high-performance Rust wrapper for the Windows Graphics Capture (WGC) API (Windows.Graphics.Capture). It allows Rust developers to easily capture windows or entire monitors on Windows 10 and 11.

Key Features

  • Realtime & AI-Optimized: High-performance frame capture suitable for streaming, computer vision, and machine learning pipelines.
  • Ergonomic Iterator API: Process frames sequentially with standard Rust iterator patterns (Wgc).
  • Interactive Picker & Explicit Handles: Pick target windows or monitors using the native Windows UI picker or construct targets explicitly from window handles (HWND) or monitor handles (HMONITOR).
  • Configurable Formats & Letterboxing: Supports RGBA8 and BGRA8 pixel formats, along with automatic resolution scaling and letterboxing (pixels_fitted).
  • Zero-Copy & Direct3D Access: Direct access to underlying DirectX/Direct3D 11 surface textures and zero-copy frame handling.

System Requirements

  • Operating System: Windows 10 October 2018 Update (version 1809 / build 17763) or later. Windows 11 is recommended.
  • Rust Toolchain: Rust 2024 edition (or compatible Rust compiler toolchain).
  • Target Platform: x86_64-pc-windows-msvc or aarch64-pc-windows-msvc.

In the following chapters, you will learn how to set up wgc, configure capture options, capture frames, and build real-world screen capture applications.

Getting Started

This chapter covers adding wgc to your Rust project and writing a basic screen capture script.

Adding wgc to Cargo.toml

Add wgc to your Cargo.toml dependencies:

[dependencies]
wgc = "2.0"

If you plan to process or save captured images, you might also want helper crates like image or anyhow:

[dependencies]
wgc = "2.0"
anyhow = "1.0"
image = "0.25"

Basic Usage Example

Below is a complete minimal example showing how to open the system picker dialog, capture a single frame, and inspect its dimensions and raw pixel data.

use wgc::{new_item_with_picker, Wgc};

fn main() -> anyhow::Result<()> {
    // 1. Prompt the user to select a window or monitor to capture
    let item = new_item_with_picker(None)?;

    // 2. Create a Wgc capture session with default settings
    let wgc = Wgc::new(item.clone(), Default::default())?;

    // 3. Iterate over captured frames (taking 1 frame here)
    for frame in wgc.take(1) {
        let frame = frame?;
        let size = frame.size()?;
        println!(
            "Captured frame from '{}' with size {}x{}",
            item.DisplayName()?,
            size.width,
            size.height
        );

        // Access raw RGBA pixel buffer
        let pixels: Vec<u8> = frame.pixels()?;
        println!("Buffer size in bytes: {}", pixels.len());
    }

    Ok(())
}

How It Works

  1. new_item_with_picker(None) opens the Windows system picker dialog allowing the user to pick any window or display.
  2. Wgc::new(item, settings) initializes the Direct3D device, capture session, and frame pool.
  3. Wgc implements Iterator<Item = Result<Frame, WgcError>>, yielding available frames sequentially.

Selecting Capture Targets

wgc provides multiple ways to select a target (GraphicsCaptureItem) for screen or window capture.

1. Using the Interactive Picker

The interactive picker displays a native Windows UI dialog that lets the user select any open window or monitor screen.

use wgc::new_item_with_picker;

fn main() -> anyhow::Result<()> {
    // Pass None for parent window handle, or Some(parent_hwnd) to center the picker over a specific window
    let item = new_item_with_picker(None)?;
    println!("Selected target: {}", item.DisplayName()?);
    Ok(())
}

2. Target by Window Handle (HWND)

If you know the window handle (HWND) of a specific application window, you can target it directly without showing a UI picker:

use wgc::new_item_for_window;
use windows::Win32::Foundation::HWND;

fn capture_window(hwnd: HWND) -> anyhow::Result<()> {
    let item = new_item_for_window(hwnd)?;
    println!("Capturing window: {}", item.DisplayName()?);
    Ok(())
}

3. Target by Monitor Handle (HMONITOR)

Similarly, you can capture an entire monitor display directly by passing its HMONITOR handle:

use wgc::new_item_for_monitor;
use windows::Win32::Graphics::Gdi::HMONITOR;

fn capture_monitor(hmonitor: HMONITOR) -> anyhow::Result<()> {
    let item = new_item_for_monitor(hmonitor)?;
    println!("Capturing monitor: {}", item.DisplayName()?);
    Ok(())
}

Target Properties

The returned GraphicsCaptureItem is a WinRT object. You can query its properties such as display name or size:

let name = item.DisplayName()?;
let size = item.Size()?;
println!("Target name: {}, size: {}x{}", name, size.Width, size.Height);

Configuration & Capabilities

wgc allows fine-grained customization of capture sessions using WgcSettings. Additionally, runtime capability functions in wgc::capabilities let you check which features are supported on the host Windows system.

WgcSettings Configuration

WgcSettings controls frame formats, buffer queue length, scaling interpolation, and optional capture features.

use std::time::Duration;
use wgc::settings::{FrameInterpolationMode, PixelFormat, WgcSettings};

let mut settings = WgcSettings::default();

// 1. Pixel Format (RGBA8 or BGRA8)
settings.pixel_format = PixelFormat::RGBA8;

// 2. Buffer Queue Length (number of frames queued in memory)
settings.frame_queue_length = 2;

// 3. Scaling Interpolation Mode (for fitted letterbox scaling)
settings.frame_interpolation_mode = FrameInterpolationMode::Linear;

// 4. Optional Windows 10/11 features (must check capabilities first!)
settings.capture_cursor = Some(false);          // Hide mouse cursor
settings.display_border = Some(false);          // Hide yellow capture border
settings.include_secondary_windows = Some(true); // Include popups/child windows
settings.min_update_interval = Some(Duration::from_millis(16)); // Throttle frame rate (~60 FPS)

Interpolation Modes

When using resolution scaling / letterboxing (pixels_fitted), you can set frame_interpolation_mode to one of the following:

  • NearestNeighbor: Fastest processing, lower visual fidelity.
  • Linear: Balanced performance and quality (default).
  • Cubic: Smooth 16-sample interpolation.
  • MultiSampleLinear: Anti-aliasing for small scale-downs.
  • HighQualityCubic: Best visual quality for significant downscaling.

Checking System Capabilities

Windows Graphics Capture added several settings in newer Windows updates (such as hiding the capture border or cursor). Attempting to enable an unsupported setting on older Windows builds will result in a runtime error.

You can inspect capabilities using the capabilities module:

use wgc::capabilities;

fn main() -> anyhow::Result<()> {
    if !capabilities::is_wgc_supported()? {
        println!("Windows Graphics Capture is not supported on this OS.");
        return Ok(());
    }

    if capabilities::is_cursor_configurable()? {
        println!("Cursor capture toggling is supported!");
    }

    if capabilities::is_border_configurable()? {
        println!("Border visibility toggling is supported!");
    }

    if capabilities::is_dirty_region_mode_configurable()? {
        println!("Dirty region tracking is supported!");
    }

    if capabilities::is_min_update_interval_configurable()? {
        println!("Minimum update interval configuration is supported!");
    }

    Ok(())
}

Capturing Frames

Wgc is an iterator over captured frames. When iterating over Wgc, each step yields a Result<Frame, WgcError>.

The Frame Type

Each Frame provides information about the captured frame and methods for extracting raw pixel data or accessing the underlying Direct3D surface texture.

Frame Properties

  • frame.size(): Returns FrameSize { width, height } of the captured frame in pixels.
  • frame.system_relative_time(): Returns the capture timestamp (Duration since system startup via QueryPerformanceCounter).

Accessing Pixels

1. Native Size Pixels (pixels)

Reads the raw pixel buffer at the captured frame’s native resolution.

use wgc::*;

fn main() -> anyhow::Result<()> {
    let item = new_item_with_picker(None)?;
    let wgc = Wgc::new(item, Default::default())?;

    for frame in wgc.take(1) {
        let frame = frame?;
        let size = frame.size()?;
        let pixels: Vec<u8> = frame.pixels()?;

        println!("Read {} bytes (width: {}, height: {})", pixels.len(), size.width, size.height);
    }
    Ok(())
}

2. Resolution-Fitted Pixels (pixels_fitted)

Scales the frame to fit a target FrameSize while preserving aspect ratio. Any remaining space is letterboxed with gray borders. This is ideal for Machine Learning (e.g. YOLO/ResNet) and computer vision pipelines that require constant input dimensions.

use wgc::*;

fn main() -> anyhow::Result<()> {
    let item = new_item_with_picker(None)?;
    let wgc = Wgc::new(item, Default::default())?;
    let target_size = FrameSize { width: 512, height: 512 };

    for frame in wgc.take(1) {
        let frame = frame?;
        let fitted_pixels: Vec<u8> = frame.pixels_fitted(target_size)?;

        // Guaranteed buffer length: width * height * 4 (RGBA8/BGRA8)
        assert_eq!(fitted_pixels.len(), (512 * 512 * 4) as usize);
    }
    Ok(())
}

Direct3D 11 Surface Access (Zero-Copy)

For low-latency GPU workflows (e.g., Direct3D rendering, video encoding with NVENC/AMF, or Direct2D drawing), you can access the underlying ID3D11Texture2D texture directly:

use wgc::*;

fn main() -> anyhow::Result<()> {
    let item = new_item_with_picker(None)?;
    let wgc = Wgc::new(item, Default::default())?;

    for frame in wgc.take(1) {
        let frame = frame?;

        // Direct3D 11 surface access
        let surface = frame.surface()?; // Windows::Graphics::DirectX::Direct3D11::IDirect3DSurface
        let texture = frame.texture()?; // windows::Win32::Graphics::Direct3D11::ID3D11Texture2D
    }
    Ok(())
}

Examples & Practical Use

This chapter provides complete runnable examples showing how to integrate wgc into real applications.

Example 1: Saving Captured Frames to PNG

In this example, we capture a single frame from a selected window or monitor, save the native image to disk as native.png, and save a letterboxed version scaled to 512x512 as fitted.png.

use image::{ImageBuffer, Rgba};
use wgc::*;

fn main() -> anyhow::Result<()> {
    // 1. Prompt user to select target
    let item = new_item_with_picker(None)?;

    // 2. Initialize Wgc session
    let wgc = Wgc::new(item.clone(), Default::default())?;

    let fitted_size = FrameSize {
        width: 512,
        height: 512,
    };

    // 3. Process 1 frame
    for frame in wgc.take(1) {
        let frame = frame?;
        let native_size = frame.size()?;
        println!("Capturing target: {}", item.DisplayName()?);

        // Native size frame
        let native_pixels = frame.pixels()?;
        save_png("native.png", native_size, native_pixels)?;

        // Resolution-fitted frame (letterboxed)
        let fitted_pixels = frame.pixels_fitted(fitted_size)?;
        save_png("fitted.png", fitted_size, fitted_pixels)?;
    }

    Ok(())
}

fn save_png(path: &str, size: FrameSize, pixels: Vec<u8>) -> anyhow::Result<()> {
    let image: ImageBuffer<Rgba<u8>, Vec<u8>> =
        ImageBuffer::from_raw(size.width, size.height, pixels)
            .ok_or_else(|| anyhow::anyhow!("pixel buffer size mismatch"))?;
    image.save(path)?;
    println!("Saved image to '{path}'");
    Ok(())
}

Example 2: Displaying Captured Video in a Window

You can pair wgc with windowing and image display crates like show-image to build real-time screen viewers or streaming clients.

use show_image::{create_window, ImageInfo, ImageView};
use wgc::*;

#[show_image::main]
fn main() -> anyhow::Result<()> {
    let item = new_item_with_picker(None)?;
    let wgc = Wgc::new(item.clone(), Default::default())?;

    let title = item.DisplayName()?.to_string_lossy();
    let window = create_window(title.clone(), Default::default())?;

    for frame in wgc {
        let frame = frame?;
        let size = frame.size()?;
        let buffer = frame.pixels()?;

        let image = ImageView::new(
            ImageInfo::rgba8_premultiplied(size.width, size.height),
            &buffer,
        );
        window.set_image(title.clone(), image)?;
    }

    Ok(())
}

Example 3: Running Existing Examples from Repository

The wgc repository includes ready-to-run examples:

  • Save Image:

    cargo run --example save_image
    
  • Show Image (Real-time GUI viewer):

    cargo run --example show_image
    
  • Check System Capabilities:

    cargo run --example capabilities