DXcam: Desktop Duplication Screen Capture for Python on Windows
A Python high-performance screen capture library for Windows using Desktop Duplication API - Updated 2026
At a glance
- What is it?
- DXcam wraps the Desktop Duplication API and Windows Graphics Capture in a Python camera object that returns numpy arrays. It is aimed at low-latency capture pipelines, and its main trade-offs are a Windows-only surface, a ring buffer that drops frames by design, and a release() call that permanently retires an instance.
- Who is it for?
- Adopt DXcam if you are building a Windows-only capture loop in Python and need frames as numpy arrays at a paced target_fps, particularly for full-screen Direct3D content where the README claims stable capture. Do not adopt it if you need macOS or Linux, or if you cannot tolerate frame loss, since the ring buffer overwrites old frames when full.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Activity is slowing. The repository last received commits 6 months ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
What DXcam replaces, and for whom
Most Python screenshot paths go through GDI or a compositor copy, which is fine for occasional grabs and awkward for a loop that has to keep up with a rendering application. DXcam targets that second case. The README describes it as a high-performance screenshot and capture library for Windows based on the Desktop Duplication API, designed for low-latency, high-FPS capture pipelines including full-screen Direct3D applications. The audience is narrow and identifiable: people writing computer-vision or agent pipelines that need frames continuously, people recording gameplay or application output where a fixed frame rate matters more than perfect fidelity to every rendered frame, and anyone who wants a numpy array rather than a file on disk.
The project lists its differentiators plainly. Higher capture throughput, quoted as 240+fps on 1080p. Stable capture for full-screen exclusive Direct3D apps, which is the case that breaks naive approaches. Better FPS pacing for continuous video capture. A dual backend of DXGI and Windows Graphics Capture. The topics list on the repository points the same direction: computer-use-agent, deep-learning, pytorch, low-latency. If your workload is a single screenshot of a window every few seconds, the machinery here is more than you need, and the ring buffer and pacing thread become costs rather than features.
The grab, start and ring buffer split
DXcam has two capture modes and they do not behave the same way. The one-shot path is grab(), which returns a numpy.ndarray, or None if no new frame is available since the last capture. That None is documented as backward compatibility behaviour, and the escape hatch is grab(new_frame_only=False) to always return the latest frame. This is a small API detail with real consequences: a loop that assumes grab() never returns None will crash on a static screen, which is exactly the situation where nothing new has been presented.
The continuous path is start() plus get_latest_frame(). According to the README, start() spins up a thread that polls newly rendered frames and stores them in an in-memory ring buffer, and get_latest_frame() blocks until a frame is available. The buffer is fixed size, default 8, configurable through max_buffer_len, and new frames overwrite old ones when full. That is a deliberate design for real-time consumption: a slow consumer loses history rather than applying backpressure to the capture thread. The README states the blocking and video_mode behaviour is designed for downstream video recording and machine learning workloads. When start() is running, grab() reads from the ring buffer instead of polling DXGI directly, so the two modes share state rather than being independent.
video_mode=True changes the pacing contract. DXcam fills the buffer at target FPS, reusing the previous frame if needed, even when no new frame has been rendered. For a cv2.VideoWriter loop writing a fixed frame count at a fixed rate, that is the difference between a video with correct timing and one with dropped or duplicated frames at inconsistent intervals.
Backends: DXGI versus Windows Graphics Capture
Two capture backends sit behind the same camera object. The default, dxgi, is the Desktop Duplication path and is described as having broad compatibility. The alternative, winrt, is the Windows Graphics Capture path, selected with dxcam.create(backend="winrt"). The README's guidance is explicit about when to switch: use winrt if you need cursor rendering, start with dxgi for most workloads especially one-shot grab, and try winrt if it performs better on your machine or fits your app constraints. That last clause is an admission that the choice is empirical rather than derivable from the documentation.
Timestamps differ by backend in a way worth knowing if you are aligning frames with external events. For dxgi the timestamp comes from DXGI_OUTDUPL_FRAME_INFO.LastPresentTime. For winrt it is derived from WinRT SystemRelativeTime. These are not the same clock, so a pipeline that mixes backends, or that compares capture timestamps against another timing source, needs to account for the difference. The README does not state that the two are directly comparable, and it would be wrong to assume they are.
A separate axis is the processor backend, which handles post-processing after the capture backend has acquired a BGRA frame: optional rotation and cropping preparation, then colour conversion to the requested output_color. The README recommends OpenCV when it is installed. The two axes are independent, so backend="winrt" with processor_backend="cv2" is a valid combination, and the full-feature install pulls in both.
Installation and the output_color dependency trap
The minimal install is pip install dxcam. The full-feature install is pip install "dxcam[cv2,winrt]", which the README says includes OpenCV-based colour conversion and WinRT capture backend support. Official Windows wheels are built for CPython 3.10 to 3.14, and the binary wheels include the Cython kernels used by processor backends, so a source build is not required for those interpreter versions.
Colour mode is where dependency weight gets decided. Supported modes are "RGB", "RGBA", "BGR", "BGRA" and "GRAY", set at creation time with dxcam.create(output_color="BGRA"). The README states that BGRA does not require OpenCV and is the leanest dependency path, while the other four require conversion through either cv2 or a compiled numpy backend. If you are deploying into a constrained environment and can adapt your downstream code to BGRA channel order, you avoid the OpenCV dependency entirely. If you cannot, you are paying for a conversion step on every frame in addition to the wheel size.
A minimal working session looks like this: import dxcam, then with dxcam.create() as camera: frame = camera.grab(). The context manager releases resources automatically. Region capture takes a (left, top, right, bottom) tuple, and the README's example computes a centred 640 by 640 box from a 1920 by 1080 display, returning an array shaped (640, 640, 3) in HXWXC order. Continuous capture is camera.start(region=..., target_fps=60), then camera.get_latest_frame() in a loop, then camera.stop().
Where the design bites: release semantics and frame loss
Two behaviours will catch people out. The first is release(). The README states that release() stops capture, frees buffers and releases capture resources, and that after release() the same instance cannot be reused. Calling start() on a released camera raises RuntimeError. There is no reinitialisation path documented; you create a new camera. Code that treats release() as a pause, or that caches camera instances in a pool and releases them on idle, will fail at the point of reuse rather than at the point of release, which is the harder place to debug.
The second is the ring buffer. A fixed buffer of 8 frames by default, with new frames overwriting old ones, means the capture thread never waits for the consumer. That is the correct choice for low latency and the wrong choice for completeness. If your consumer stalls for longer than eight frame intervals, you lose frames silently. Nothing in the material suggests DXcam reports how many frames were dropped, so a pipeline that needs to know whether it saw every rendered frame cannot get that answer from the API as documented. Raising max_buffer_len to 120, as the README shows, trades memory for headroom but does not change the underlying property.
There is also a scope limit. This is Windows only, built on Windows-specific APIs. The README makes no claim about other platforms, and the mechanism it relies on does not exist elsewhere. The README also warns that target_fps greater than 120 is resource heavy, which is a caution rather than a measured figure.
The alternative, and the actual difference
The obvious comparison is mss, the widely used pure-Python screenshot library. The difference is architectural rather than a matter of degree. mss reaches the screen through platform-specific paths that ultimately copy pixels from the display into a user-space buffer on each call; it is request-driven, and the caller decides when a frame is taken. DXcam's dxgi backend sits on the Desktop Duplication API, which hands the application access to the desktop image as it is presented by the compositor, and the winrt backend uses Windows Graphics Capture. That is why DXcam can run a pacing thread at target_fps and a ring buffer, and why it can claim stable capture for full-screen exclusive Direct3D applications, a case where the compositor path is not the one the application is rendering through.
The practical consequence is that mss is portable and simple and does not need a GPU-side capture path, while DXcam is Windows-only and carries the backend and processor backend decisions described above. If your code has to run on macOS or Linux, mss is the realistic option and DXcam is not a candidate. If you are on Windows and your loop is CPU-bound at the grab call, the Desktop Duplication path is the thing worth evaluating. The README's throughput claim of 240+fps on 1080p is the project's own figure; treat it as a starting hypothesis for your hardware, not as a result.
Licence, versions and what to check before adopting
DXcam is MIT licensed. That permits commercial and closed-source use, modification and redistribution provided the copyright notice and licence text are retained, but this is a summary of the identifier and not legal advice; read the LICENSE file in the repository for the operative terms. There is no separate enterprise or dual-licence arrangement mentioned in the material.
On maintenance, the repository is not archived and the last push recorded is 2026-03-18. Three releases landed in quick succession: v0.1.0 on 2026-03-08, v0.2.0 on 2026-03-10 and v0.3.0 on 2026-03-12. A burst of releases inside a week is consistent with early-stage API churn, and the README's note about grab() returning None for backward compatibility is a small piece of evidence that the API has already shifted once. Pin a version in your requirements file rather than tracking the default branch, and read the release notes for v0.2.0 and v0.3.0 before upgrading, because the material here does not describe what changed between them.
Upgrade cost is mostly bounded by the API surface you touch: create(), grab(), start(), stop(), get_latest_frame(), release(). The parts most likely to move are the backend and processor backend options and the colour mode handling, since those are where the dependency story lives. The homepage field is empty, so the project's public face is the GitHub repository and the API docs site at ra1nty.github.io/DXcam.
Editorial conclusion
Adopt DXcam if you are building a Windows-only capture loop in Python and need frames as numpy arrays at a paced target_fps, particularly for full-screen Direct3D content where the README claims stable capture. Do not adopt it if you need macOS or Linux, or if you cannot tolerate frame loss, since the ring buffer overwrites old frames when full. Before committing, verify three things on your own machine: that dxcam.device_info() and dxcam.output_info() enumerate the GPU and outputs you expect, that your chosen output_color does not force an OpenCV dependency you wanted to avoid, and that the 240+fps figure in the README holds for your resolution and backend, because that number is the project's claim and not something this review measured.
Community notes