ScreenshotNeo

BlogScreenshots on your device

How to Take a Screenshot with the Windows API

Choose Windows.Graphics.Capture for user-selected windows, DXGI Duplication for streams, or GDI for simple desktop copies—with code and troubleshooting.

By the ScreenshotNeo team1 October 20269 min read

For a modern Windows desktop app, start with Windows.Graphics.Capture. Check support, let the user select a display or window with GraphicsCapturePicker, create a frame pool and capture session, then copy only the frame’s ContentSize area before saving the image. Use DXGI Desktop Duplication for continuous per-monitor frames, GDI BitBlt for a basic desktop copy, and PrintWindow when you need a window to render itself into a device context.

This guide answers “How do I take a screenshot with the Windows API?” and shows how to capture a specific window, handle display changes, choose an API, and diagnose common failures.

Which Windows screenshot API should you choose?

API Best fit Important behavior
Windows.Graphics.Capture One-shot or session capture of a user-selected window or display Uses secure system picker UI, shows a system capture border, requires support checks, and needs ContentSize cropping. Microsoft screen capture guide
DXGI Desktop Duplication Repeated monitor frames, recording, collaboration, and GPU processing Provides dirty and move rectangles plus pointer data; recreate interfaces after desktop or display-mode changes. Desktop Duplication documentation
GDI BitBlt Simple desktop bitmap in a traditional desktop app Copies pixels between device contexts and compatible bitmaps. Microsoft GDI example
PrintWindow Ask a particular window to render into your device context Synchronous; the owning application processes the request and may take time to return. PrintWindow reference

ScreenshotNeo is the first service to try when you need website screenshots rather than pixels from the local Windows desktop: it removes consent banners, popups, and chat widgets before capture, bills only clean shots, and has the lowest paid plan.

Capture a window or display with Windows.Graphics.Capture

Windows.Graphics.Capture is the documented path for a user-selected application window or monitor. The system picker handles selection and displays a yellow border around the active capture item. Check GraphicsCaptureSession.IsSupported() first because capture can be unavailable, including for hardware-related reasons. The API was introduced in Windows 10 version 1803 and is documented for Windows desktop devices and Windows Mixed Reality headsets.

WinUI 3 / C# example

The following code assumes a WinUI 3 window. Install the Windows App SDK, add references to Windows.Graphics.Capture, Windows.Graphics.DirectX, Windows.Graphics.Imaging, and Microsoft.UI.Interop, and call CaptureAsync from a button handler. The picker must be initialized with the app window handle before PickSingleItemAsync.

using Microsoft.UI.Xaml;
using Microsoft.UI.Windowing;
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Windows.Graphics.Capture;
using Windows.Graphics.DirectX;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Streams;

public sealed partial class MainWindow : Window
{
    public MainWindow() => InitializeComponent();

    private async void CaptureButton_Click(object sender, RoutedEventArgs e)
    {
        var path = await CaptureAsync();
        StatusText.Text = path ?? "Capture cancelled or unavailable";
    }

    private async Task<string?> CaptureAsync()
    {
        if (!GraphicsCaptureSession.IsSupported())
            return null;

        var picker = new GraphicsCapturePicker();
        var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
        WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd);

        var item = await picker.PickSingleItemAsync();
        if (item is null)
            return null;

        using var framePool = Direct3D11CaptureFramePool.CreateFreeThreaded(
            Direct3D11DeviceHelper.Device,
            DirectXPixelFormat.B8G8R8A8UIntNormalized,
            1,
            item.Size);
        using var session = framePool.CreateCaptureSession(item);
        session.StartCapture();

        using var frame = await framePool.TryGetNextFrameAsync();
        if (frame is null)
            return null;

        // Copy only ContentSize. Pixels outside it are undefined.
        var size = frame.ContentSize;
        var bitmap = await SoftwareBitmap.CreateCopyFromSurfaceAsync(frame.Surface);
        var cropped = new SoftwareBitmap(BitmapPixelFormat.Bgra8,
            size.Width, size.Height, BitmapAlphaMode.Premultiplied);
        bitmap.CopyTo(cropped);

