Skip to content

Latest commit

ย 

History

History
321 lines (253 loc) ยท 11.1 KB

File metadata and controls

321 lines (253 loc) ยท 11.1 KB

wgc_python

English | ็ฎ€ไฝ“ไธญๆ–‡

๐Ÿš€ Window Capture Library Built for Python Automation
180+ FPS Performance ยท Zero-Resource Standby ยท Capture Behind Windows ยท Minimalist API


Why wgc_python?

๐ŸŽฏ Designed for Automation Scenarios

Are you struggling with these problems?

  • mss/BitBlt: Cannot capture occluded or background windows
  • PrintWindow: Performance bottleneck, fixed 26ms+ latency
  • Other WGC wrappers: Continuous resource consumption, huge overhead for frequent start/stop (50ms+)

wgc_python solves this dilemma with its innovative Pause/Resume mechanism:

# Traditional approach: Either waste resources or suffer latency
start_capture()  # 50ms overhead
get_frame()      # Get screenshot
stop_capture()   # Destroy session (50ms)

# wgc_python approach: One-time init, on-demand capture, zero-overhead standby
with WindowCapture("Window", "Class") as cap:
    while running:
        frame = cap.capture_one()  # auto Resume โ†’ wait โ†’ copy โ†’ Pause
        # Process image...

๐Ÿ“Š Performance Comparison

