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.
Files changed (43) hide show
  1. dxcam-0.2.0/MANIFEST.in +1 -0
  2. {dxcam-0.1.0.dev2/dxcam.egg-info → dxcam-0.2.0}/PKG-INFO +103 -28
  3. dxcam-0.1.0.dev2/PKG-INFO → dxcam-0.2.0/README.md +88 -58
  4. {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/__init__.py +96 -5
  5. {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/_libs/d3d11.py +10 -0
  6. {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/_libs/dxgi.py +11 -0
  7. dxcam-0.2.0/dxcam/core/__init__.py +10 -0
  8. dxcam-0.2.0/dxcam/core/backend.py +73 -0
  9. dxcam-0.2.0/dxcam/core/duplicator.py +22 -0
  10. dxcam-0.1.0.dev2/dxcam/core/duplicator.py → dxcam-0.2.0/dxcam/core/dxgi_duplicator.py +7 -3
  11. dxcam-0.2.0/dxcam/core/winrt_duplicator.py +549 -0
  12. {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/dxcam.py +248 -36
  13. dxcam-0.2.0/dxcam/processor/__init__.py +9 -0
  14. dxcam-0.2.0/dxcam/processor/_numpy_kernels.pyx +725 -0
  15. dxcam-0.2.0/dxcam/processor/base.py +125 -0
  16. dxcam-0.2.0/dxcam/processor/cv2_processor.py +356 -0
  17. dxcam-0.2.0/dxcam/processor/numpy_processor.py +128 -0
  18. dxcam-0.2.0/dxcam/types.py +47 -0
  19. dxcam-0.2.0/dxcam.egg-info/PKG-INFO +313 -0
  20. {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam.egg-info/SOURCES.txt +9 -1
  21. dxcam-0.2.0/dxcam.egg-info/requires.txt +19 -0
  22. {dxcam-0.1.0.dev2 → dxcam-0.2.0}/pyproject.toml +29 -1
  23. dxcam-0.2.0/setup.py +46 -0
  24. dxcam-0.2.0/tests/test_bgra_conversion_parity.py +88 -0
  25. dxcam-0.1.0.dev2/README.md +0 -207
  26. dxcam-0.1.0.dev2/dxcam/core/__init__.py +0 -7
  27. dxcam-0.1.0.dev2/dxcam/processor/__init__.py +0 -1
  28. dxcam-0.1.0.dev2/dxcam/processor/base.py +0 -53
  29. dxcam-0.1.0.dev2/dxcam/processor/numpy_processor.py +0 -171
  30. dxcam-0.1.0.dev2/dxcam/types.py +0 -10
  31. dxcam-0.1.0.dev2/dxcam.egg-info/requires.txt +0 -5
  32. {dxcam-0.1.0.dev2 → dxcam-0.2.0}/LICENSE +0 -0
  33. {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/_libs/__init__.py +0 -0
  34. {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/_libs/user32.py +0 -0
  35. {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/core/device.py +0 -0
  36. {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/core/output.py +0 -0
  37. {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/core/stagesurf.py +0 -0
  38. {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/util/__init__.py +0 -0
  39. {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/util/io.py +0 -0
  40. {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam/util/timer.py +0 -0
  41. {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam.egg-info/dependency_links.txt +0 -0
  42. {dxcam-0.1.0.dev2 → dxcam-0.2.0}/dxcam.egg-info/top_level.txt +0 -0
  43. {dxcam-0.1.0.dev2 → dxcam-0.2.0}/setup.cfg +0 -0
@@ -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.1.0.dev2
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
- - Correct handling of scaled/stretched outputs
50
- - Better FPS pacing for continuous/video capture
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
- ### From source (uv)
76
+ Full feature: (includes OpenCV-based color conversion, WinRT capture backend support:):
65
77
  ```bash
66
- uv sync
67
- # include OpenCV
68
- uv sync --extra cv2
78
+ pip install "dxcam[cv2,winrt]"
69
79
  ```
70
80
 
71
- ### Dev environment (uv + ruff + ty)
72
- ```bash
73
- uv venv --python 3.11 .venv
74
- uv sync --dev
75
- uv run ruff check dxcam
76
- uv run ty check dxcam
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, mainly for backward compatibility. You can use `new_frame_only=False` to change this behavior.
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 polling the rendered new frames and store in an in-memory frame buffer. The blocking / video_mode behavior is designed with downstream video recording / machine learning applications in mind.
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
- ** When `start()` capture is running, calling `grab()` reads from the in-memory ring buffer instead of directly polling DXGI.
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
- - Other color modes conversion require OpenCV (`dxcam[cv2]`).
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
- This value is directly from `DXGI_OUTDUPL_FRAME_INFO.LastPresentTime`, which can be used to calculate PTS / achieve VFR in video capture.
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 DXGI resources.
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
- - Correct handling of scaled/stretched outputs
50
- - Better FPS pacing for continuous/video capture
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
- ### From source (uv)
31
+ Full feature: (includes OpenCV-based color conversion, WinRT capture backend support:):
65
32
  ```bash
66
- uv sync
67
- # include OpenCV
68
- uv sync --extra cv2
33
+ pip install "dxcam[cv2,winrt]"
69
34
  ```
70
35
 
71
- ### Dev environment (uv + ruff + ty)
72
- ```bash
73
- uv venv --python 3.11 .venv
74
- uv sync --dev
75
- uv run ruff check dxcam
76
- uv run ty check dxcam
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, mainly for backward compatibility. You can use `new_frame_only=False` to change this behavior.
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 polling the rendered new frames and store in an in-memory frame buffer. The blocking / video_mode behavior is designed with downstream video recording / machine learning applications in mind.
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
- ** When `start()` capture is running, calling `grab()` reads from the in-memory ring buffer instead of directly polling DXGI.
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
- - Other color modes conversion require OpenCV (`dxcam[cv2]`).
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
- This value is directly from `DXGI_OUTDUPL_FRAME_INFO.LastPresentTime`, which can be used to calculate PTS / achieve VFR in video capture.
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 DXGI resources.
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.types import ColorMode, Region
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_ = [