wgc-python 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wgc_python-2.0.0/LICENSE +21 -0
- wgc_python-2.0.0/PKG-INFO +372 -0
- wgc_python-2.0.0/README.md +343 -0
- wgc_python-2.0.0/pyproject.toml +45 -0
- wgc_python-2.0.0/setup.cfg +4 -0
- wgc_python-2.0.0/wgc_python/__init__.py +160 -0
- wgc_python-2.0.0/wgc_python/wgc_python.dll +0 -0
- wgc_python-2.0.0/wgc_python.egg-info/PKG-INFO +372 -0
- wgc_python-2.0.0/wgc_python.egg-info/SOURCES.txt +10 -0
- wgc_python-2.0.0/wgc_python.egg-info/dependency_links.txt +1 -0
- wgc_python-2.0.0/wgc_python.egg-info/requires.txt +2 -0
- wgc_python-2.0.0/wgc_python.egg-info/top_level.txt +2 -0
wgc_python-2.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 XuanChenxuan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: wgc_python
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Windows Graphics Capture 窗口捕获库 — BGRA numpy 帧、按需捕获、零拷贝 GPU 路径
|
|
5
|
+
Author: XuanChenxuan
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/XuanChenxuan/wgc_python
|
|
8
|
+
Project-URL: Repository, https://github.com/XuanChenxuan/wgc_python
|
|
9
|
+
Project-URL: BugTracker, https://github.com/XuanChenxuan/wgc_python/issues
|
|
10
|
+
Keywords: wgc,windows-graphics-capture,screen-capture,automation,game-capture
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Multimedia :: Graphics :: Capture
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Requires-Python: >=3.8
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: numpy
|
|
28
|
+
Requires-Dist: opencv-python
|
|
29
|
+
|
|
30
|
+
# wgc_python
|
|
31
|
+
|
|
32
|
+
[English](README_EN.md) | 简体中文
|
|
33
|
+
|
|
34
|
+
> **🚀 为 Python 自动化而生的窗口捕获库**
|
|
35
|
+
> 高帧率捕获 · 零资源待机 · 无视遮挡 · API 极简
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 为什么选择 wgc_python?
|
|
40
|
+
|
|
41
|
+
### 🎯 专为自动化场景设计
|
|
42
|
+
|
|
43
|
+
你是否在为以下问题困扰?
|
|
44
|
+
|
|
45
|
+
- **mss/BitBlt**:无法捕获被遮挡或后台窗口
|
|
46
|
+
- **PrintWindow**:性能瓶颈,固定 26ms+ 延迟
|
|
47
|
+
- **其他 WGC 封装**:持续运行占用资源,频繁启停开销巨大(50ms+)
|
|
48
|
+
|
|
49
|
+
**wgc_python 通过 Pause/Resume 机制解决了这个矛盾:**
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
# 传统方式:要么持续空转浪费资源,要么频繁启停承受延迟
|
|
53
|
+
start_capture() # 50ms 开销
|
|
54
|
+
get_frame() # 获取截图
|
|
55
|
+
stop_capture() # 销毁会话(50ms)
|
|
56
|
+
# 下次截图又要重新开始...
|
|
57
|
+
|
|
58
|
+
# wgc_python 方式:一次启动,按需截图,零开销待机
|
|
59
|
+
with WindowCapture("窗口", "类名") as cap:
|
|
60
|
+
while running:
|
|
61
|
+
frame = cap.capture_one() # auto Resume → 等待帧 → 拷贝 → Pause
|
|
62
|
+
# 处理图像...
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 📊 性能对比
|
|
66
|
+
|
|
67
|
+
| 方案 | FPS | 后台捕获 | CPU 占用 | 频繁切换开销 | 暂停后 GPU 占用 |
|
|
68
|
+
|------|-----|---------|---------|-------------|----------------|
|
|
69
|
+
| python-mss / BitBlt | ~60 | ❌ | 高 | 低 | N/A (无暂停概念) |
|
|
70
|
+
| PrintWindow | ~38 | ✅ | 中 | 低 | N/A (每次调用即捕获) |
|
|
71
|
+
| 其他 WGC 封装 | 高 | ✅ | 高(持续空转) | 高 (启停会话开销大) | 高 (无法真正暂停) |
|
|
72
|
+
| **wgc_python** | **高** | ✅ | **极低(Pause时归零)** | **极低(原子标志位)** | **归零(无 D3D 操作)** |
|
|
73
|
+
|
|
74
|
+
> 表中为定性对比,具体数值因硬件、窗口内容与场景而异,建议以自己的实测为准。
|
|
75
|
+
|
|
76
|
+
### ✨ 核心优势
|
|
77
|
+
|
|
78
|
+
#### 1. 高帧率
|
|
79
|
+
- WGC 直接捕获 GPU 合成输出,不逐帧截屏,帧率上限远高于 PrintWindow 等 GDI 方案
|
|
80
|
+
- **双缓冲 Staging 纹理**:GPU 异步拷贝,读写互不阻塞
|
|
81
|
+
- **零拷贝友好**:`np.ndarray(strides=...)` 直接从 GPU 映射内存构造视图
|
|
82
|
+
|
|
83
|
+
#### 2. 智能资源管理
|
|
84
|
+
- **Pause/Resume 软暂停**:不销毁不重建 WGC session,仅原子标志位跳过帧处理
|
|
85
|
+
- **capture_one() 自动管理**:Resume → 等待帧 → 拷贝 → Pause,间隙 GPU 驱动零开销
|
|
86
|
+
- **会话复用**:避免频繁创建/销毁 D3D 设备的开销
|
|
87
|
+
|
|
88
|
+
#### 3. 极简 API
|
|
89
|
+
- **capture_one()**:一行代码完成按需捕获,返回 numpy 数组
|
|
90
|
+
- **get_frame()**:零拷贝裸指针路径(高级使用)
|
|
91
|
+
- **线程安全**:C++ 层处理所有多线程复杂性
|
|
92
|
+
|
|
93
|
+
#### 4. 多开并发
|
|
94
|
+
- 同一进程内可同时创建多个捕获会话,互不干扰
|
|
95
|
+
- 每个会话独立 D3D11 设备 + 独立纹理 + 独立 WinRT session,完全隔离
|
|
96
|
+
- 支持同窗口多路并发捕获
|
|
97
|
+
|
|
98
|
+
#### 5. 客户区精准裁剪(默认不截取标题栏/边框)
|
|
99
|
+
- **默认 `client_area_only=True`**:只捕获窗口客户区内容,自动裁剪标题栏和边框,直接输出有效像素
|
|
100
|
+
- **设置 `client_area_only=False`**:捕获整个窗口(含标题栏和边框),满足 UI 记录场景
|
|
101
|
+
- DPI 感知:自动修正高 DPI 缩放偏移,裁剪精度像素级
|
|
102
|
+
- GPU 级裁剪:`CopySubresourceRegion` 在 GPU 上完成裁剪,不浪费带宽和 CPU
|
|
103
|
+
|
|
104
|
+
#### 6. 光标捕获开关
|
|
105
|
+
- **默认 `capture_cursor=True`**:画面包含鼠标光标,与常规录屏行为一致
|
|
106
|
+
- **设置 `capture_cursor=False`**:画面不含鼠标指针,适合自动化 / 数据采集场景(也可用 `set_cursor_capture_enabled()` 运行时切换)
|
|
107
|
+
- 需 Windows 10 2004 (19041) 及以上系统,旧系统自动忽略该选项
|
|
108
|
+
|
|
109
|
+
#### 7. 无视遮挡
|
|
110
|
+
- 支持捕获被遮挡、最小化、后台窗口
|
|
111
|
+
- 完美适配游戏、桌面应用等各种场景
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 快速开始
|
|
116
|
+
|
|
117
|
+
### 安装
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pip install wgc-python
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 基础用法
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from wgc_python import WindowCapture, enumerate_windows
|
|
127
|
+
|
|
128
|
+
# 枚举所有窗口
|
|
129
|
+
for title, class_name in enumerate_windows():
|
|
130
|
+
print(f"{title} ({class_name})")
|
|
131
|
+
|
|
132
|
+
# 按需捕获(推荐 —— 零开销待机)
|
|
133
|
+
with WindowCapture("窗口标题", "窗口类名") as cap:
|
|
134
|
+
frame = cap.capture_one() # BGRA numpy 数组,shape (h, w, 4)
|
|
135
|
+
if frame is not None:
|
|
136
|
+
print(f"捕获成功: {frame.shape}")
|
|
137
|
+
|
|
138
|
+
# 客户区裁剪演示
|
|
139
|
+
# 默认 client_area_only=True:只截取客户区,不含标题栏/边框
|
|
140
|
+
cap_client = WindowCapture("记事本", "Notepad") # 只截内容
|
|
141
|
+
cap_full = WindowCapture("记事本", "Notepad", client_area_only=False) # 含标题栏
|
|
142
|
+
frame_client = cap_client.capture_one() # 只有编辑区
|
|
143
|
+
frame_full = cap_full.capture_one() # 含标题栏 + 菜单 + 编辑区
|
|
144
|
+
cap_client.close()
|
|
145
|
+
cap_full.close()
|
|
146
|
+
|
|
147
|
+
# 不捕获鼠标光标(默认 capture_cursor=True,保持旧版行为)
|
|
148
|
+
cap = WindowCapture("记事本", "Notepad", capture_cursor=False)
|
|
149
|
+
frame = cap.capture_one() # 画面不含鼠标指针
|
|
150
|
+
cap.set_cursor_capture_enabled(True) # 也支持运行时切换
|
|
151
|
+
cap.close()
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 自动化最佳实践
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from wgc_python import WindowCapture
|
|
158
|
+
|
|
159
|
+
cap = WindowCapture("游戏窗口", "UnityWndClass")
|
|
160
|
+
|
|
161
|
+
while True:
|
|
162
|
+
frame = cap.capture_one(timeout=1.0)
|
|
163
|
+
if frame is not None:
|
|
164
|
+
# frame 是 BGRA numpy 数组,直接用于 OpenCV/模板匹配
|
|
165
|
+
pass
|
|
166
|
+
time.sleep(1)
|
|
167
|
+
|
|
168
|
+
cap.close()
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 零拷贝高级用法
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
from wgc_python import WindowCapture
|
|
175
|
+
import numpy as np
|
|
176
|
+
import ctypes
|
|
177
|
+
|
|
178
|
+
with WindowCapture("窗口", "类名") as cap:
|
|
179
|
+
cap.resume()
|
|
180
|
+
r = cap.get_frame() # (ptr, w, h, row_pitch) — GPU 映射裸指针
|
|
181
|
+
if r:
|
|
182
|
+
ptr, w, h, rp = r
|
|
183
|
+
arr = np.ndarray((h, w, 4), dtype=np.uint8,
|
|
184
|
+
buffer=(ctypes.c_ubyte * (h * rp)).from_address(ptr),
|
|
185
|
+
strides=(rp, 4, 1))
|
|
186
|
+
# arr 是 GPU 内存的零拷贝视图
|
|
187
|
+
cap.release_frame()
|
|
188
|
+
cap.pause()
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 实时显示
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from wgc_python import WindowCapture
|
|
195
|
+
import cv2
|
|
196
|
+
|
|
197
|
+
with WindowCapture("窗口标题", "窗口类名") as cap:
|
|
198
|
+
while True:
|
|
199
|
+
frame = cap.capture_one()
|
|
200
|
+
if frame is not None:
|
|
201
|
+
cv2.imshow("Capture", cv2.cvtColor(frame, cv2.COLOR_BGRA2BGR))
|
|
202
|
+
if cv2.waitKey(1) & 0xFF == ord('q'):
|
|
203
|
+
break
|
|
204
|
+
cv2.destroyAllWindows()
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## API 参考
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
from wgc_python import (
|
|
213
|
+
WindowCapture, # 窗口捕获类(上下文管理器支持)
|
|
214
|
+
enumerate_windows, # 枚举所有可见窗口
|
|
215
|
+
get_last_error, # 获取最后错误信息(线程安全)
|
|
216
|
+
get_active_capture_count, # 获取活跃捕获数
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# WindowCapture 类方法:
|
|
220
|
+
# cap = WindowCapture(title, class_name, client_area_only=True, capture_cursor=True)
|
|
221
|
+
#
|
|
222
|
+
# cap.capture_one(timeout=0.5) -> np.ndarray | None ★ 推荐
|
|
223
|
+
# 自动 Resume → 等待帧 → 拷贝为 numpy → Pause
|
|
224
|
+
# 捕获间隙 WGC 完全休眠,GPU 驱动零开销
|
|
225
|
+
#
|
|
226
|
+
# cap.get_frame() -> (ptr, w, h, row_pitch) | None
|
|
227
|
+
# cap.release_frame() # 释放 GPU 映射
|
|
228
|
+
# cap.pause() # 暂停捕获(零资源待机)
|
|
229
|
+
# cap.resume() # 恢复捕获
|
|
230
|
+
# cap.set_cursor_capture_enabled(enabled) # 运行时切换光标捕获
|
|
231
|
+
# cap.stop() # 停止帧到达
|
|
232
|
+
# cap.close() # 销毁会话
|
|
233
|
+
# cap.is_capturing() -> bool
|
|
234
|
+
# cap.is_paused() -> bool
|
|
235
|
+
# cap.get_frame_count() -> int
|
|
236
|
+
# cap.handle -> int (DLL handle)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## 技术架构
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
WGC捕获 → GPU Surface纹理
|
|
245
|
+
│
|
|
246
|
+
┌────────▼────────┐
|
|
247
|
+
│ FrameArrived │
|
|
248
|
+
│ if pausing → ↑ │ ← Pause时直接返回,零 D3D 操作
|
|
249
|
+
└────────┬─────────┘
|
|
250
|
+
│
|
|
251
|
+
CopyResource (GPU异步复制)
|
|
252
|
+
↓
|
|
253
|
+
┌─────────────────────────┐
|
|
254
|
+
│ 双缓冲Staging纹理 │
|
|
255
|
+
│ [0] 写入 ←→ [1] 读取 │
|
|
256
|
+
│ m_textureInUse 防冲撞 │
|
|
257
|
+
└─────────────────────────┘
|
|
258
|
+
↓
|
|
259
|
+
Map (永久映射 GPU 内存)
|
|
260
|
+
↓
|
|
261
|
+
┌────── 零拷贝输出 ───────┐
|
|
262
|
+
│ get_frame() │
|
|
263
|
+
│ 返回裸指针 → numpy零拷贝 │
|
|
264
|
+
│ 需手动 release_frame() │
|
|
265
|
+
└──────────────────────────┘
|
|
266
|
+
|
|
267
|
+
┌────── 一键捕获 ──────────┐
|
|
268
|
+
│ capture_one() │
|
|
269
|
+
│ auto Pause/Resume │
|
|
270
|
+
│ 返回 numpy 数组 │
|
|
271
|
+
│ 间隙 GPU 驱动零开销 │
|
|
272
|
+
└──────────────────────────┘
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Pause/Resume 工作原理
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
用户调用 cap.pause()
|
|
279
|
+
│
|
|
280
|
+
m_isPaused = true ◄──── 原子标志位,微秒级
|
|
281
|
+
m_readableStagingIndex = -1
|
|
282
|
+
│
|
|
283
|
+
┌────▼────────────────────────────────────────────┐
|
|
284
|
+
│ FrameArrived 回调(WGC 仍会触发) │
|
|
285
|
+
│ │
|
|
286
|
+
│ lock(mutex); │
|
|
287
|
+
│ if (m_isPaused) return; // ← 纯CPU判断,跳过│
|
|
288
|
+
│ // ↓ 以下只在 resume 后执行 ↓ │
|
|
289
|
+
│ CopyResource(staging, frame); │
|
|
290
|
+
│ m_readableStagingIndex = idx; │
|
|
291
|
+
│ unlock(mutex); │
|
|
292
|
+
└────▲────────────────┬───────────────────────────┘
|
|
293
|
+
│ │
|
|
294
|
+
用户调用 cap.resume() MapFrame 检查 readableStagingIndex
|
|
295
|
+
m_isPaused = false <0 → 最近帧尚未就绪,返回 false
|
|
296
|
+
|
|
297
|
+
不销毁 WGC session / 不重建 D3D 设备 / 不重新注册回调
|
|
298
|
+
→ 恢复零延迟,无突刺
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## 文件结构
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
wgc_python/
|
|
307
|
+
├── wgc_python/ # Python 包
|
|
308
|
+
│ ├── __init__.py # Python API(ctypes FFI)
|
|
309
|
+
│ └── wgc_python.dll # 编译后的 DLL
|
|
310
|
+
├── wgc_python_dll/ # C++ DLL 项目
|
|
311
|
+
│ ├── WGCWindowCapture.h/cpp # WGC 捕获核心(双缓冲 + 零拷贝)
|
|
312
|
+
│ ├── WGCExport.h/cpp # DLL 导出(含线程安全错误处理)
|
|
313
|
+
│ ├── D3DInterop.cpp # D3D11 设备互操作
|
|
314
|
+
│ ├── WindowEnumerator.h/cpp # 窗口枚举
|
|
315
|
+
│ ├── pch.h # 预编译头
|
|
316
|
+
│ └── packages/ # NuGet 包
|
|
317
|
+
├── test.py # 功能测试
|
|
318
|
+
├── demon.py # 多线程实时显示示例
|
|
319
|
+
├── pyproject.toml # pip 构建配置
|
|
320
|
+
├── BUILD.md / BUILD_EN.md # 构建说明(中/英)
|
|
321
|
+
├── README.md / README_EN.md # 使用文档(中/英)
|
|
322
|
+
├── CONTRIBUTING.md # 贡献指南
|
|
323
|
+
├── CODE_OF_CONDUCT.md # 行为准则
|
|
324
|
+
├── LICENSE # MIT 许可证
|
|
325
|
+
└── requirements.txt # Python 依赖
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## 系统要求
|
|
331
|
+
|
|
332
|
+
- Windows 10 1903+ (Build 18362),光标捕获开关需 2004+ (Build 19041)
|
|
333
|
+
- Python 3.8+
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## 构建 DLL
|
|
338
|
+
|
|
339
|
+
详见 [BUILD.md](BUILD.md)
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## 故障排除
|
|
344
|
+
|
|
345
|
+
| 问题 | 解决方案 |
|
|
346
|
+
|------|---------|
|
|
347
|
+
| DLL 未找到 | 确保 `wgc_python.dll` 在正确位置 |
|
|
348
|
+
| 捕获失败 | 检查窗口是否可见,Windows 版本 >= 1903 |
|
|
349
|
+
| 中文路径保存失败 | 使用 `cv2.imencode` + `open().write()` 代替 `cv2.imwrite` |
|
|
350
|
+
| 依赖缺失 | `pip install numpy opencv-python` |
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## 适用场景
|
|
355
|
+
|
|
356
|
+
- ✅ 游戏 AI / 自动化脚本
|
|
357
|
+
- ✅ RPA 流程自动化
|
|
358
|
+
- ✅ 屏幕录制 / 直播
|
|
359
|
+
- ✅ UI 自动化测试
|
|
360
|
+
- ✅ 计算机视觉应用
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## 鸣谢
|
|
365
|
+
|
|
366
|
+
本项目基于 [robmikh/Win32CaptureSample](https://github.com/robmikh/Win32CaptureSample) 开发。
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## License
|
|
371
|
+
|
|
372
|
+
MIT License
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# wgc_python
|
|
2
|
+
|
|
3
|
+
[English](README_EN.md) | 简体中文
|
|
4
|
+
|
|
5
|
+
> **🚀 为 Python 自动化而生的窗口捕获库**
|
|
6
|
+
> 高帧率捕获 · 零资源待机 · 无视遮挡 · API 极简
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 为什么选择 wgc_python?
|
|
11
|
+
|
|
12
|
+
### 🎯 专为自动化场景设计
|
|
13
|
+
|
|
14
|
+
你是否在为以下问题困扰?
|
|
15
|
+
|
|
16
|
+
- **mss/BitBlt**:无法捕获被遮挡或后台窗口
|
|
17
|
+
- **PrintWindow**:性能瓶颈,固定 26ms+ 延迟
|
|
18
|
+
- **其他 WGC 封装**:持续运行占用资源,频繁启停开销巨大(50ms+)
|
|
19
|
+
|
|
20
|
+
**wgc_python 通过 Pause/Resume 机制解决了这个矛盾:**
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
# 传统方式:要么持续空转浪费资源,要么频繁启停承受延迟
|
|
24
|
+
start_capture() # 50ms 开销
|
|
25
|
+
get_frame() # 获取截图
|
|
26
|
+
stop_capture() # 销毁会话(50ms)
|
|
27
|
+
# 下次截图又要重新开始...
|
|
28
|
+
|
|
29
|
+
# wgc_python 方式:一次启动,按需截图,零开销待机
|
|
30
|
+
with WindowCapture("窗口", "类名") as cap:
|
|
31
|
+
while running:
|
|
32
|
+
frame = cap.capture_one() # auto Resume → 等待帧 → 拷贝 → Pause
|
|
33
|
+
# 处理图像...
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 📊 性能对比
|
|
37
|
+
|
|
38
|
+
| 方案 | FPS | 后台捕获 | CPU 占用 | 频繁切换开销 | 暂停后 GPU 占用 |
|
|
39
|
+
|------|-----|---------|---------|-------------|----------------|
|
|
40
|
+
| python-mss / BitBlt | ~60 | ❌ | 高 | 低 | N/A (无暂停概念) |
|
|
41
|
+
| PrintWindow | ~38 | ✅ | 中 | 低 | N/A (每次调用即捕获) |
|
|
42
|
+
| 其他 WGC 封装 | 高 | ✅ | 高(持续空转) | 高 (启停会话开销大) | 高 (无法真正暂停) |
|
|
43
|
+
| **wgc_python** | **高** | ✅ | **极低(Pause时归零)** | **极低(原子标志位)** | **归零(无 D3D 操作)** |
|
|
44
|
+
|
|
45
|
+
> 表中为定性对比,具体数值因硬件、窗口内容与场景而异,建议以自己的实测为准。
|
|
46
|
+
|
|
47
|
+
### ✨ 核心优势
|
|
48
|
+
|
|
49
|
+
#### 1. 高帧率
|
|
50
|
+
- WGC 直接捕获 GPU 合成输出,不逐帧截屏,帧率上限远高于 PrintWindow 等 GDI 方案
|
|
51
|
+
- **双缓冲 Staging 纹理**:GPU 异步拷贝,读写互不阻塞
|
|
52
|
+
- **零拷贝友好**:`np.ndarray(strides=...)` 直接从 GPU 映射内存构造视图
|
|
53
|
+
|
|
54
|
+
#### 2. 智能资源管理
|
|
55
|
+
- **Pause/Resume 软暂停**:不销毁不重建 WGC session,仅原子标志位跳过帧处理
|
|
56
|
+
- **capture_one() 自动管理**:Resume → 等待帧 → 拷贝 → Pause,间隙 GPU 驱动零开销
|
|
57
|
+
- **会话复用**:避免频繁创建/销毁 D3D 设备的开销
|
|
58
|
+
|
|
59
|
+
#### 3. 极简 API
|
|
60
|
+
- **capture_one()**:一行代码完成按需捕获,返回 numpy 数组
|
|
61
|
+
- **get_frame()**:零拷贝裸指针路径(高级使用)
|
|
62
|
+
- **线程安全**:C++ 层处理所有多线程复杂性
|
|
63
|
+
|
|
64
|
+
#### 4. 多开并发
|
|
65
|
+
- 同一进程内可同时创建多个捕获会话,互不干扰
|
|
66
|
+
- 每个会话独立 D3D11 设备 + 独立纹理 + 独立 WinRT session,完全隔离
|
|
67
|
+
- 支持同窗口多路并发捕获
|
|
68
|
+
|
|
69
|
+
#### 5. 客户区精准裁剪(默认不截取标题栏/边框)
|
|
70
|
+
- **默认 `client_area_only=True`**:只捕获窗口客户区内容,自动裁剪标题栏和边框,直接输出有效像素
|
|
71
|
+
- **设置 `client_area_only=False`**:捕获整个窗口(含标题栏和边框),满足 UI 记录场景
|
|
72
|
+
- DPI 感知:自动修正高 DPI 缩放偏移,裁剪精度像素级
|
|
73
|
+
- GPU 级裁剪:`CopySubresourceRegion` 在 GPU 上完成裁剪,不浪费带宽和 CPU
|
|
74
|
+
|
|
75
|
+
#### 6. 光标捕获开关
|
|
76
|
+
- **默认 `capture_cursor=True`**:画面包含鼠标光标,与常规录屏行为一致
|
|
77
|
+
- **设置 `capture_cursor=False`**:画面不含鼠标指针,适合自动化 / 数据采集场景(也可用 `set_cursor_capture_enabled()` 运行时切换)
|
|
78
|
+
- 需 Windows 10 2004 (19041) 及以上系统,旧系统自动忽略该选项
|
|
79
|
+
|
|
80
|
+
#### 7. 无视遮挡
|
|
81
|
+
- 支持捕获被遮挡、最小化、后台窗口
|
|
82
|
+
- 完美适配游戏、桌面应用等各种场景
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 快速开始
|
|
87
|
+
|
|
88
|
+
### 安装
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip install wgc-python
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 基础用法
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from wgc_python import WindowCapture, enumerate_windows
|
|
98
|
+
|
|
99
|
+
# 枚举所有窗口
|
|
100
|
+
for title, class_name in enumerate_windows():
|
|
101
|
+
print(f"{title} ({class_name})")
|
|
102
|
+
|
|
103
|
+
# 按需捕获(推荐 —— 零开销待机)
|
|
104
|
+
with WindowCapture("窗口标题", "窗口类名") as cap:
|
|
105
|
+
frame = cap.capture_one() # BGRA numpy 数组,shape (h, w, 4)
|
|
106
|
+
if frame is not None:
|
|
107
|
+
print(f"捕获成功: {frame.shape}")
|
|
108
|
+
|
|
109
|
+
# 客户区裁剪演示
|
|
110
|
+
# 默认 client_area_only=True:只截取客户区,不含标题栏/边框
|
|
111
|
+
cap_client = WindowCapture("记事本", "Notepad") # 只截内容
|
|
112
|
+
cap_full = WindowCapture("记事本", "Notepad", client_area_only=False) # 含标题栏
|
|
113
|
+
frame_client = cap_client.capture_one() # 只有编辑区
|
|
114
|
+
frame_full = cap_full.capture_one() # 含标题栏 + 菜单 + 编辑区
|
|
115
|
+
cap_client.close()
|
|
116
|
+
cap_full.close()
|
|
117
|
+
|
|
118
|
+
# 不捕获鼠标光标(默认 capture_cursor=True,保持旧版行为)
|
|
119
|
+
cap = WindowCapture("记事本", "Notepad", capture_cursor=False)
|
|
120
|
+
frame = cap.capture_one() # 画面不含鼠标指针
|
|
121
|
+
cap.set_cursor_capture_enabled(True) # 也支持运行时切换
|
|
122
|
+
cap.close()
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 自动化最佳实践
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from wgc_python import WindowCapture
|
|
129
|
+
|
|
130
|
+
cap = WindowCapture("游戏窗口", "UnityWndClass")
|
|
131
|
+
|
|
132
|
+
while True:
|
|
133
|
+
frame = cap.capture_one(timeout=1.0)
|
|
134
|
+
if frame is not None:
|
|
135
|
+
# frame 是 BGRA numpy 数组,直接用于 OpenCV/模板匹配
|
|
136
|
+
pass
|
|
137
|
+
time.sleep(1)
|
|
138
|
+
|
|
139
|
+
cap.close()
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 零拷贝高级用法
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
from wgc_python import WindowCapture
|
|
146
|
+
import numpy as np
|
|
147
|
+
import ctypes
|
|
148
|
+
|
|
149
|
+
with WindowCapture("窗口", "类名") as cap:
|
|
150
|
+
cap.resume()
|
|
151
|
+
r = cap.get_frame() # (ptr, w, h, row_pitch) — GPU 映射裸指针
|
|
152
|
+
if r:
|
|
153
|
+
ptr, w, h, rp = r
|
|
154
|
+
arr = np.ndarray((h, w, 4), dtype=np.uint8,
|
|
155
|
+
buffer=(ctypes.c_ubyte * (h * rp)).from_address(ptr),
|
|
156
|
+
strides=(rp, 4, 1))
|
|
157
|
+
# arr 是 GPU 内存的零拷贝视图
|
|
158
|
+
cap.release_frame()
|
|
159
|
+
cap.pause()
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### 实时显示
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
from wgc_python import WindowCapture
|
|
166
|
+
import cv2
|
|
167
|
+
|
|
168
|
+
with WindowCapture("窗口标题", "窗口类名") as cap:
|
|
169
|
+
while True:
|
|
170
|
+
frame = cap.capture_one()
|
|
171
|
+
if frame is not None:
|
|
172
|
+
cv2.imshow("Capture", cv2.cvtColor(frame, cv2.COLOR_BGRA2BGR))
|
|
173
|
+
if cv2.waitKey(1) & 0xFF == ord('q'):
|
|
174
|
+
break
|
|
175
|
+
cv2.destroyAllWindows()
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## API 参考
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from wgc_python import (
|
|
184
|
+
WindowCapture, # 窗口捕获类(上下文管理器支持)
|
|
185
|
+
enumerate_windows, # 枚举所有可见窗口
|
|
186
|
+
get_last_error, # 获取最后错误信息(线程安全)
|
|
187
|
+
get_active_capture_count, # 获取活跃捕获数
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# WindowCapture 类方法:
|
|
191
|
+
# cap = WindowCapture(title, class_name, client_area_only=True, capture_cursor=True)
|
|
192
|
+
#
|
|
193
|
+
# cap.capture_one(timeout=0.5) -> np.ndarray | None ★ 推荐
|
|
194
|
+
# 自动 Resume → 等待帧 → 拷贝为 numpy → Pause
|
|
195
|
+
# 捕获间隙 WGC 完全休眠,GPU 驱动零开销
|
|
196
|
+
#
|
|
197
|
+
# cap.get_frame() -> (ptr, w, h, row_pitch) | None
|
|
198
|
+
# cap.release_frame() # 释放 GPU 映射
|
|
199
|
+
# cap.pause() # 暂停捕获(零资源待机)
|
|
200
|
+
# cap.resume() # 恢复捕获
|
|
201
|
+
# cap.set_cursor_capture_enabled(enabled) # 运行时切换光标捕获
|
|
202
|
+
# cap.stop() # 停止帧到达
|
|
203
|
+
# cap.close() # 销毁会话
|
|
204
|
+
# cap.is_capturing() -> bool
|
|
205
|
+
# cap.is_paused() -> bool
|
|
206
|
+
# cap.get_frame_count() -> int
|
|
207
|
+
# cap.handle -> int (DLL handle)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 技术架构
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
WGC捕获 → GPU Surface纹理
|
|
216
|
+
│
|
|
217
|
+
┌────────▼────────┐
|
|
218
|
+
│ FrameArrived │
|
|
219
|
+
│ if pausing → ↑ │ ← Pause时直接返回,零 D3D 操作
|
|
220
|
+
└────────┬─────────┘
|
|
221
|
+
│
|
|
222
|
+
CopyResource (GPU异步复制)
|
|
223
|
+
↓
|
|
224
|
+
┌─────────────────────────┐
|
|
225
|
+
│ 双缓冲Staging纹理 │
|
|
226
|
+
│ [0] 写入 ←→ [1] 读取 │
|
|
227
|
+
│ m_textureInUse 防冲撞 │
|
|
228
|
+
└─────────────────────────┘
|
|
229
|
+
↓
|
|
230
|
+
Map (永久映射 GPU 内存)
|
|
231
|
+
↓
|
|
232
|
+
┌────── 零拷贝输出 ───────┐
|
|
233
|
+
│ get_frame() │
|
|
234
|
+
│ 返回裸指针 → numpy零拷贝 │
|
|
235
|
+
│ 需手动 release_frame() │
|
|
236
|
+
└──────────────────────────┘
|
|
237
|
+
|
|
238
|
+
┌────── 一键捕获 ──────────┐
|
|
239
|
+
│ capture_one() │
|
|
240
|
+
│ auto Pause/Resume │
|
|
241
|
+
│ 返回 numpy 数组 │
|
|
242
|
+
│ 间隙 GPU 驱动零开销 │
|
|
243
|
+
└──────────────────────────┘
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Pause/Resume 工作原理
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
用户调用 cap.pause()
|
|
250
|
+
│
|
|
251
|
+
m_isPaused = true ◄──── 原子标志位,微秒级
|
|
252
|
+
m_readableStagingIndex = -1
|
|
253
|
+
│
|
|
254
|
+
┌────▼────────────────────────────────────────────┐
|
|
255
|
+
│ FrameArrived 回调(WGC 仍会触发) │
|
|
256
|
+
│ │
|
|
257
|
+
│ lock(mutex); │
|
|
258
|
+
│ if (m_isPaused) return; // ← 纯CPU判断,跳过│
|
|
259
|
+
│ // ↓ 以下只在 resume 后执行 ↓ │
|
|
260
|
+
│ CopyResource(staging, frame); │
|
|
261
|
+
│ m_readableStagingIndex = idx; │
|
|
262
|
+
│ unlock(mutex); │
|
|
263
|
+
└────▲────────────────┬───────────────────────────┘
|
|
264
|
+
│ │
|
|
265
|
+
用户调用 cap.resume() MapFrame 检查 readableStagingIndex
|
|
266
|
+
m_isPaused = false <0 → 最近帧尚未就绪,返回 false
|
|
267
|
+
|
|
268
|
+
不销毁 WGC session / 不重建 D3D 设备 / 不重新注册回调
|
|
269
|
+
→ 恢复零延迟,无突刺
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## 文件结构
|
|
275
|
+
|
|
276
|
+
```
|
|
277
|
+
wgc_python/
|
|
278
|
+
├── wgc_python/ # Python 包
|
|
279
|
+
│ ├── __init__.py # Python API(ctypes FFI)
|
|
280
|
+
│ └── wgc_python.dll # 编译后的 DLL
|
|
281
|
+
├── wgc_python_dll/ # C++ DLL 项目
|
|
282
|
+
│ ├── WGCWindowCapture.h/cpp # WGC 捕获核心(双缓冲 + 零拷贝)
|
|
283
|
+
│ ├── WGCExport.h/cpp # DLL 导出(含线程安全错误处理)
|
|
284
|
+
│ ├── D3DInterop.cpp # D3D11 设备互操作
|
|
285
|
+
│ ├── WindowEnumerator.h/cpp # 窗口枚举
|
|
286
|
+
│ ├── pch.h # 预编译头
|
|
287
|
+
│ └── packages/ # NuGet 包
|
|
288
|
+
├── test.py # 功能测试
|
|
289
|
+
├── demon.py # 多线程实时显示示例
|
|
290
|
+
├── pyproject.toml # pip 构建配置
|
|
291
|
+
├── BUILD.md / BUILD_EN.md # 构建说明(中/英)
|
|
292
|
+
├── README.md / README_EN.md # 使用文档(中/英)
|
|
293
|
+
├── CONTRIBUTING.md # 贡献指南
|
|
294
|
+
├── CODE_OF_CONDUCT.md # 行为准则
|
|
295
|
+
├── LICENSE # MIT 许可证
|
|
296
|
+
└── requirements.txt # Python 依赖
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## 系统要求
|
|
302
|
+
|
|
303
|
+
- Windows 10 1903+ (Build 18362),光标捕获开关需 2004+ (Build 19041)
|
|
304
|
+
- Python 3.8+
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## 构建 DLL
|
|
309
|
+
|
|
310
|
+
详见 [BUILD.md](BUILD.md)
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## 故障排除
|
|
315
|
+
|
|
316
|
+
| 问题 | 解决方案 |
|
|
317
|
+
|------|---------|
|
|
318
|
+
| DLL 未找到 | 确保 `wgc_python.dll` 在正确位置 |
|
|
319
|
+
| 捕获失败 | 检查窗口是否可见,Windows 版本 >= 1903 |
|
|
320
|
+
| 中文路径保存失败 | 使用 `cv2.imencode` + `open().write()` 代替 `cv2.imwrite` |
|
|
321
|
+
| 依赖缺失 | `pip install numpy opencv-python` |
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## 适用场景
|
|
326
|
+
|
|
327
|
+
- ✅ 游戏 AI / 自动化脚本
|
|
328
|
+
- ✅ RPA 流程自动化
|
|
329
|
+
- ✅ 屏幕录制 / 直播
|
|
330
|
+
- ✅ UI 自动化测试
|
|
331
|
+
- ✅ 计算机视觉应用
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## 鸣谢
|
|
336
|
+
|
|
337
|
+
本项目基于 [robmikh/Win32CaptureSample](https://github.com/robmikh/Win32CaptureSample) 开发。
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## License
|
|
342
|
+
|
|
343
|
+
MIT License
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "wgc_python"
|
|
7
|
+
version = "2.0.0"
|
|
8
|
+
description = "Windows Graphics Capture 窗口捕获库 — BGRA numpy 帧、按需捕获、零拷贝 GPU 路径"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "XuanChenxuan"},
|
|
14
|
+
]
|
|
15
|
+
keywords = ["wgc", "windows-graphics-capture", "screen-capture", "automation", "game-capture"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: Microsoft :: Windows",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.8",
|
|
23
|
+
"Programming Language :: Python :: 3.9",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Topic :: Multimedia :: Graphics :: Capture",
|
|
29
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
30
|
+
]
|
|
31
|
+
dependencies = [
|
|
32
|
+
"numpy",
|
|
33
|
+
"opencv-python",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/XuanChenxuan/wgc_python"
|
|
38
|
+
Repository = "https://github.com/XuanChenxuan/wgc_python"
|
|
39
|
+
BugTracker = "https://github.com/XuanChenxuan/wgc_python/issues"
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.packages.find]
|
|
42
|
+
include = ["wgc_python*"]
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.package-data]
|
|
45
|
+
wgc_python = ["*.dll"]
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""
|
|
2
|
+
wgc-python — 基于 Windows Graphics Capture 的 Python 窗口捕获库
|
|
3
|
+
|
|
4
|
+
支持多窗口并发捕获、客户区裁剪、按需捕获(Pause/Resume)、关闭光标捕获。
|
|
5
|
+
|
|
6
|
+
Copyright (c) 2026 XuanChenxuan
|
|
7
|
+
MIT License — see LICENSE file for details
|
|
8
|
+
"""
|
|
9
|
+
import ctypes
|
|
10
|
+
import os
|
|
11
|
+
import time
|
|
12
|
+
from typing import List, Tuple, Optional
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
_dll = ctypes.CDLL(os.path.join(os.path.dirname(__file__), 'wgc_python.dll'))
|
|
16
|
+
|
|
17
|
+
_dll.StartCapture.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int]
|
|
18
|
+
_dll.StartCapture.restype = ctypes.c_uint64
|
|
19
|
+
if hasattr(_dll, 'SetCursorCaptureEnabled'):
|
|
20
|
+
_dll.SetCursorCaptureEnabled.argtypes = [ctypes.c_uint64, ctypes.c_int]
|
|
21
|
+
_dll.SetCursorCaptureEnabled.restype = None
|
|
22
|
+
_dll.DestroyCapture.argtypes = [ctypes.c_uint64]
|
|
23
|
+
_dll.GetFrameMapped.argtypes = [ctypes.c_uint64, ctypes.POINTER(ctypes.POINTER(ctypes.c_ubyte)),
|
|
24
|
+
ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int),
|
|
25
|
+
ctypes.POINTER(ctypes.c_int)]
|
|
26
|
+
_dll.GetFrameMapped.restype = ctypes.c_int
|
|
27
|
+
_dll.ReleaseMappedFrame.argtypes = [ctypes.c_uint64]
|
|
28
|
+
_dll.StopCapture.argtypes = [ctypes.c_uint64]
|
|
29
|
+
_dll.PauseCapture.argtypes = [ctypes.c_uint64]
|
|
30
|
+
_dll.ResumeCapture.argtypes = [ctypes.c_uint64]
|
|
31
|
+
_dll.IsCapturing.argtypes = [ctypes.c_uint64]
|
|
32
|
+
_dll.IsCapturing.restype = ctypes.c_int
|
|
33
|
+
_dll.IsPaused.argtypes = [ctypes.c_uint64]
|
|
34
|
+
_dll.IsPaused.restype = ctypes.c_int
|
|
35
|
+
_dll.GetFrameCount.argtypes = [ctypes.c_uint64]
|
|
36
|
+
_dll.GetFrameCount.restype = ctypes.c_int
|
|
37
|
+
_dll.GetActiveCaptureCount.restype = ctypes.c_size_t
|
|
38
|
+
_dll.GetLastErrorMsg.restype = ctypes.c_char_p
|
|
39
|
+
_dll.EnumerateWindows.argtypes = [ctypes.POINTER(ctypes.POINTER(ctypes.c_char_p)),
|
|
40
|
+
ctypes.POINTER(ctypes.POINTER(ctypes.c_char_p)), ctypes.POINTER(ctypes.c_int)]
|
|
41
|
+
_dll.EnumerateWindows.restype = ctypes.c_int
|
|
42
|
+
_dll.FreeStringArray.argtypes = [ctypes.POINTER(ctypes.c_char_p), ctypes.c_int]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def enumerate_windows() -> List[Tuple[str, str]]:
|
|
46
|
+
"""枚举所有可见窗口,返回 (标题, 类名) 列表。失败时抛出 RuntimeError"""
|
|
47
|
+
titles = ctypes.POINTER(ctypes.c_char_p)()
|
|
48
|
+
classes = ctypes.POINTER(ctypes.c_char_p)()
|
|
49
|
+
cnt = ctypes.c_int()
|
|
50
|
+
if _dll.EnumerateWindows(ctypes.byref(titles), ctypes.byref(classes), ctypes.byref(cnt)) == 0:
|
|
51
|
+
err = get_last_error()
|
|
52
|
+
raise RuntimeError(err if err else "EnumerateWindows failed")
|
|
53
|
+
result = [(titles[i].decode(), classes[i].decode()) for i in range(cnt.value)]
|
|
54
|
+
_dll.FreeStringArray(titles, cnt.value)
|
|
55
|
+
_dll.FreeStringArray(classes, cnt.value)
|
|
56
|
+
return result
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_last_error() -> str:
|
|
60
|
+
msg = _dll.GetLastErrorMsg()
|
|
61
|
+
return msg.decode() if msg else ""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_active_capture_count() -> int:
|
|
65
|
+
return _dll.GetActiveCaptureCount()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class WindowCapture:
|
|
69
|
+
"""窗口捕获类 - 支持多线程多窗口并发捕获
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
title: 窗口标题
|
|
73
|
+
class_name: 窗口类名
|
|
74
|
+
client_area_only: 只捕获客户区(不含标题栏/边框),默认 True
|
|
75
|
+
capture_cursor: 是否捕获鼠标光标,默认 True(需 Windows 10 2004+,旧系统自动忽略)
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, title: str, class_name: str, client_area_only: bool = True,
|
|
79
|
+
capture_cursor: bool = True):
|
|
80
|
+
self._h = _dll.StartCapture(title.encode(), class_name.encode(), int(client_area_only))
|
|
81
|
+
if not self._h:
|
|
82
|
+
raise RuntimeError(get_last_error())
|
|
83
|
+
if not capture_cursor and hasattr(_dll, 'SetCursorCaptureEnabled'):
|
|
84
|
+
_dll.SetCursorCaptureEnabled(self._h, 0)
|
|
85
|
+
|
|
86
|
+
def __del__(self): self.close()
|
|
87
|
+
def __enter__(self): return self
|
|
88
|
+
def __exit__(self, *_): self.close()
|
|
89
|
+
|
|
90
|
+
def get_frame(self) -> Optional[Tuple[int, int, int, int]]:
|
|
91
|
+
"""返回 (data_ptr, w, h, row_pitch) — 零拷贝 GPU 映射内存。
|
|
92
|
+
|
|
93
|
+
用完必须调用 release_frame(),期间不要再次调用。"""
|
|
94
|
+
if not self._h: return None
|
|
95
|
+
data = ctypes.POINTER(ctypes.c_ubyte)()
|
|
96
|
+
w, h, rp = ctypes.c_int(), ctypes.c_int(), ctypes.c_int()
|
|
97
|
+
if _dll.GetFrameMapped(self._h, ctypes.byref(data), ctypes.byref(w), ctypes.byref(h), ctypes.byref(rp)) == 0:
|
|
98
|
+
return None
|
|
99
|
+
return (ctypes.cast(data, ctypes.c_void_p).value, w.value, h.value, rp.value)
|
|
100
|
+
|
|
101
|
+
def release_frame(self):
|
|
102
|
+
if self._h: _dll.ReleaseMappedFrame(self._h)
|
|
103
|
+
|
|
104
|
+
def stop(self):
|
|
105
|
+
if self._h: _dll.StopCapture(self._h)
|
|
106
|
+
|
|
107
|
+
def pause(self):
|
|
108
|
+
if self._h: _dll.PauseCapture(self._h)
|
|
109
|
+
|
|
110
|
+
def resume(self):
|
|
111
|
+
if self._h: _dll.ResumeCapture(self._h)
|
|
112
|
+
|
|
113
|
+
def set_cursor_capture_enabled(self, enabled: bool):
|
|
114
|
+
"""运行时切换是否捕获鼠标光标"""
|
|
115
|
+
if self._h and hasattr(_dll, 'SetCursorCaptureEnabled'):
|
|
116
|
+
_dll.SetCursorCaptureEnabled(self._h, int(enabled))
|
|
117
|
+
|
|
118
|
+
def is_capturing(self) -> bool:
|
|
119
|
+
return bool(self._h and _dll.IsCapturing(self._h))
|
|
120
|
+
|
|
121
|
+
def is_paused(self) -> bool:
|
|
122
|
+
return bool(self._h and _dll.IsPaused(self._h))
|
|
123
|
+
|
|
124
|
+
def get_frame_count(self) -> int:
|
|
125
|
+
return _dll.GetFrameCount(self._h) if self._h else 0
|
|
126
|
+
|
|
127
|
+
def close(self):
|
|
128
|
+
if self._h:
|
|
129
|
+
_dll.DestroyCapture(self._h)
|
|
130
|
+
self._h = 0
|
|
131
|
+
|
|
132
|
+
def capture_one(self, timeout: float = 0.5) -> Optional[np.ndarray]:
|
|
133
|
+
"""按需捕获一帧:Resume → 等待帧 → 拷贝 → Pause,捕获间隙零开销。
|
|
134
|
+
|
|
135
|
+
返回 BGRA numpy 数组 (h, w, 4),超时返回 None。"""
|
|
136
|
+
self.resume()
|
|
137
|
+
deadline = time.perf_counter() + timeout
|
|
138
|
+
while time.perf_counter() < deadline:
|
|
139
|
+
r = self.get_frame()
|
|
140
|
+
if r:
|
|
141
|
+
break
|
|
142
|
+
time.sleep(0.002)
|
|
143
|
+
else:
|
|
144
|
+
self.pause()
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
ptr, w, h, rp = r
|
|
148
|
+
arr = np.ndarray((h, w, 4), dtype=np.uint8,
|
|
149
|
+
buffer=(ctypes.c_ubyte * (h * rp)).from_address(ptr),
|
|
150
|
+
strides=(rp, 4, 1))
|
|
151
|
+
frame = arr.copy()
|
|
152
|
+
self.release_frame()
|
|
153
|
+
self.pause()
|
|
154
|
+
return frame
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def handle(self) -> int: return self._h
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
__all__ = ['WindowCapture', 'enumerate_windows', 'get_last_error', 'get_active_capture_count']
|
|
Binary file
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: wgc_python
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Windows Graphics Capture 窗口捕获库 — BGRA numpy 帧、按需捕获、零拷贝 GPU 路径
|
|
5
|
+
Author: XuanChenxuan
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/XuanChenxuan/wgc_python
|
|
8
|
+
Project-URL: Repository, https://github.com/XuanChenxuan/wgc_python
|
|
9
|
+
Project-URL: BugTracker, https://github.com/XuanChenxuan/wgc_python/issues
|
|
10
|
+
Keywords: wgc,windows-graphics-capture,screen-capture,automation,game-capture
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Multimedia :: Graphics :: Capture
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Requires-Python: >=3.8
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: numpy
|
|
28
|
+
Requires-Dist: opencv-python
|
|
29
|
+
|
|
30
|
+
# wgc_python
|
|
31
|
+
|
|
32
|
+
[English](README_EN.md) | 简体中文
|
|
33
|
+
|
|
34
|
+
> **🚀 为 Python 自动化而生的窗口捕获库**
|
|
35
|
+
> 高帧率捕获 · 零资源待机 · 无视遮挡 · API 极简
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 为什么选择 wgc_python?
|
|
40
|
+
|
|
41
|
+
### 🎯 专为自动化场景设计
|
|
42
|
+
|
|
43
|
+
你是否在为以下问题困扰?
|
|
44
|
+
|
|
45
|
+
- **mss/BitBlt**:无法捕获被遮挡或后台窗口
|
|
46
|
+
- **PrintWindow**:性能瓶颈,固定 26ms+ 延迟
|
|
47
|
+
- **其他 WGC 封装**:持续运行占用资源,频繁启停开销巨大(50ms+)
|
|
48
|
+
|
|
49
|
+
**wgc_python 通过 Pause/Resume 机制解决了这个矛盾:**
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
# 传统方式:要么持续空转浪费资源,要么频繁启停承受延迟
|
|
53
|
+
start_capture() # 50ms 开销
|
|
54
|
+
get_frame() # 获取截图
|
|
55
|
+
stop_capture() # 销毁会话(50ms)
|
|
56
|
+
# 下次截图又要重新开始...
|
|
57
|
+
|
|
58
|
+
# wgc_python 方式:一次启动,按需截图,零开销待机
|
|
59
|
+
with WindowCapture("窗口", "类名") as cap:
|
|
60
|
+
while running:
|
|
61
|
+
frame = cap.capture_one() # auto Resume → 等待帧 → 拷贝 → Pause
|
|
62
|
+
# 处理图像...
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 📊 性能对比
|
|
66
|
+
|
|
67
|
+
| 方案 | FPS | 后台捕获 | CPU 占用 | 频繁切换开销 | 暂停后 GPU 占用 |
|
|
68
|
+
|------|-----|---------|---------|-------------|----------------|
|
|
69
|
+
| python-mss / BitBlt | ~60 | ❌ | 高 | 低 | N/A (无暂停概念) |
|
|
70
|
+
| PrintWindow | ~38 | ✅ | 中 | 低 | N/A (每次调用即捕获) |
|
|
71
|
+
| 其他 WGC 封装 | 高 | ✅ | 高(持续空转) | 高 (启停会话开销大) | 高 (无法真正暂停) |
|
|
72
|
+
| **wgc_python** | **高** | ✅ | **极低(Pause时归零)** | **极低(原子标志位)** | **归零(无 D3D 操作)** |
|
|
73
|
+
|
|
74
|
+
> 表中为定性对比,具体数值因硬件、窗口内容与场景而异,建议以自己的实测为准。
|
|
75
|
+
|
|
76
|
+
### ✨ 核心优势
|
|
77
|
+
|
|
78
|
+
#### 1. 高帧率
|
|
79
|
+
- WGC 直接捕获 GPU 合成输出,不逐帧截屏,帧率上限远高于 PrintWindow 等 GDI 方案
|
|
80
|
+
- **双缓冲 Staging 纹理**:GPU 异步拷贝,读写互不阻塞
|
|
81
|
+
- **零拷贝友好**:`np.ndarray(strides=...)` 直接从 GPU 映射内存构造视图
|
|
82
|
+
|
|
83
|
+
#### 2. 智能资源管理
|
|
84
|
+
- **Pause/Resume 软暂停**:不销毁不重建 WGC session,仅原子标志位跳过帧处理
|
|
85
|
+
- **capture_one() 自动管理**:Resume → 等待帧 → 拷贝 → Pause,间隙 GPU 驱动零开销
|
|
86
|
+
- **会话复用**:避免频繁创建/销毁 D3D 设备的开销
|
|
87
|
+
|
|
88
|
+
#### 3. 极简 API
|
|
89
|
+
- **capture_one()**:一行代码完成按需捕获,返回 numpy 数组
|
|
90
|
+
- **get_frame()**:零拷贝裸指针路径(高级使用)
|
|
91
|
+
- **线程安全**:C++ 层处理所有多线程复杂性
|
|
92
|
+
|
|
93
|
+
#### 4. 多开并发
|
|
94
|
+
- 同一进程内可同时创建多个捕获会话,互不干扰
|
|
95
|
+
- 每个会话独立 D3D11 设备 + 独立纹理 + 独立 WinRT session,完全隔离
|
|
96
|
+
- 支持同窗口多路并发捕获
|
|
97
|
+
|
|
98
|
+
#### 5. 客户区精准裁剪(默认不截取标题栏/边框)
|
|
99
|
+
- **默认 `client_area_only=True`**:只捕获窗口客户区内容,自动裁剪标题栏和边框,直接输出有效像素
|
|
100
|
+
- **设置 `client_area_only=False`**:捕获整个窗口(含标题栏和边框),满足 UI 记录场景
|
|
101
|
+
- DPI 感知:自动修正高 DPI 缩放偏移,裁剪精度像素级
|
|
102
|
+
- GPU 级裁剪:`CopySubresourceRegion` 在 GPU 上完成裁剪,不浪费带宽和 CPU
|
|
103
|
+
|
|
104
|
+
#### 6. 光标捕获开关
|
|
105
|
+
- **默认 `capture_cursor=True`**:画面包含鼠标光标,与常规录屏行为一致
|
|
106
|
+
- **设置 `capture_cursor=False`**:画面不含鼠标指针,适合自动化 / 数据采集场景(也可用 `set_cursor_capture_enabled()` 运行时切换)
|
|
107
|
+
- 需 Windows 10 2004 (19041) 及以上系统,旧系统自动忽略该选项
|
|
108
|
+
|
|
109
|
+
#### 7. 无视遮挡
|
|
110
|
+
- 支持捕获被遮挡、最小化、后台窗口
|
|
111
|
+
- 完美适配游戏、桌面应用等各种场景
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 快速开始
|
|
116
|
+
|
|
117
|
+
### 安装
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pip install wgc-python
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 基础用法
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from wgc_python import WindowCapture, enumerate_windows
|
|
127
|
+
|
|
128
|
+
# 枚举所有窗口
|
|
129
|
+
for title, class_name in enumerate_windows():
|
|
130
|
+
print(f"{title} ({class_name})")
|
|
131
|
+
|
|
132
|
+
# 按需捕获(推荐 —— 零开销待机)
|
|
133
|
+
with WindowCapture("窗口标题", "窗口类名") as cap:
|
|
134
|
+
frame = cap.capture_one() # BGRA numpy 数组,shape (h, w, 4)
|
|
135
|
+
if frame is not None:
|
|
136
|
+
print(f"捕获成功: {frame.shape}")
|
|
137
|
+
|
|
138
|
+
# 客户区裁剪演示
|
|
139
|
+
# 默认 client_area_only=True:只截取客户区,不含标题栏/边框
|
|
140
|
+
cap_client = WindowCapture("记事本", "Notepad") # 只截内容
|
|
141
|
+
cap_full = WindowCapture("记事本", "Notepad", client_area_only=False) # 含标题栏
|
|
142
|
+
frame_client = cap_client.capture_one() # 只有编辑区
|
|
143
|
+
frame_full = cap_full.capture_one() # 含标题栏 + 菜单 + 编辑区
|
|
144
|
+
cap_client.close()
|
|
145
|
+
cap_full.close()
|
|
146
|
+
|
|
147
|
+
# 不捕获鼠标光标(默认 capture_cursor=True,保持旧版行为)
|
|
148
|
+
cap = WindowCapture("记事本", "Notepad", capture_cursor=False)
|
|
149
|
+
frame = cap.capture_one() # 画面不含鼠标指针
|
|
150
|
+
cap.set_cursor_capture_enabled(True) # 也支持运行时切换
|
|
151
|
+
cap.close()
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 自动化最佳实践
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from wgc_python import WindowCapture
|
|
158
|
+
|
|
159
|
+
cap = WindowCapture("游戏窗口", "UnityWndClass")
|
|
160
|
+
|
|
161
|
+
while True:
|
|
162
|
+
frame = cap.capture_one(timeout=1.0)
|
|
163
|
+
if frame is not None:
|
|
164
|
+
# frame 是 BGRA numpy 数组,直接用于 OpenCV/模板匹配
|
|
165
|
+
pass
|
|
166
|
+
time.sleep(1)
|
|
167
|
+
|
|
168
|
+
cap.close()
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 零拷贝高级用法
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
from wgc_python import WindowCapture
|
|
175
|
+
import numpy as np
|
|
176
|
+
import ctypes
|
|
177
|
+
|
|
178
|
+
with WindowCapture("窗口", "类名") as cap:
|
|
179
|
+
cap.resume()
|
|
180
|
+
r = cap.get_frame() # (ptr, w, h, row_pitch) — GPU 映射裸指针
|
|
181
|
+
if r:
|
|
182
|
+
ptr, w, h, rp = r
|
|
183
|
+
arr = np.ndarray((h, w, 4), dtype=np.uint8,
|
|
184
|
+
buffer=(ctypes.c_ubyte * (h * rp)).from_address(ptr),
|
|
185
|
+
strides=(rp, 4, 1))
|
|
186
|
+
# arr 是 GPU 内存的零拷贝视图
|
|
187
|
+
cap.release_frame()
|
|
188
|
+
cap.pause()
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 实时显示
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from wgc_python import WindowCapture
|
|
195
|
+
import cv2
|
|
196
|
+
|
|
197
|
+
with WindowCapture("窗口标题", "窗口类名") as cap:
|
|
198
|
+
while True:
|
|
199
|
+
frame = cap.capture_one()
|
|
200
|
+
if frame is not None:
|
|
201
|
+
cv2.imshow("Capture", cv2.cvtColor(frame, cv2.COLOR_BGRA2BGR))
|
|
202
|
+
if cv2.waitKey(1) & 0xFF == ord('q'):
|
|
203
|
+
break
|
|
204
|
+
cv2.destroyAllWindows()
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## API 参考
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
from wgc_python import (
|
|
213
|
+
WindowCapture, # 窗口捕获类(上下文管理器支持)
|
|
214
|
+
enumerate_windows, # 枚举所有可见窗口
|
|
215
|
+
get_last_error, # 获取最后错误信息(线程安全)
|
|
216
|
+
get_active_capture_count, # 获取活跃捕获数
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# WindowCapture 类方法:
|
|
220
|
+
# cap = WindowCapture(title, class_name, client_area_only=True, capture_cursor=True)
|
|
221
|
+
#
|
|
222
|
+
# cap.capture_one(timeout=0.5) -> np.ndarray | None ★ 推荐
|
|
223
|
+
# 自动 Resume → 等待帧 → 拷贝为 numpy → Pause
|
|
224
|
+
# 捕获间隙 WGC 完全休眠,GPU 驱动零开销
|
|
225
|
+
#
|
|
226
|
+
# cap.get_frame() -> (ptr, w, h, row_pitch) | None
|
|
227
|
+
# cap.release_frame() # 释放 GPU 映射
|
|
228
|
+
# cap.pause() # 暂停捕获(零资源待机)
|
|
229
|
+
# cap.resume() # 恢复捕获
|
|
230
|
+
# cap.set_cursor_capture_enabled(enabled) # 运行时切换光标捕获
|
|
231
|
+
# cap.stop() # 停止帧到达
|
|
232
|
+
# cap.close() # 销毁会话
|
|
233
|
+
# cap.is_capturing() -> bool
|
|
234
|
+
# cap.is_paused() -> bool
|
|
235
|
+
# cap.get_frame_count() -> int
|
|
236
|
+
# cap.handle -> int (DLL handle)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## 技术架构
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
WGC捕获 → GPU Surface纹理
|
|
245
|
+
│
|
|
246
|
+
┌────────▼────────┐
|
|
247
|
+
│ FrameArrived │
|
|
248
|
+
│ if pausing → ↑ │ ← Pause时直接返回,零 D3D 操作
|
|
249
|
+
└────────┬─────────┘
|
|
250
|
+
│
|
|
251
|
+
CopyResource (GPU异步复制)
|
|
252
|
+
↓
|
|
253
|
+
┌─────────────────────────┐
|
|
254
|
+
│ 双缓冲Staging纹理 │
|
|
255
|
+
│ [0] 写入 ←→ [1] 读取 │
|
|
256
|
+
│ m_textureInUse 防冲撞 │
|
|
257
|
+
└─────────────────────────┘
|
|
258
|
+
↓
|
|
259
|
+
Map (永久映射 GPU 内存)
|
|
260
|
+
↓
|
|
261
|
+
┌────── 零拷贝输出 ───────┐
|
|
262
|
+
│ get_frame() │
|
|
263
|
+
│ 返回裸指针 → numpy零拷贝 │
|
|
264
|
+
│ 需手动 release_frame() │
|
|
265
|
+
└──────────────────────────┘
|
|
266
|
+
|
|
267
|
+
┌────── 一键捕获 ──────────┐
|
|
268
|
+
│ capture_one() │
|
|
269
|
+
│ auto Pause/Resume │
|
|
270
|
+
│ 返回 numpy 数组 │
|
|
271
|
+
│ 间隙 GPU 驱动零开销 │
|
|
272
|
+
└──────────────────────────┘
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Pause/Resume 工作原理
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
用户调用 cap.pause()
|
|
279
|
+
│
|
|
280
|
+
m_isPaused = true ◄──── 原子标志位,微秒级
|
|
281
|
+
m_readableStagingIndex = -1
|
|
282
|
+
│
|
|
283
|
+
┌────▼────────────────────────────────────────────┐
|
|
284
|
+
│ FrameArrived 回调(WGC 仍会触发) │
|
|
285
|
+
│ │
|
|
286
|
+
│ lock(mutex); │
|
|
287
|
+
│ if (m_isPaused) return; // ← 纯CPU判断,跳过│
|
|
288
|
+
│ // ↓ 以下只在 resume 后执行 ↓ │
|
|
289
|
+
│ CopyResource(staging, frame); │
|
|
290
|
+
│ m_readableStagingIndex = idx; │
|
|
291
|
+
│ unlock(mutex); │
|
|
292
|
+
└────▲────────────────┬───────────────────────────┘
|
|
293
|
+
│ │
|
|
294
|
+
用户调用 cap.resume() MapFrame 检查 readableStagingIndex
|
|
295
|
+
m_isPaused = false <0 → 最近帧尚未就绪,返回 false
|
|
296
|
+
|
|
297
|
+
不销毁 WGC session / 不重建 D3D 设备 / 不重新注册回调
|
|
298
|
+
→ 恢复零延迟,无突刺
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## 文件结构
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
wgc_python/
|
|
307
|
+
├── wgc_python/ # Python 包
|
|
308
|
+
│ ├── __init__.py # Python API(ctypes FFI)
|
|
309
|
+
│ └── wgc_python.dll # 编译后的 DLL
|
|
310
|
+
├── wgc_python_dll/ # C++ DLL 项目
|
|
311
|
+
│ ├── WGCWindowCapture.h/cpp # WGC 捕获核心(双缓冲 + 零拷贝)
|
|
312
|
+
│ ├── WGCExport.h/cpp # DLL 导出(含线程安全错误处理)
|
|
313
|
+
│ ├── D3DInterop.cpp # D3D11 设备互操作
|
|
314
|
+
│ ├── WindowEnumerator.h/cpp # 窗口枚举
|
|
315
|
+
│ ├── pch.h # 预编译头
|
|
316
|
+
│ └── packages/ # NuGet 包
|
|
317
|
+
├── test.py # 功能测试
|
|
318
|
+
├── demon.py # 多线程实时显示示例
|
|
319
|
+
├── pyproject.toml # pip 构建配置
|
|
320
|
+
├── BUILD.md / BUILD_EN.md # 构建说明(中/英)
|
|
321
|
+
├── README.md / README_EN.md # 使用文档(中/英)
|
|
322
|
+
├── CONTRIBUTING.md # 贡献指南
|
|
323
|
+
├── CODE_OF_CONDUCT.md # 行为准则
|
|
324
|
+
├── LICENSE # MIT 许可证
|
|
325
|
+
└── requirements.txt # Python 依赖
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## 系统要求
|
|
331
|
+
|
|
332
|
+
- Windows 10 1903+ (Build 18362),光标捕获开关需 2004+ (Build 19041)
|
|
333
|
+
- Python 3.8+
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## 构建 DLL
|
|
338
|
+
|
|
339
|
+
详见 [BUILD.md](BUILD.md)
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## 故障排除
|
|
344
|
+
|
|
345
|
+
| 问题 | 解决方案 |
|
|
346
|
+
|------|---------|
|
|
347
|
+
| DLL 未找到 | 确保 `wgc_python.dll` 在正确位置 |
|
|
348
|
+
| 捕获失败 | 检查窗口是否可见,Windows 版本 >= 1903 |
|
|
349
|
+
| 中文路径保存失败 | 使用 `cv2.imencode` + `open().write()` 代替 `cv2.imwrite` |
|
|
350
|
+
| 依赖缺失 | `pip install numpy opencv-python` |
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## 适用场景
|
|
355
|
+
|
|
356
|
+
- ✅ 游戏 AI / 自动化脚本
|
|
357
|
+
- ✅ RPA 流程自动化
|
|
358
|
+
- ✅ 屏幕录制 / 直播
|
|
359
|
+
- ✅ UI 自动化测试
|
|
360
|
+
- ✅ 计算机视觉应用
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## 鸣谢
|
|
365
|
+
|
|
366
|
+
本项目基于 [robmikh/Win32CaptureSample](https://github.com/robmikh/Win32CaptureSample) 开发。
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## License
|
|
371
|
+
|
|
372
|
+
MIT License
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
wgc_python/__init__.py
|
|
5
|
+
wgc_python/wgc_python.dll
|
|
6
|
+
wgc_python.egg-info/PKG-INFO
|
|
7
|
+
wgc_python.egg-info/SOURCES.txt
|
|
8
|
+
wgc_python.egg-info/dependency_links.txt
|
|
9
|
+
wgc_python.egg-info/requires.txt
|
|
10
|
+
wgc_python.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|