Solution FPS Background Capture CPU Usage Toggle Overhead GPU When Paused
python-mss / BitBlt ~60 โŒ High Low N/A (no pause)
PrintWindow ~38 โœ… Medium Low N/A (per-call)
Other WGC wrappers 180+ โœ… High (continuous) Very High (50ms+) High (can't truly pause)
wgc_python 180+ โœ… Near Zero (when paused) <1ฮผs (atomic flag) Zero (no D3D ops)

โœจ Core Advantages

1. Extreme Performance

  • 180+ FPS high frame rate capture, 5x faster than PrintWindow
  • Double-buffered Staging Texture: GPU async copy, read/write non-blocking
  • Zero-copy path: np.ndarray(strides=...) directly from GPU-mapped memory

2. Smart Resource Management

  • Pause/Resume <1ฮผs soft-pause: Atomic flag only, no WGC session teardown
  • capture_one() auto management: Resume โ†’ wait โ†’ copy โ†’ Pause, zero GPU driver overhead between captures
  • Session Reuse: No frequent D3D device creation/destruction

3. Minimalist API

  • capture_one(): One-line on-demand capture, returns numpy array
  • get_frame(): Zero-copy raw pointer path (advanced)
  • Thread safe: C++ handles all multi-threading complexity

4. Multi-Instance Capture

  • Create multiple capture sessions simultaneously within one process
  • Each session has its own D3D11 device, staging textures, and WinRT session โ€” fully isolated
  • Supports concurrent capture of the same window

5. Client Area Precision (No Title Bar by Default)

  • Default client_area_only=True: captures only window client area, automatically crops title bar and borders
  • Set client_area_only=False: captures entire window including title bar and borders, for UI recording
  • DPI-aware: automatic high-DPI scaling correction for pixel-perfect cropping
  • GPU-level cropping via CopySubresourceRegion, no wasted bandwidth or CPU

6. Capture Behind Windows

  • Supports capturing occluded, minimized, and background windows
  • Perfect for games, desktop apps, and various scenarios

Quick Start

Installation

pip install wgc-python

Basic Usage

from wgc_python import WindowCapture, enumerate_windows

# Enumerate all windows
for title, class_name in enumerate_windows():
    print(f"{title} ({class_name})")

# On-demand capture (recommended โ€” zero-resource standby)
with WindowCapture("Window Title", "WindowClass") as cap:
    frame = cap.capture_one()     # BGRA numpy array, shape (h, w, 4)
    if frame is not None:
        print(f"Captured: {frame.shape}")

# Client area demo
# Default client_area_only=True: content only, no title bar/borders
cap_client = WindowCapture("Notepad", "Notepad")                       # content only
cap_full  = WindowCapture("Notepad", "Notepad", client_area_only=False) # with title bar
frame_client = cap_client.capture_one()  # edit area only
frame_full  = cap_full.capture_one()    # title bar + menu + edit area
cap_client.close()
cap_full.close()

Best Practice for Automation

from wgc_python import WindowCapture

cap = WindowCapture("Game Window", "UnityWndClass")

while True:
    frame = cap.capture_one(timeout=1.0)
    if frame is not None:
        # frame is BGRA numpy array, ready for OpenCV/template matching
        pass
    time.sleep(1)

cap.close()

Zero-Copy Advanced Usage

from wgc_python import WindowCapture
import numpy as np
import ctypes

with WindowCapture("Window", "Class") as cap:
    cap.resume()
    r = cap.get_frame()  # (ptr, w, h, row_pitch) โ€” GPU mapped pointer
    if r:
        ptr, w, h, rp = r
        arr = np.ndarray((h, w, 4), dtype=np.uint8,
                         buffer=(ctypes.c_ubyte * (h * rp)).from_address(ptr),
                         strides=(rp, 4, 1))
        # arr is a zero-copy view into GPU-mapped memory
        cap.release_frame()
    cap.pause()

Real-time Display

from wgc_python import WindowCapture
import cv2

with WindowCapture("Window Title", "WindowClass") as cap:
    while True:
        frame = cap.capture_one()
        if frame is not None:
            cv2.imshow("Capture", cv2.cvtColor(frame, cv2.COLOR_BGRA2BGR))
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    cv2.destroyAllWindows()

API Reference

from wgc_python import (
    WindowCapture,             # Window capture class (context manager support)
    enumerate_windows,         # Enumerate all visible windows
    get_last_error,            # Get last error message (thread-safe)
    get_active_capture_count,  # Get active capture count
)

# WindowCapture methods:
#   cap = WindowCapture(title, class_name, client_area_only=True)
#
#   cap.capture_one(timeout=0.5)  -> np.ndarray | None  โ˜… recommended
#      Auto Resume โ†’ wait for frame โ†’ copy to numpy โ†’ Pause
#      WGC fully dormant between captures, zero GPU driver overhead
#
#   cap.get_frame()              -> (ptr, w, h, row_pitch) | None
#   cap.release_frame()           # Release GPU mapping
#   cap.pause()                   # Pause capture (zero-resource standby)
#   cap.resume()                  # Resume capture
#   cap.stop()                    # Stop frame arrival
#   cap.close()                   # Destroy session
#   cap.is_capturing()  -> bool
#   cap.is_paused()     -> bool
#   cap.get_frame_count()  -> int
#   cap.handle             -> int (DLL handle)

Technical Architecture

WGC Capture โ†’ GPU Surface Texture
                   โ”‚
         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
         โ”‚   FrameArrived     โ”‚
         โ”‚  if pausing โ†’ โ†‘   โ”‚  โ† Paused: return directly, zero D3D ops
         โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
              CopyResource (GPU async copy)
                   โ”‚
     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
     โ”‚  Double-buffered Staging    โ”‚
     โ”‚  [0] Write โ†โ†’ [1] Read      โ”‚
     โ”‚  m_textureInUse anti-collide โ”‚
     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
              Map (permanently mapped GPU memory)
                   โ”‚
     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€ Zero-Copy Output โ”€โ”€โ”€โ”€โ”
     โ”‚ get_frame()                 โ”‚
     โ”‚ raw ptr โ†’ numpy zero-copy   โ”‚
     โ”‚ requires release_frame()    โ”‚
     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€ One-Click Capture โ”€โ”€โ”€โ”
     โ”‚ capture_one()               โ”‚
     โ”‚ auto Pause/Resume           โ”‚
     โ”‚ returns numpy array         โ”‚
     โ”‚ zero GPU overhead when idle โ”‚
     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

How Pause/Resume Works

  User calls cap.pause()
         โ”‚
    m_isPaused = true   โ—„โ”€โ”€โ”€โ”€ atomic flag, <1ฮผs
    m_readableStagingIndex = -1
         โ”‚
    โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚           FrameArrived Callback (WGC still fires)โ”‚
    โ”‚                                                  โ”‚
    โ”‚  lock(mutex);                                    โ”‚
    โ”‚  if (m_isPaused) return;     // โ† pure CPU, skipโ”‚
    โ”‚  // โ†“ below runs only after resume โ†“             โ”‚
    โ”‚  CopyResource(staging, frame);                   โ”‚
    โ”‚  m_readableStagingIndex = idx;                   โ”‚
    โ”‚  unlock(mutex);                                  โ”‚
    โ””โ”€โ”€โ”€โ”€โ–ฒโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
         โ”‚                โ”‚
  User calls cap.resume()  MapFrame checks readableStagingIndex
  m_isPaused = false       <0 โ†’ no frame ready โ†’ returns false

  No WGC session destroy / no D3D device recreate / no callback re-register
  โ†’ zero-latency resume, no spikes

File Structure

wgc_python/
โ”œโ”€โ”€ wgc_python/                   # Python package
โ”‚   โ”œโ”€โ”€ __init__.py               # Python API (ctypes FFI)
โ”‚   โ””โ”€โ”€ wgc_python.dll            # Compiled DLL
โ”œโ”€โ”€ wgc_python_dll/               # C++ DLL Project
โ”‚   โ”œโ”€โ”€ WGCWindowCapture.h/cpp    # Capture core (double-buffered + zero-copy)
โ”‚   โ”œโ”€โ”€ WGCExport.h/cpp           # DLL exports (thread-safe error handling)
โ”‚   โ”œโ”€โ”€ D3DInterop.cpp            # D3D11 device interop
โ”‚   โ”œโ”€โ”€ WindowEnumerator.h/cpp    # Window enumeration
โ”‚   โ”œโ”€โ”€ pch.h                     # Precompiled header
โ”‚   โ””โ”€โ”€ packages/                 # NuGet packages
โ”œโ”€โ”€ test.py                       # Functional tests
โ”œโ”€โ”€ test_mt.py                    # Multi-threaded on-demand capture example
โ”œโ”€โ”€ pyproject.toml                # pip build config
โ”œโ”€โ”€ BUILD.md / BUILD_EN.md        # Build instructions (CN/EN)
โ”œโ”€โ”€ README.md / README_EN.md      # Usage docs (CN/EN)
โ”œโ”€โ”€ CONTRIBUTING.md               # Contributing guide
โ”œโ”€โ”€ CODE_OF_CONDUCT.md            # Code of conduct
โ”œโ”€โ”€ LICENSE                       # MIT License
โ””โ”€โ”€ requirements.txt              # Python dependencies

Build DLL

See BUILD.md


Troubleshooting

Issue Solution
DLL not found Ensure wgc_python.dll is in the correct location
Capture failed Check if window is visible, Windows version >= 1903
Non-ASCII path save failed Use cv2.imencode + open().write() instead of cv2.imwrite
Missing dependencies pip install numpy opencv-python

Use Cases

  • โœ… Game AI / Automation Scripts
  • โœ… RPA Process Automation
  • โœ… Screen Recording / Streaming
  • โœ… UI Automation Testing
  • โœ… Computer Vision Applications

Acknowledgments

This project is based on robmikh/Win32CaptureSample.


License

MIT License