diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..b5dedc0 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,79 @@ +name: CI & Deploy + +on: + push: + branches: + - master + pull_request: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +jobs: + # --- JOB 1: TEST --- + test: + name: Run mdbook test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - name: Install Rust + run: | + rustup set profile minimal + rustup toolchain install stable + rustup default stable + + - name: Install latest mdbook + run: | + tag=$(curl 'https://api.github.com/repos/rust-lang/mdbook/releases/latest' | jq -r '.tag_name') + url="https://github.com/rust-lang/mdbook/releases/download/${tag}/mdbook-${tag}-x86_64-unknown-linux-gnu.tar.gz" + mkdir bin + curl -sSL $url | tar -xz --directory=bin + echo "$(pwd)/bin" >> $GITHUB_PATH + + - name: Run tests + run: mdbook test + + # --- JOB 2: DEPLOY --- + deploy: + name: Deploy to GitHub Pages + runs-on: ubuntu-latest + needs: test # This ensures Deploy ONLY runs if Test passes! + # This ensures Deploy ONLY runs on the main branch, not on PRs + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') + # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. + # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. + concurrency: + group: "pages" + cancel-in-progress: false + + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Install latest mdbook + run: | + tag=$(curl 'https://api.github.com/repos/rust-lang/mdbook/releases/latest' | jq -r '.tag_name') + url="https://github.com/rust-lang/mdbook/releases/download/${tag}/mdbook-${tag}-x86_64-unknown-linux-gnu.tar.gz" + mkdir bin + curl -sSL $url | tar -xz --directory=bin + echo "$(pwd)/bin" >> $GITHUB_PATH + + - name: Build Book + run: mdbook build + + - name: Setup Pages + uses: actions/configure-pages@v6 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v5 + with: + path: "book" # The default mdbook output folder + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v5 diff --git a/.gitignore b/.gitignore index d16d776..c58a2bd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target /src/.* +/book diff --git a/Cargo.toml b/Cargo.toml index 979fc44..9dcaa1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" authors = ["Atliac"] description = "An ergonomic Rust wrapper for Windows.Graphics.Capture API" repository = "https://github.com/atliac/wgc" -documentation = "https://deepwiki.com/Atliac/wgc" +documentation = "https://books.atliac.com/wgc" homepage = "https://github.com/atliac/wgc" readme = "README.md" keywords = ["windows", "screen-capture", "recording", "machine-learning"] diff --git a/MIGRATION.md b/MIGRATION.md index 31dc1ff..cab3e64 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,7 +1,6 @@ # Migration Guide This document describes breaking API changes and how to update downstream code. -For the full history of changes, see [CHANGELOG.md](./CHANGELOG.md). - [v1.x → v2.0](#v1x--v20) @@ -14,7 +13,7 @@ For the full history of changes, see [CHANGELOG.md](./CHANGELOG.md). | 1 | `Frame::read_pixels(Option)` split into `Frame::pixels()` and `Frame::pixels_fitted(FrameSize)` | Rename calls (see [below](#1-frameread_pixels--framepixels--framepixels_fitted)) | | 2 | `WgcSettings` is now `#[non_exhaustive]` | Build it from `WgcSettings::default()` and assign fields (see [below](#2-wgcsettings-is-now-non_exhaustive)) | | 3 | The `tracing` Cargo feature was removed; `tracing` is a required dependency | Drop `--features tracing` and `features = ["tracing"]` | -| 4 | The `tutorial` example was removed | Use the [examples](./examples/) and [DeepWiki docs](https://deepwiki.com/Atliac/wgc) | +| 4 | The `tutorial` example was removed | Use the [examples](./examples/) and [tutorial docs](https://books.atliac.com/wgc) | ### 1. `Frame::read_pixels` → `Frame::pixels` / `Frame::pixels_fitted` @@ -178,7 +177,7 @@ RUST_LOG=wgc=debug cargo run --example save_image - Read the [save_image](./examples/save_image.rs) example, which now demonstrates both `pixels()` (native size) and `pixels_fitted()` (letterboxed scaling). - Read the [show_image](./examples/show_image.rs) example for a continuous capture loop. -- Consult the [DeepWiki documentation](https://deepwiki.com/Atliac/wgc) for a +- Consult the [tutorial documentation](https://books.atliac.com/wgc) for a narrative walkthrough of the crate. diff --git a/README.md b/README.md index e4e0e53..ba58706 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![Rust CI](https://github.com/Atliac/wgc/actions/workflows/ci.yml/badge.svg)](https://github.com/Atliac/wgc/actions/workflows/ci.yml) [![Stability: Stable](https://img.shields.io/badge/stability-stable-brightgreen)](https://github.com/atliac/wgc) [![Maintenance: Active](https://img.shields.io/badge/maintenance-active-blue)](https://github.com/atliac/wgc) -[![Doc: DeepWiki](https://img.shields.io/badge/Doc-DeepWiki-blue)](https://deepwiki.com/Atliac/wgc) +[![Docs: Tutorial](https://img.shields.io/badge/Docs-Tutorial-blue)](https://books.atliac.com/wgc) A simple and ergonomic Rust wrapper for Windows.Graphics.Capture API, enabling screen/window capture on Windows 10/11. @@ -65,8 +65,8 @@ Check out the [examples](./examples/) directory for more detailed usage examples ## Documentation +- [Tutorial](https://books.atliac.com/wgc): narrative documentation and architecture overview. - [Migration guide](./MIGRATION.md): how to upgrade from `wgc` 1.x to 2.0. -- [DeepWiki](https://deepwiki.com/Atliac/wgc): narrative documentation and architecture overview. - [docs.rs](https://docs.rs/wgc): API reference. ## License diff --git a/book.toml b/book.toml new file mode 100644 index 0000000..3e2c98b --- /dev/null +++ b/book.toml @@ -0,0 +1,14 @@ +[book] +authors = ["Atliac"] +language = "en" +src = "docs" +title = "wgc Tutorial" + +[build] +build-dir = "book" +create-missing = true + +[output.html] +default-theme = "rust" +git-repository-url = "https://github.com/Atliac/wgc" +edit-url-template = "https://github.com/Atliac/wgc/edit/main/docs/{path}" diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md new file mode 100644 index 0000000..151170d --- /dev/null +++ b/docs/SUMMARY.md @@ -0,0 +1,8 @@ +# Summary + +[Introduction](index.md) +[Getting Started](getting_started.md) +[Selecting Targets](selecting_targets.md) +[Configuration & Capabilities](configuration.md) +[Capturing Frames](capturing_frames.md) +[Examples & Practical Use](examples.md) diff --git a/docs/capturing_frames.md b/docs/capturing_frames.md new file mode 100644 index 0000000..fbbea0f --- /dev/null +++ b/docs/capturing_frames.md @@ -0,0 +1,81 @@ +# Capturing Frames + +`Wgc` is an iterator over captured frames. When iterating over `Wgc`, each step yields a `Result`. + +## 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. + +```rust,ignore +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 = 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. + +```rust,ignore +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 = 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: + +```rust,ignore +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(()) +} +``` diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..d0085fe --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,76 @@ +# 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. + +```rust,ignore +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: + +```rust,ignore +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(()) +} +``` diff --git a/docs/examples.md b/docs/examples.md new file mode 100644 index 0000000..600dd3d --- /dev/null +++ b/docs/examples.md @@ -0,0 +1,106 @@ +# 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`. + +```rust,ignore +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) -> anyhow::Result<()> { + let image: ImageBuffer, Vec> = + 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. + +```rust,ignore +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**: + ```bash + cargo run --example save_image + ``` + +- **Show Image (Real-time GUI viewer)**: + ```bash + cargo run --example show_image + ``` + +- **Check System Capabilities**: + ```bash + cargo run --example capabilities + ``` diff --git a/docs/getting_started.md b/docs/getting_started.md new file mode 100644 index 0000000..4b5528d --- /dev/null +++ b/docs/getting_started.md @@ -0,0 +1,61 @@ +# 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: + +```toml +[dependencies] +wgc = "2.0" +``` + +If you plan to process or save captured images, you might also want helper crates like `image` or `anyhow`: + +```toml +[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. + +```rust,ignore +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 = 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>`, yielding available frames sequentially. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..dd12b84 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,21 @@ +# 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. diff --git a/docs/selecting_targets.md b/docs/selecting_targets.md new file mode 100644 index 0000000..40e21df --- /dev/null +++ b/docs/selecting_targets.md @@ -0,0 +1,58 @@ +# 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. + +```rust,ignore +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: + +```rust,ignore +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: + +```rust,ignore +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: + +```rust,ignore +let name = item.DisplayName()?; +let size = item.Size()?; +println!("Target name: {}, size: {}x{}", name, size.Width, size.Height); +```