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
RGBA8andBGRA8pixel 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-msvcoraarch64-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
new_item_with_picker(None)opens the Windows system picker dialog allowing the user to pick any window or display.Wgc::new(item, settings)initializes the Direct3D device, capture session, and frame pool.WgcimplementsIterator<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(): ReturnsFrameSize { width, height }of the captured frame in pixels.frame.system_relative_time(): Returns the capture timestamp (Durationsince 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