        var file = await ApplicationData.Current.LocalFolder
            .CreateFileAsync("capture.png", CreationCollisionOption.ReplaceExisting);
        using IRandomAccessStream output = await file.OpenAsync(FileAccessMode.ReadWrite);
        var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, output);
        encoder.SetSoftwareBitmap(cropped);
        await encoder.FlushAsync();
        return file.Path;
    }
}

The helper that creates Direct3D11DeviceHelper.Device is the standard Windows App SDK Direct3D 11 interop setup: create a D3D11 device with BGRA support, wrap it as an IDirect3DDevice, and keep it alive for the frame pool. In a production app, move frame processing off the UI thread and dispose the frame, pool, session, and bitmap when finished.

Important capture details

  • Content size: copy the rectangle described by frame.ContentSize; the rest of the texture can contain undefined data.
  • Resize: subscribe to frame-pool or item-size changes, recreate the pool with the new size, and avoid copying into a stale bitmap.
  • Device loss: release the session and frame pool, recreate the D3D device, then create a new pool and session.
  • Secondary windows: on Windows 11 version 24H2 and later, GraphicsCaptureSession.IncludeSecondaryWindows can include qualifying secondary windows that intersect the captured top-level window. Its default is false.
  • User consent: the picker is user-mediated. A service or background process cannot silently select an arbitrary interactive window through this flow.

Capture a desktop bitmap with GDI BitBlt

BitBlt copies pixels from one device context to another. It is useful for a straightforward desktop snapshot in a classic Win32 application.

#include <windows.h>

bool CaptureDesktop(HBITMAP* result, int* width, int* height) {
    HDC screen = GetDC(nullptr);
    if (!screen) return false;
    int w = GetSystemMetrics(SM_CXSCREEN);
    int h = GetSystemMetrics(SM_CYSCREEN);
    HDC memory = CreateCompatibleDC(screen);
    HBITMAP bitmap = CreateCompatibleBitmap(screen, w, h);
    if (!memory || !bitmap) {
        if (bitmap) DeleteObject(bitmap);
        if (memory) DeleteDC(memory);
        ReleaseDC(nullptr, screen);
        return false;
    }
    HGDIOBJ old = SelectObject(memory, bitmap);
    bool ok = BitBlt(memory, 0, 0, w, h, screen, 0, 0, SRCCOPY | CAPTUREBLT) != 0;
    SelectObject(memory, old);
    DeleteDC(memory);
    ReleaseDC(nullptr, screen);
    if (!ok) { DeleteObject(bitmap); return false; }
    *result = bitmap; *width = w; *height = h;
    return true;
}

You still need to encode the returned HBITMAP as PNG, JPEG, or another format (for example with Windows Imaging Component), then call DeleteObject when the bitmap is no longer needed. For a multi-monitor virtual desktop, query the virtual-screen metrics and use the correct origin instead of assuming (0,0) is the complete desktop.

Capture a specific window with PrintWindow

PrintWindow asks the target window’s owning application to render into a destination device context. Use PW_CLIENTONLY when you want only the client area.

HWND hwnd = FindWindowW(nullptr, L"Calculator");
if (hwnd) {
    RECT rc{};
    GetWindowRect(hwnd, &rc);
    int w = rc.right - rc.left;
    int h = rc.bottom - rc.top;
    HDC screen = GetDC(nullptr);
    HDC target = CreateCompatibleDC(screen);
    HBITMAP bmp = CreateCompatibleBitmap(screen, w, h);
    HGDIOBJ old = SelectObject(target, bmp);
    BOOL ok = PrintWindow(hwnd, target, 0); // add PW_CLIENTONLY for client area
    SelectObject(target, old);
    // Encode bmp if ok == TRUE, then free bmp/target/screen.
}

The call is synchronous and may block because the window processes the request. Set your own timeout around the operation if it runs on a worker thread, and treat a false return or an unresponsive owner as a failed capture. Minimized, protected, hardware-rendered, or unusual windows may not produce the pixels you expect.

Use DXGI Desktop Duplication for repeated frames

