dxcam 0.1.0.dev2__tar.gz → 0.2.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.
- dxcam-0.2.0/MANIFEST.in +1 -0
- {dxcam-0.1.0.dev2/dxcam.egg-info → dxcam-0.2.0}/PKG-INFO +103 -28
- dxcam-0.1.0.dev2/PKG-INFO → dxcam-0.2.0/README.md +88 -58
- {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/__init__.py +96 -5
- {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/_libs/d3d11.py +10 -0
- {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/_libs/dxgi.py +11 -0
- dxcam-0.2.0/dxcam/core/__init__.py +10 -0
- dxcam-0.2.0/dxcam/core/backend.py +73 -0
- dxcam-0.2.0/dxcam/core/duplicator.py +22 -0
- dxcam-0.1.0.dev2/dxcam/core/duplicator.py → dxcam-0.2.0/dxcam/core/dxgi_duplicator.py +7 -3
- dxcam-0.2.0/dxcam/core/winrt_duplicator.py +549 -0
- {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/dxcam.py +248 -36
- dxcam-0.2.0/dxcam/processor/__init__.py +9 -0
- dxcam-0.2.0/dxcam/processor/_numpy_kernels.pyx +725 -0
- dxcam-0.2.0/dxcam/processor/base.py +125 -0
- dxcam-0.2.0/dxcam/processor/cv2_processor.py +356 -0
- dxcam-0.2.0/dxcam/processor/numpy_processor.py +128 -0
- dxcam-0.2.0/dxcam/types.py +47 -0
- dxcam-0.2.0/dxcam.egg-info/PKG-INFO +313 -0
- {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam.egg-info/SOURCES.txt +9 -1
- dxcam-0.2.0/dxcam.egg-info/requires.txt +19 -0
- {dxcam-0.1.0.dev2 → dxcam-0.2.0}/pyproject.toml +29 -1
- dxcam-0.2.0/setup.py +46 -0
- dxcam-0.2.0/tests/test_bgra_conversion_parity.py +88 -0
- dxcam-0.1.0.dev2/README.md +0 -207
- dxcam-0.1.0.dev2/dxcam/core/__init__.py +0 -7
- dxcam-0.1.0.dev2/dxcam/processor/__init__.py +0 -1
- dxcam-0.1.0.dev2/dxcam/processor/base.py +0 -53
- dxcam-0.1.0.dev2/dxcam/processor/numpy_processor.py +0 -171
- dxcam-0.1.0.dev2/dxcam/types.py +0 -10
- dxcam-0.1.0.dev2/dxcam.egg-info/requires.txt +0 -5
- {dxcam-0.1.0.dev2 → dxcam-0.2.0}/LICENSE +0 -0
- {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/_libs/__init__.py +0 -0
- {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/_libs/user32.py +0 -0
- {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/core/device.py +0 -0
- {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/core/output.py +0 -0
- {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/core/stagesurf.py +0 -0
- {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/util/__init__.py +0 -0
- {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/util/io.py +0 -0
- {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/util/timer.py +0 -0
- {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam.egg-info/dependency_links.txt +0 -0
- {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam.egg-info/top_level.txt +0 -0
- {dxcam-0.1.0.dev2 → dxcam-0.2.0}/setup.cfg +0 -0
dxcam-0.2.0/MANIFEST.in
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
recursive-include dxcam/processor *.pyx
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dxcam
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A Python high-performance screenshot library for Windows using Desktop Duplication API
|
|
5
5
|
Author: ra1nty
|
|
6
6
|
License-Expression: MIT
|
|
@@ -11,9 +11,11 @@ Classifier: Development Status :: 4 - Beta
|
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
13
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
19
|
Classifier: Operating System :: Microsoft :: Windows
|
|
18
20
|
Classifier: Operating System :: Microsoft :: Windows :: Windows 10
|
|
19
21
|
Classifier: Operating System :: Microsoft :: Windows :: Windows 11
|
|
@@ -27,6 +29,18 @@ Requires-Dist: comtypes
|
|
|
27
29
|
Requires-Dist: numpy
|
|
28
30
|
Provides-Extra: cv2
|
|
29
31
|
Requires-Dist: opencv-python; extra == "cv2"
|
|
32
|
+
Provides-Extra: cython
|
|
33
|
+
Requires-Dist: Cython==3.1.1; extra == "cython"
|
|
34
|
+
Provides-Extra: winrt
|
|
35
|
+
Requires-Dist: winrt-runtime>=3.2.1; extra == "winrt"
|
|
36
|
+
Requires-Dist: winrt-Windows.Foundation>=3.2.1; extra == "winrt"
|
|
37
|
+
Requires-Dist: winrt-Windows.Foundation.Collections>=3.2.1; extra == "winrt"
|
|
38
|
+
Requires-Dist: winrt-Windows.Graphics>=3.2.1; extra == "winrt"
|
|
39
|
+
Requires-Dist: winrt-Windows.Graphics.Capture>=3.2.1; extra == "winrt"
|
|
40
|
+
Requires-Dist: winrt-Windows.Graphics.Capture.Interop>=3.2.1; extra == "winrt"
|
|
41
|
+
Requires-Dist: winrt-Windows.Graphics.DirectX>=3.2.1; extra == "winrt"
|
|
42
|
+
Requires-Dist: winrt-Windows.Graphics.DirectX.Direct3D11>=3.2.1; extra == "winrt"
|
|
43
|
+
Requires-Dist: winrt-Windows.Graphics.DirectX.Direct3D11.Interop>=3.2.1; extra == "winrt"
|
|
30
44
|
Dynamic: license-file
|
|
31
45
|
|
|
32
46
|
# **DXcam**
|
|
@@ -39,42 +53,41 @@ camera = dxcam.create()
|
|
|
39
53
|
frame = camera.grab()
|
|
40
54
|
```
|
|
41
55
|
|
|
56
|
+
> **Live API Docs:** [https://ra1nty.github.io/DXcam/](https://ra1nty.github.io/DXcam/)
|
|
57
|
+
|
|
42
58
|
## Introduction
|
|
43
59
|
DXcam is a high-performance python screenshot and capture library for Windows based on the Desktop Duplication API.
|
|
44
60
|
It is designed for low-latency, high-FPS capture pipelines (including full-screen Direct3D applications).
|
|
45
61
|
|
|
46
62
|
Compared with common Python alternatives, DXcam focuses on:
|
|
47
|
-
- Higher capture throughput
|
|
63
|
+
- Higher capture throughput (240+fps on 1080p)
|
|
48
64
|
- Stable capture for full-screen exclusive Direct3D apps
|
|
49
|
-
-
|
|
50
|
-
-
|
|
65
|
+
- Better FPS pacing for continuous video capture
|
|
66
|
+
- Support DXGI / Windows Graphics Capture dual backend
|
|
67
|
+
- Seamless integration for AI Agent / Computer Vision use cases.
|
|
51
68
|
|
|
52
69
|
## Installation
|
|
53
70
|
### From PyPI (pip)
|
|
54
|
-
Recommended (with OpenCV):
|
|
55
|
-
```bash
|
|
56
|
-
pip install "dxcam[cv2]"
|
|
57
|
-
```
|
|
58
|
-
|
|
59
71
|
Minimal install:
|
|
60
72
|
```bash
|
|
61
73
|
pip install dxcam
|
|
62
74
|
```
|
|
63
75
|
|
|
64
|
-
|
|
76
|
+
Full feature: (includes OpenCV-based color conversion, WinRT capture backend support:):
|
|
65
77
|
```bash
|
|
66
|
-
|
|
67
|
-
# include OpenCV
|
|
68
|
-
uv sync --extra cv2
|
|
78
|
+
pip install "dxcam[cv2,winrt]"
|
|
69
79
|
```
|
|
70
80
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
81
|
+
Notes:
|
|
82
|
+
- Official Windows wheels are built for CPython `3.10` to `3.14`.
|
|
83
|
+
- Binary wheels include the Cython kernels used by processor backends.
|
|
84
|
+
|
|
85
|
+
### From source
|
|
86
|
+
Please refer to [CONTRIBUTING](CONTRIBUTING.md).
|
|
87
|
+
|
|
88
|
+
### Contributing / Dev
|
|
89
|
+
Contributions are welcome!
|
|
90
|
+
Development setup and contributor workflow are documented in [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
78
91
|
|
|
79
92
|
## Usage
|
|
80
93
|
Each output (monitor) is associated with one `DXCamera` instance.
|
|
@@ -84,13 +97,21 @@ import dxcam
|
|
|
84
97
|
camera = dxcam.create() # primary output on device 0
|
|
85
98
|
```
|
|
86
99
|
|
|
100
|
+
To specify backend:
|
|
101
|
+
```python
|
|
102
|
+
camera = dxcam.create(
|
|
103
|
+
backend="dxgi", # default Desktop Duplication backend
|
|
104
|
+
processor_backend="cv2" # default OpenCV processor
|
|
105
|
+
)
|
|
106
|
+
```
|
|
107
|
+
|
|
87
108
|
### Screenshot
|
|
88
109
|
```python
|
|
89
110
|
frame = camera.grab()
|
|
90
111
|
```
|
|
91
|
-
`grab()` returns a `numpy.ndarray`. `None` if no new frame is available since the last capture
|
|
112
|
+
`grab()` returns a `numpy.ndarray`. `None` if no new frame is available since the last capture (for backward compatibility); use `camera.grab(new_frame_only=False)` to make dxcam always return the latest frame.
|
|
92
113
|
|
|
93
|
-
Use `copy=False` (or `camera.grab_view()`) for a zero-copy view.
|
|
114
|
+
Use `copy=False` (or `camera.grab_view()`) for a zero-copy view. This is faster, but the returned buffer can be overwritten by later captures.
|
|
94
115
|
|
|
95
116
|
To capture a region:
|
|
96
117
|
```python
|
|
@@ -113,14 +134,16 @@ camera.is_capturing # False
|
|
|
113
134
|
for _ in range(1000):
|
|
114
135
|
frame = camera.get_latest_frame() # blocks until a frame is available
|
|
115
136
|
```
|
|
116
|
-
>The screen capture mode spins up a thread
|
|
137
|
+
>The screen capture mode spins up a thread that polls newly rendered frames and stores them in an in-memory ring buffer. The blocking and `video_mode` behavior is designed for downstream video recording and machine learning workloads.
|
|
117
138
|
|
|
118
139
|
Useful variants:
|
|
119
140
|
- `camera.get_latest_frame(with_timestamp=True)` -> `(frame, frame_timestamp)` -> return frame timestamp
|
|
120
141
|
- `camera.get_latest_frame_view()` -> zero-copy view into the frame buffer
|
|
121
142
|
- `camera.grab(copy=False)` / `camera.grab_view()` -> zero-copy latest-frame snapshot
|
|
122
143
|
|
|
123
|
-
|
|
144
|
+
> When `start()` capture is running, calling `grab()` reads from the in-memory ring buffer instead of directly polling DXGI.
|
|
145
|
+
|
|
146
|
+
**Full API Docs:** [https://ra1nty.github.io/DXcam/](https://ra1nty.github.io/DXcam/)
|
|
124
147
|
|
|
125
148
|
## Advanced Usage and Remarks
|
|
126
149
|
### Multiple monitors / GPUs
|
|
@@ -153,8 +176,8 @@ Supported modes: `"RGB"`, `"RGBA"`, `"BGR"`, `"BGRA"`, `"GRAY"`.
|
|
|
153
176
|
|
|
154
177
|
Notes:
|
|
155
178
|
- Data is returned as `numpy.ndarray`.
|
|
156
|
-
- `BGRA` does not require OpenCV.
|
|
157
|
-
-
|
|
179
|
+
- `BGRA` does not require OpenCV and is the leanest dependency path.
|
|
180
|
+
- `RGB`, `BGR`, `RGBA`, `GRAY` require conversion (`cv2` or compiled `numpy` backend).
|
|
158
181
|
|
|
159
182
|
### Frame Buffer
|
|
160
183
|
DXcam uses a fixed-size ring buffer in-memory. New frames overwrite old frames when full.
|
|
@@ -181,7 +204,8 @@ frame, ts = camera.get_latest_frame(with_timestamp=True)
|
|
|
181
204
|
camera.stop()
|
|
182
205
|
```
|
|
183
206
|
|
|
184
|
-
|
|
207
|
+
For `backend="dxgi"`, this value comes from `DXGI_OUTDUPL_FRAME_INFO.LastPresentTime`.
|
|
208
|
+
For `backend="winrt"`, this value is derived from WinRT `SystemRelativeTime`.
|
|
185
209
|
|
|
186
210
|
### Video Mode
|
|
187
211
|
With `video_mode=True`, DXcam fills the buffer at target FPS, reusing the previous frame if needed, even if no new frame is rendered.
|
|
@@ -204,8 +228,52 @@ camera.stop()
|
|
|
204
228
|
writer.release()
|
|
205
229
|
```
|
|
206
230
|
|
|
231
|
+
### Capture Backend
|
|
232
|
+
DXcam supports two capture backends:
|
|
233
|
+
- `dxgi` (default): Desktop Duplication API path with broad compatibility.
|
|
234
|
+
- `winrt`: Windows Graphics Capture path.
|
|
235
|
+
|
|
236
|
+
Use it like this:
|
|
237
|
+
```python
|
|
238
|
+
camera = dxcam.create(backend="dxgi")
|
|
239
|
+
camera = dxcam.create(backend="winrt")
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Guideline:
|
|
243
|
+
- If you need cursor rendering, use `winrt`.
|
|
244
|
+
- Start with `dxgi` for most workloads, especially one-shot grab.
|
|
245
|
+
- Try `winrt` if it performs better on your machine or fits your app constraints.
|
|
246
|
+
|
|
247
|
+
### Processor Backend
|
|
248
|
+
DXcam capture backends (`dxgi`/`winrt`) first acquire a BGRA frame.
|
|
249
|
+
The processor backend then handles post-processing:
|
|
250
|
+
- optional rotation/cropping preparation
|
|
251
|
+
- color conversion to your `output_color`
|
|
252
|
+
|
|
253
|
+
Recommended backend choice:
|
|
254
|
+
- OpenCV installed: use `cv2` (default)
|
|
255
|
+
- No OpenCV installed: use `numpy` (Cython kernels)
|
|
256
|
+
|
|
257
|
+
Use it like this:
|
|
258
|
+
```python
|
|
259
|
+
camera = dxcam.create(processor_backend="cv2")
|
|
260
|
+
camera = dxcam.create(processor_backend="numpy")
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Official Windows wheels already include the compiled NumPy kernels.
|
|
264
|
+
|
|
265
|
+
Only for source installs:
|
|
266
|
+
```bash
|
|
267
|
+
set DXCAM_BUILD_CYTHON=1
|
|
268
|
+
pip install -e .[cython] --no-build-isolation
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
If `processor_backend="numpy"` is selected but compiled kernels are unavailable,
|
|
272
|
+
DXcam logs a warning and falls back to `cv2` behavior. In that fallback path,
|
|
273
|
+
install OpenCV for non-`BGRA` output modes.
|
|
274
|
+
|
|
207
275
|
### Safely Releasing Resources
|
|
208
|
-
`release()` stops capture, frees buffers, and releases
|
|
276
|
+
`release()` stops capture, frees buffers, and releases capture resources.
|
|
209
277
|
After `release()`, the same instance cannot be reused.
|
|
210
278
|
|
|
211
279
|
```python
|
|
@@ -230,6 +298,13 @@ The benchmark is across 5 runs, with a light-moderate usage on my PC (5900X + 30
|
|
|
230
298
|
| 60fps | 61.71, 0.26 :checkered_flag: | N/A | 47.11, 1.33 |
|
|
231
299
|
| 30fps | 30.08, 0.02 :checkered_flag: | N/A | 21.24, 0.17 |
|
|
232
300
|
|
|
301
|
+
Processor backend comparison helper:
|
|
302
|
+
```bash
|
|
303
|
+
python benchmarks/dxcam_processor_compare.py --backend dxgi --target-fps 120 --target-frames 1000
|
|
304
|
+
python benchmarks/dxcam_capture.py --backend dxgi --processor-backend numpy
|
|
305
|
+
python benchmarks/numpy_processor_micro.py --width 3840 --height 2160 --modes RGB --variants process into --processor-backends cv2 numpy
|
|
306
|
+
```
|
|
307
|
+
|
|
233
308
|
|
|
234
309
|
## Work Referenced
|
|
235
310
|
|
|
@@ -1,34 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: dxcam
|
|
3
|
-
Version: 0.1.0.dev2
|
|
4
|
-
Summary: A Python high-performance screenshot library for Windows using Desktop Duplication API
|
|
5
|
-
Author: ra1nty
|
|
6
|
-
License-Expression: MIT
|
|
7
|
-
Project-URL: Source, https://github.com/ra1nty/DXcam
|
|
8
|
-
Project-URL: Tracker, https://github.com/ra1nty/DXcam/issues
|
|
9
|
-
Keywords: screen,screenshot,screencapture,screengrab,windows
|
|
10
|
-
Classifier: Development Status :: 4 - Beta
|
|
11
|
-
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
-
Classifier: Operating System :: Microsoft :: Windows
|
|
18
|
-
Classifier: Operating System :: Microsoft :: Windows :: Windows 10
|
|
19
|
-
Classifier: Operating System :: Microsoft :: Windows :: Windows 11
|
|
20
|
-
Classifier: Topic :: Multimedia :: Graphics
|
|
21
|
-
Classifier: Topic :: Multimedia :: Graphics :: Capture
|
|
22
|
-
Classifier: Topic :: Multimedia :: Graphics :: Capture :: Screen Capture
|
|
23
|
-
Requires-Python: >=3.10
|
|
24
|
-
Description-Content-Type: text/markdown
|
|
25
|
-
License-File: LICENSE
|
|
26
|
-
Requires-Dist: comtypes
|
|
27
|
-
Requires-Dist: numpy
|
|
28
|
-
Provides-Extra: cv2
|
|
29
|
-
Requires-Dist: opencv-python; extra == "cv2"
|
|
30
|
-
Dynamic: license-file
|
|
31
|
-
|
|
32
1
|
# **DXcam**
|
|
33
2
|
> ***Fast Python Screen Capture for Windows - Updated 2026***
|
|
34
3
|
|
|
@@ -39,42 +8,41 @@ camera = dxcam.create()
|
|
|
39
8
|
frame = camera.grab()
|
|
40
9
|
```
|
|
41
10
|
|
|
11
|
+
> **Live API Docs:** [https://ra1nty.github.io/DXcam/](https://ra1nty.github.io/DXcam/)
|
|
12
|
+
|
|
42
13
|
## Introduction
|
|
43
14
|
DXcam is a high-performance python screenshot and capture library for Windows based on the Desktop Duplication API.
|
|
44
15
|
It is designed for low-latency, high-FPS capture pipelines (including full-screen Direct3D applications).
|
|
45
16
|
|
|
46
17
|
Compared with common Python alternatives, DXcam focuses on:
|
|
47
|
-
- Higher capture throughput
|
|
18
|
+
- Higher capture throughput (240+fps on 1080p)
|
|
48
19
|
- Stable capture for full-screen exclusive Direct3D apps
|
|
49
|
-
-
|
|
50
|
-
-
|
|
20
|
+
- Better FPS pacing for continuous video capture
|
|
21
|
+
- Support DXGI / Windows Graphics Capture dual backend
|
|
22
|
+
- Seamless integration for AI Agent / Computer Vision use cases.
|
|
51
23
|
|
|
52
24
|
## Installation
|
|
53
25
|
### From PyPI (pip)
|
|
54
|
-
Recommended (with OpenCV):
|
|
55
|
-
```bash
|
|
56
|
-
pip install "dxcam[cv2]"
|
|
57
|
-
```
|
|
58
|
-
|
|
59
26
|
Minimal install:
|
|
60
27
|
```bash
|
|
61
28
|
pip install dxcam
|
|
62
29
|
```
|
|
63
30
|
|
|
64
|
-
|
|
31
|
+
Full feature: (includes OpenCV-based color conversion, WinRT capture backend support:):
|
|
65
32
|
```bash
|
|
66
|
-
|
|
67
|
-
# include OpenCV
|
|
68
|
-
uv sync --extra cv2
|
|
33
|
+
pip install "dxcam[cv2,winrt]"
|
|
69
34
|
```
|
|
70
35
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
36
|
+
Notes:
|
|
37
|
+
- Official Windows wheels are built for CPython `3.10` to `3.14`.
|
|
38
|
+
- Binary wheels include the Cython kernels used by processor backends.
|
|
39
|
+
|
|
40
|
+
### From source
|
|
41
|
+
Please refer to [CONTRIBUTING](CONTRIBUTING.md).
|
|
42
|
+
|
|
43
|
+
### Contributing / Dev
|
|
44
|
+
Contributions are welcome!
|
|
45
|
+
Development setup and contributor workflow are documented in [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
78
46
|
|
|
79
47
|
## Usage
|
|
80
48
|
Each output (monitor) is associated with one `DXCamera` instance.
|
|
@@ -84,13 +52,21 @@ import dxcam
|
|
|
84
52
|
camera = dxcam.create() # primary output on device 0
|
|
85
53
|
```
|
|
86
54
|
|
|
55
|
+
To specify backend:
|
|
56
|
+
```python
|
|
57
|
+
camera = dxcam.create(
|
|
58
|
+
backend="dxgi", # default Desktop Duplication backend
|
|
59
|
+
processor_backend="cv2" # default OpenCV processor
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
87
63
|
### Screenshot
|
|
88
64
|
```python
|
|
89
65
|
frame = camera.grab()
|
|
90
66
|
```
|
|
91
|
-
`grab()` returns a `numpy.ndarray`. `None` if no new frame is available since the last capture
|
|
67
|
+
`grab()` returns a `numpy.ndarray`. `None` if no new frame is available since the last capture (for backward compatibility); use `camera.grab(new_frame_only=False)` to make dxcam always return the latest frame.
|
|
92
68
|
|
|
93
|
-
Use `copy=False` (or `camera.grab_view()`) for a zero-copy view.
|
|
69
|
+
Use `copy=False` (or `camera.grab_view()`) for a zero-copy view. This is faster, but the returned buffer can be overwritten by later captures.
|
|
94
70
|
|
|
95
71
|
To capture a region:
|
|
96
72
|
```python
|
|
@@ -113,14 +89,16 @@ camera.is_capturing # False
|
|
|
113
89
|
for _ in range(1000):
|
|
114
90
|
frame = camera.get_latest_frame() # blocks until a frame is available
|
|
115
91
|
```
|
|
116
|
-
>The screen capture mode spins up a thread
|
|
92
|
+
>The screen capture mode spins up a thread that polls newly rendered frames and stores them in an in-memory ring buffer. The blocking and `video_mode` behavior is designed for downstream video recording and machine learning workloads.
|
|
117
93
|
|
|
118
94
|
Useful variants:
|
|
119
95
|
- `camera.get_latest_frame(with_timestamp=True)` -> `(frame, frame_timestamp)` -> return frame timestamp
|
|
120
96
|
- `camera.get_latest_frame_view()` -> zero-copy view into the frame buffer
|
|
121
97
|
- `camera.grab(copy=False)` / `camera.grab_view()` -> zero-copy latest-frame snapshot
|
|
122
98
|
|
|
123
|
-
|
|
99
|
+
> When `start()` capture is running, calling `grab()` reads from the in-memory ring buffer instead of directly polling DXGI.
|
|
100
|
+
|
|
101
|
+
**Full API Docs:** [https://ra1nty.github.io/DXcam/](https://ra1nty.github.io/DXcam/)
|
|
124
102
|
|
|
125
103
|
## Advanced Usage and Remarks
|
|
126
104
|
### Multiple monitors / GPUs
|
|
@@ -153,8 +131,8 @@ Supported modes: `"RGB"`, `"RGBA"`, `"BGR"`, `"BGRA"`, `"GRAY"`.
|
|
|
153
131
|
|
|
154
132
|
Notes:
|
|
155
133
|
- Data is returned as `numpy.ndarray`.
|
|
156
|
-
- `BGRA` does not require OpenCV.
|
|
157
|
-
-
|
|
134
|
+
- `BGRA` does not require OpenCV and is the leanest dependency path.
|
|
135
|
+
- `RGB`, `BGR`, `RGBA`, `GRAY` require conversion (`cv2` or compiled `numpy` backend).
|
|
158
136
|
|
|
159
137
|
### Frame Buffer
|
|
160
138
|
DXcam uses a fixed-size ring buffer in-memory. New frames overwrite old frames when full.
|
|
@@ -181,7 +159,8 @@ frame, ts = camera.get_latest_frame(with_timestamp=True)
|
|
|
181
159
|
camera.stop()
|
|
182
160
|
```
|
|
183
161
|
|
|
184
|
-
|
|
162
|
+
For `backend="dxgi"`, this value comes from `DXGI_OUTDUPL_FRAME_INFO.LastPresentTime`.
|
|
163
|
+
For `backend="winrt"`, this value is derived from WinRT `SystemRelativeTime`.
|
|
185
164
|
|
|
186
165
|
### Video Mode
|
|
187
166
|
With `video_mode=True`, DXcam fills the buffer at target FPS, reusing the previous frame if needed, even if no new frame is rendered.
|
|
@@ -204,8 +183,52 @@ camera.stop()
|
|
|
204
183
|
writer.release()
|
|
205
184
|
```
|
|
206
185
|
|
|
186
|
+
### Capture Backend
|
|
187
|
+
DXcam supports two capture backends:
|
|
188
|
+
- `dxgi` (default): Desktop Duplication API path with broad compatibility.
|
|
189
|
+
- `winrt`: Windows Graphics Capture path.
|
|
190
|
+
|
|
191
|
+
Use it like this:
|
|
192
|
+
```python
|
|
193
|
+
camera = dxcam.create(backend="dxgi")
|
|
194
|
+
camera = dxcam.create(backend="winrt")
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Guideline:
|
|
198
|
+
- If you need cursor rendering, use `winrt`.
|
|
199
|
+
- Start with `dxgi` for most workloads, especially one-shot grab.
|
|
200
|
+
- Try `winrt` if it performs better on your machine or fits your app constraints.
|
|
201
|
+
|
|
202
|
+
### Processor Backend
|
|
203
|
+
DXcam capture backends (`dxgi`/`winrt`) first acquire a BGRA frame.
|
|
204
|
+
The processor backend then handles post-processing:
|
|
205
|
+
- optional rotation/cropping preparation
|
|
206
|
+
- color conversion to your `output_color`
|
|
207
|
+
|
|
208
|
+
Recommended backend choice:
|
|
209
|
+
- OpenCV installed: use `cv2` (default)
|
|
210
|
+
- No OpenCV installed: use `numpy` (Cython kernels)
|
|
211
|
+
|
|
212
|
+
Use it like this:
|
|
213
|
+
```python
|
|
214
|
+
camera = dxcam.create(processor_backend="cv2")
|
|
215
|
+
camera = dxcam.create(processor_backend="numpy")
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Official Windows wheels already include the compiled NumPy kernels.
|
|
219
|
+
|
|
220
|
+
Only for source installs:
|
|
221
|
+
```bash
|
|
222
|
+
set DXCAM_BUILD_CYTHON=1
|
|
223
|
+
pip install -e .[cython] --no-build-isolation
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
If `processor_backend="numpy"` is selected but compiled kernels are unavailable,
|
|
227
|
+
DXcam logs a warning and falls back to `cv2` behavior. In that fallback path,
|
|
228
|
+
install OpenCV for non-`BGRA` output modes.
|
|
229
|
+
|
|
207
230
|
### Safely Releasing Resources
|
|
208
|
-
`release()` stops capture, frees buffers, and releases
|
|
231
|
+
`release()` stops capture, frees buffers, and releases capture resources.
|
|
209
232
|
After `release()`, the same instance cannot be reused.
|
|
210
233
|
|
|
211
234
|
```python
|
|
@@ -230,6 +253,13 @@ The benchmark is across 5 runs, with a light-moderate usage on my PC (5900X + 30
|
|
|
230
253
|
| 60fps | 61.71, 0.26 :checkered_flag: | N/A | 47.11, 1.33 |
|
|
231
254
|
| 30fps | 30.08, 0.02 :checkered_flag: | N/A | 21.24, 0.17 |
|
|
232
255
|
|
|
256
|
+
Processor backend comparison helper:
|
|
257
|
+
```bash
|
|
258
|
+
python benchmarks/dxcam_processor_compare.py --backend dxgi --target-fps 120 --target-frames 1000
|
|
259
|
+
python benchmarks/dxcam_capture.py --backend dxgi --processor-backend numpy
|
|
260
|
+
python benchmarks/numpy_processor_micro.py --width 3840 --height 2160 --modes RGB --variants process into --processor-backends cv2 numpy
|
|
261
|
+
```
|
|
262
|
+
|
|
233
263
|
|
|
234
264
|
## Work Referenced
|
|
235
265
|
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
"""Public DXcam API.
|
|
2
|
+
|
|
3
|
+
Quick start:
|
|
4
|
+
>>> import dxcam
|
|
5
|
+
>>> cam = dxcam.create()
|
|
6
|
+
>>> frame = cam.grab()
|
|
7
|
+
>>> cam.release()
|
|
8
|
+
|
|
9
|
+
Threaded capture:
|
|
10
|
+
>>> import dxcam
|
|
11
|
+
>>> cam = dxcam.create(backend="dxgi")
|
|
12
|
+
>>> cam.start(target_fps=60)
|
|
13
|
+
>>> frame = cam.get_latest_frame()
|
|
14
|
+
>>> cam.stop()
|
|
15
|
+
>>> cam.release()
|
|
16
|
+
"""
|
|
17
|
+
|
|
1
18
|
from __future__ import annotations
|
|
2
19
|
|
|
3
20
|
import logging
|
|
@@ -7,8 +24,10 @@ import weakref
|
|
|
7
24
|
from types import FrameType
|
|
8
25
|
from typing import Any, Callable, cast
|
|
9
26
|
|
|
27
|
+
from dxcam.core.backend import normalize_backend_name
|
|
10
28
|
from dxcam.dxcam import DXCamera, Output, Device
|
|
11
|
-
from dxcam.
|
|
29
|
+
from dxcam.processor import normalize_processor_backend_name
|
|
30
|
+
from dxcam.types import CaptureBackend, ColorMode, ProcessorBackend, Region
|
|
12
31
|
from dxcam.util.io import (
|
|
13
32
|
enum_dxgi_adapters,
|
|
14
33
|
get_output_metadata,
|
|
@@ -18,6 +37,27 @@ logger = logging.getLogger(__name__)
|
|
|
18
37
|
_sigterm_handler_installed = False
|
|
19
38
|
_previous_sigterm_handler: int | Callable[[int, FrameType | None], Any] | None = None
|
|
20
39
|
|
|
40
|
+
__all__ = [
|
|
41
|
+
"DXCamera",
|
|
42
|
+
"CaptureBackend",
|
|
43
|
+
"ColorMode",
|
|
44
|
+
"ProcessorBackend",
|
|
45
|
+
"Region",
|
|
46
|
+
"create",
|
|
47
|
+
"device_info",
|
|
48
|
+
"output_info",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
# Hide internal factory/signal plumbing from pdoc output.
|
|
52
|
+
__pdoc__: dict[str, bool] = {
|
|
53
|
+
"Singleton": False,
|
|
54
|
+
"DXFactory": False,
|
|
55
|
+
"_configure_comtypes_logging": False,
|
|
56
|
+
"_handle_sigterm": False,
|
|
57
|
+
"_install_sigterm_handler": False,
|
|
58
|
+
"__factory": False,
|
|
59
|
+
}
|
|
60
|
+
|
|
21
61
|
|
|
22
62
|
def _configure_comtypes_logging() -> None:
|
|
23
63
|
# Suppress noisy per-frame COM Release debug logs from comtypes internals.
|
|
@@ -42,7 +82,7 @@ class Singleton(type):
|
|
|
42
82
|
class DXFactory(metaclass=Singleton):
|
|
43
83
|
"""Factory that owns device/output discovery and camera singletons."""
|
|
44
84
|
|
|
45
|
-
_camera_instances: weakref.WeakValueDictionary[tuple[int, int], DXCamera] = (
|
|
85
|
+
_camera_instances: weakref.WeakValueDictionary[tuple[int, int, CaptureBackend], DXCamera] = (
|
|
46
86
|
weakref.WeakValueDictionary()
|
|
47
87
|
)
|
|
48
88
|
|
|
@@ -65,7 +105,11 @@ class DXFactory(metaclass=Singleton):
|
|
|
65
105
|
region: Region | None = None,
|
|
66
106
|
output_color: ColorMode = "RGB",
|
|
67
107
|
max_buffer_len: int = 64,
|
|
108
|
+
backend: CaptureBackend = "dxgi",
|
|
109
|
+
processor_backend: ProcessorBackend = "cv2",
|
|
68
110
|
) -> DXCamera:
|
|
111
|
+
backend = normalize_backend_name(str(backend))
|
|
112
|
+
processor_backend = normalize_processor_backend_name(str(processor_backend))
|
|
69
113
|
device = self.devices[device_idx]
|
|
70
114
|
if output_idx is None:
|
|
71
115
|
# Select Primary Output
|
|
@@ -80,23 +124,25 @@ class DXFactory(metaclass=Singleton):
|
|
|
80
124
|
if not primary_output_indices:
|
|
81
125
|
raise RuntimeError(f"No primary output found for device index {device_idx}")
|
|
82
126
|
output_idx = primary_output_indices[0]
|
|
83
|
-
instance_key = (device_idx, output_idx)
|
|
127
|
+
instance_key = (device_idx, output_idx, backend)
|
|
84
128
|
existing_camera = self._camera_instances.get(instance_key)
|
|
85
129
|
if existing_camera is not None and existing_camera.is_released:
|
|
86
130
|
logger.info(
|
|
87
|
-
"Dropping released DXCamera instance for device=%s output=%s.",
|
|
131
|
+
"Dropping released DXCamera instance for device=%s output=%s backend=%s.",
|
|
88
132
|
device_idx,
|
|
89
133
|
output_idx,
|
|
134
|
+
backend,
|
|
90
135
|
)
|
|
91
136
|
del self._camera_instances[instance_key]
|
|
92
137
|
existing_camera = None
|
|
93
138
|
if existing_camera is not None:
|
|
94
139
|
logger.warning(
|
|
95
|
-
"DXCamera instance already exists for device=%s output=%s; "
|
|
140
|
+
"DXCamera instance already exists for device=%s output=%s backend=%s; "
|
|
96
141
|
"returning existing instance. Delete the old object with `del obj` "
|
|
97
142
|
"to recreate it with new parameters.",
|
|
98
143
|
device_idx,
|
|
99
144
|
output_idx,
|
|
145
|
+
backend,
|
|
100
146
|
)
|
|
101
147
|
return existing_camera
|
|
102
148
|
|
|
@@ -108,6 +154,8 @@ class DXFactory(metaclass=Singleton):
|
|
|
108
154
|
region=region,
|
|
109
155
|
output_color=output_color,
|
|
110
156
|
max_buffer_len=max_buffer_len,
|
|
157
|
+
backend=backend,
|
|
158
|
+
processor_backend=processor_backend,
|
|
111
159
|
)
|
|
112
160
|
self._camera_instances[instance_key] = camera
|
|
113
161
|
time.sleep(0.1) # Fix for https://github.com/ra1nty/DXcam/issues/31
|
|
@@ -178,7 +226,36 @@ def create(
|
|
|
178
226
|
region: Region | None = None,
|
|
179
227
|
output_color: ColorMode = "RGB",
|
|
180
228
|
max_buffer_len: int = 64,
|
|
229
|
+
backend: CaptureBackend = "dxgi",
|
|
230
|
+
processor_backend: ProcessorBackend = "cv2",
|
|
181
231
|
) -> DXCamera:
|
|
232
|
+
"""Create or return a singleton camera for a device/output/backend tuple.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
device_idx: DXGI adapter index.
|
|
236
|
+
output_idx: Output index on the selected adapter. ``None`` chooses
|
|
237
|
+
the primary output.
|
|
238
|
+
region: Optional capture region as ``(left, top, right, bottom)``.
|
|
239
|
+
output_color: Output pixel format.
|
|
240
|
+
max_buffer_len: Ring-buffer size used in threaded capture mode.
|
|
241
|
+
backend: Capture backend, ``"dxgi"`` or ``"winrt"``.
|
|
242
|
+
processor_backend: Post-processing backend, ``"cv2"`` (default)
|
|
243
|
+
or ``"numpy"``. The ``"numpy"`` backend uses compiled Cython
|
|
244
|
+
kernels when available and falls back to cv2 behavior otherwise.
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
A :class:`dxcam.dxcam.DXCamera` instance.
|
|
248
|
+
|
|
249
|
+
Example:
|
|
250
|
+
>>> import dxcam
|
|
251
|
+
>>> cam = dxcam.create(
|
|
252
|
+
... output_color="BGR",
|
|
253
|
+
... backend="winrt",
|
|
254
|
+
... processor_backend="cv2",
|
|
255
|
+
... )
|
|
256
|
+
>>> frame = cam.grab()
|
|
257
|
+
>>> cam.release()
|
|
258
|
+
"""
|
|
182
259
|
_install_sigterm_handler()
|
|
183
260
|
return __factory.create(
|
|
184
261
|
device_idx=device_idx,
|
|
@@ -186,12 +263,26 @@ def create(
|
|
|
186
263
|
region=region,
|
|
187
264
|
output_color=output_color,
|
|
188
265
|
max_buffer_len=max_buffer_len,
|
|
266
|
+
backend=backend,
|
|
267
|
+
processor_backend=processor_backend,
|
|
189
268
|
)
|
|
190
269
|
|
|
191
270
|
|
|
192
271
|
def device_info() -> str:
|
|
272
|
+
"""Return a formatted list of detected DXGI adapters.
|
|
273
|
+
|
|
274
|
+
Example:
|
|
275
|
+
>>> import dxcam
|
|
276
|
+
>>> print(dxcam.device_info())
|
|
277
|
+
"""
|
|
193
278
|
return __factory.device_info()
|
|
194
279
|
|
|
195
280
|
|
|
196
281
|
def output_info() -> str:
|
|
282
|
+
"""Return a formatted list of detected outputs for each adapter.
|
|
283
|
+
|
|
284
|
+
Example:
|
|
285
|
+
>>> import dxcam
|
|
286
|
+
>>> print(dxcam.output_info())
|
|
287
|
+
"""
|
|
197
288
|
return __factory.output_info()
|
|
@@ -212,6 +212,16 @@ class ID3D11DeviceContext(ID3D11DeviceChild):
|
|
|
212
212
|
]
|
|
213
213
|
|
|
214
214
|
|
|
215
|
+
class ID3D11Multithread(comtypes.IUnknown):
|
|
216
|
+
_iid_ = comtypes.GUID("{9b7e4e00-342c-4106-a19f-4f2704f689f0}")
|
|
217
|
+
_methods_ = [
|
|
218
|
+
comtypes.STDMETHOD(None, "Enter"),
|
|
219
|
+
comtypes.STDMETHOD(None, "Leave"),
|
|
220
|
+
comtypes.STDMETHOD(wintypes.BOOL, "SetMultithreadProtected", [wintypes.BOOL]),
|
|
221
|
+
comtypes.STDMETHOD(wintypes.BOOL, "GetMultithreadProtected"),
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
|
|
215
225
|
class ID3D11Device(comtypes.IUnknown):
|
|
216
226
|
_iid_ = comtypes.GUID("{db6f6ddb-ac77-4e88-8253-819df9bbf140}")
|
|
217
227
|
_methods_ = [
|
|
@@ -101,6 +101,17 @@ class IDXGISurface(IDXGIDeviceSubObject):
|
|
|
101
101
|
]
|
|
102
102
|
|
|
103
103
|
|
|
104
|
+
class IDXGIDevice(IDXGIObject):
|
|
105
|
+
_iid_ = comtypes.GUID("{54ec77fa-1377-44e6-8c32-88fd5f44c84c}")
|
|
106
|
+
_methods_ = [
|
|
107
|
+
comtypes.STDMETHOD(comtypes.HRESULT, "GetAdapter"),
|
|
108
|
+
comtypes.STDMETHOD(comtypes.HRESULT, "CreateSurface"),
|
|
109
|
+
comtypes.STDMETHOD(comtypes.HRESULT, "QueryResourceResidency"),
|
|
110
|
+
comtypes.STDMETHOD(comtypes.HRESULT, "SetGPUThreadPriority"),
|
|
111
|
+
comtypes.STDMETHOD(comtypes.HRESULT, "GetGPUThreadPriority"),
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
|
|
104
115
|
class IDXGIOutputDuplication(IDXGIObject):
|
|
105
116
|
_iid_ = comtypes.GUID("{191cfac3-a341-470d-b26e-a864f428319c}")
|
|
106
117
|
_methods_ = [
|