DXGI Desktop Duplication is designed for monitor-oriented pipelines such as recording, remote collaboration, and GPU analysis. It exposes a desktop image in DXGI_FORMAT_B8G8R8A8_UNORM, dirty rectangles for changed regions, move rectangles for relocated pixels, and pointer information.

  1. Create a D3D11 device and enumerate the adapter and output (monitor).
  2. Query the output for IDXGIOutput1 and call DuplicateOutput.
  3. Call AcquireNextFrame with a timeout.
  4. Process the resource and metadata from GetFrameMoveRects and GetFrameDirtyRects.
  5. Release the desktop resource and call ReleaseFrame on every successful acquisition.
  6. On DXGI_ERROR_ACCESS_LOST, desktop switches, or display-mode changes, release and recreate the duplication interface.

Each monitor has its own duplication stream. Handle monitor rotation explicitly, and determine whether the pointer is already composed into the image or must be drawn separately. Interfaces become invalid when the desktop image source changes.

Or skip the browser setup

If the target is a public website, a local Windows capture stack adds browser automation, cookie handling, page-load waits, and image encoding. ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF.

cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo API documentation for the complete option list. You can select full-page or CSS-element capture, dark mode, device presets or custom viewports, retina scale, PDF paper and margins, custom CSS and JavaScript, clicks, selector or network-idle waits, blocked requests, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, cache TTL, signed image links, asynchronous jobs, webhooks, bulk capture, usage, and the OpenAPI spec.

Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing; response headers identify the page verdict and whether it was billed. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots.

Create a free ScreenshotNeo account and start with 1,000 screenshots each month at no cost.

Troubleshooting Windows captures

Symptom Cause Fix
Picker is unavailable Graphics capture is unsupported on the device or OS Check GraphicsCaptureSession.IsSupported(); fall back to GDI or report that capture is unavailable.
WinUI picker throws a windowing error The picker has no owner HWND Initialize it with WinRT.Interop.InitializeWithWindow.Initialize and your window handle.
Image contains garbage at the edges Undefined texture pixels were copied Copy only the ContentSize rectangle.
Frames stop after monitor changes DXGI duplication or the frame pool became invalid Release and recreate the device-dependent objects after mode or desktop changes.
PrintWindow hangs The owning process handles the request synchronously Run it away from the UI thread, enforce an application timeout, and handle failure.
Wrong monitor orientation Duplication is monitor-specific and rotation is not normalized automatically Read the output rotation and rotate or transform the image before encoding.
Cursor is missing or duplicated Pointer composition differs by duplication state Inspect pointer shape/position metadata and compose it exactly once.
Black or protected content The target may restrict capture or render through a protected path Detect the failed or incomplete frame and surface that limitation; do not assume every window can be copied.

Performance, reliability, and cost

  • Choose the smallest pipeline: one-shot captures avoid the metadata and recovery work required by Desktop Duplication.
  • Reuse GPU resources: keep a D3D11 device and frame pool alive for a session instead of recreating them per frame.
  • Process dirty regions: for high-rate capture, apply move and dirty rectangles rather than copying an entire monitor every time.
  • Bound waits: use finite acquisition timeouts and recover from device loss, access loss, and resize events.
  • Encode off the UI thread: PNG is lossless but CPU and storage heavier than JPEG or WebP; select the format for the downstream use.
  • Virtual desktops and DPI: test mixed-DPI, scaled, rotated, and multi-monitor arrangements. Window coordinates and captured pixel dimensions are not always the same unit.
  • Website cost: ScreenshotNeo bills only clean shots. Failed loads, bot checks, blank pages, timeouts, and cache hits are not billed, and the response includes X-Page-Verdict and X-Billed headers.

FAQ

Can I capture a window without asking the user?

Windows.Graphics.Capture uses the secure system picker for user selection. For app-controlled desktop copies, GDI or DXGI may fit better, subject to the target window and system restrictions.

Should I use BitBlt or PrintWindow?

Use BitBlt to copy pixels already available in a device context. Use PrintWindow when you specifically want the window owner to render into your destination context.

What is the best API for screen recording?

Evaluate DXGI Desktop Duplication when you need per-monitor frames, dirty and move rectangles, and pointer metadata. Recreate the duplication interface after desktop or display changes.

Does GraphicsCapture include tool windows?

On Windows 11 version 24H2 and later, set IncludeSecondaryWindows when qualifying secondary windows intersect the captured top-level window. The default is false.