Window Screenshot Capture with BitBlt in C#

Win32 API Constants

This implementation provides efficient window capture capabiilties using BitBlt operations. For a 1920×1080 window, capture operations typically compelte in 2-4 milliseconds.

Win32Constants Class

using System.ComponentModel;

public static class Win32Constants
{
    public enum ColorTableUsage : uint
    {
        RGB_COLORS = 0x00,
        PALETTE_COLORS = 0x01,
        PALETTE_INDICES = 0x02
    }

    public enum CompressionType : uint
    {
        NONE = 0,
        RLE_8BIT = 1,
        RLE_4BIT = 2,
        BIT_FIELDS = 3,
        JPEG = 4,
        PNG = 5
    }

    public enum RasterOperations : uint
    {
        SOURCE_COPY = 0x00CC0020,
        SOURCE_PAINT = 0x00EE0086,
        SOURCE_AND = 0x008800C6,
        SOURCE_INVERT = 0x00660046,
        SOURCE_ERASE = 0x00440328,
        NOT_SOURCE_COPY = 0x00330008,
        NOT_SOURCE_ERASE = 0x001100A6,
        MERGE_COPY = 0x00C000CA,
        MERGE_PAINT = 0x00BB0226,
        PATTERN_COPY = 0x00F00021,
        PATTERN_PAINT = 0x00FB0A09,
        PATTERN_INVERT = 0x005A0049,
        DESTINATION_INVERT = 0x00550009,
        BLACK_OUTPUT = 0x00000042,
        WHITE_OUTPUT = 0x00FF0062,
        CAPTURE_BIT = 0x40000000
    }

    public enum PrintWindowOptions : uint
    {
        [Description("Captures only the client area")]
        CLIENT_AREA_ONLY = 0x00000001,
        
        [Description("Captures DirectX/DirectComposition content")]
        FULL_CONTENT_RENDER = 0x00000002
    }
}

Win32Types Class

using System.Runtime.InteropServices;

public static class Win32Types
{
    [StructLayout(LayoutKind.Sequential)]
    public struct Coordinate
    {
        public int X;
        public int Y;

        public Coordinate(int x, int y)
        {
            X = x;
            Y = y;
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct Bounds
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;

        public int Width => Right - Left;
        public int Height => Bottom - Top;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 2)]
    public struct BitmapFileHeader
    {
        public ushort FileType;
        public uint FileSize;
        public ushort Reserved1;
        public ushort Reserved2;
        public uint PixelDataOffset;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct BitmapInfoHeader
    {
        public uint HeaderSize;
        public int ImageWidth;
        public int ImageHeight;
        public ushort ColorPlanes;
        public ushort BitsPerPixel;
        public uint Compression;
        public uint ImageSize;
        public int HorizontalResolution;
        public int VerticalResolution;
        public uint ColorsUsed;
        public uint ImportantColors;

        public void Initialize()
        {
            HeaderSize = (uint)Marshal.SizeOf(this);
        }
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct RgbColor
    {
        public byte Blue;
        public byte Green;
        public byte Red;
        public byte Reserved;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct BitmapInformation
    {
        public BitmapInfoHeader Header;
        public RgbColor Colors;
    }
}

Win32Functions Class

using System;
using System.Runtime.InteropServices;

public static class Win32Functions
{
    [DllImport("User32.dll", SetLastError = true)]
    public static extern IntPtr FindWindow(string className, string windowName);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool GetWindowRect(IntPtr windowHandle, out Win32Types.Bounds bounds);

    [DllImport("user32.dll")]
    public static extern bool GetClientRect(IntPtr windowHandle, out Win32Types.Bounds bounds);

    [DllImport("user32.dll", EntryPoint = "GetWindowDC")]
    public static extern IntPtr GetWindowDC(IntPtr windowHandle);

    [DllImport("gdi32.dll")]
    public static extern IntPtr CreateCompatibleDC(IntPtr deviceContext);

    [DllImport("gdi32.dll")]
    public static extern IntPtr CreateCompatibleBitmap(IntPtr deviceContext, int width, int height);

    [DllImport("gdi32.dll")]
    public static extern bool DeleteDC(IntPtr deviceContext);

    [DllImport("user32.dll")]
    public static extern IntPtr ReleaseDC(IntPtr windowHandle, IntPtr deviceContext);

    [DllImport("gdi32.dll")]
    public static extern IntPtr CreateDIBSection(IntPtr deviceContext, ref Win32Types.BitmapInformation bitmapInfo,
        uint colorUsage, out IntPtr pixelData, IntPtr fileMapping, uint offset);

    [DllImport("gdi32.dll")]
    public static extern IntPtr SelectObject(IntPtr deviceContext, IntPtr graphicsObject);

    [DllImport("gdi32.dll")]
    public static extern bool DeleteObject(IntPtr graphicsObject);

    [DllImport("gdi32.dll", SetLastError = true)]
    public static extern bool BitBlt(
        IntPtr destinationDC, int destX, int destY, int width, int height,
        IntPtr sourceDC, int sourceX, int sourceY, uint rasterOperation);

    [DllImport("user32.dll")]
    public static extern bool PrintWindow(IntPtr windowHandle, IntPtr deviceContext, uint flags);
}

DIB Capture Implementation

DibCaptureHelper Class

using System;

public class DibCaptureHelper
{
    public IntPtr BitmapHandle => bitmapHandle;
    public Win32Types.BitmapInformation BitmapInfo => bitmapInfo;
    public Win32Types.Bounds WindowBounds => windowBounds;
    public Win32Types.Bounds ClientBounds => clientBounds;
    public int PixelDataSize => pixelDataSize;

    private IntPtr windowHandle = IntPtr.Zero;
    private IntPtr sourceDC = IntPtr.Zero;
    private IntPtr memoryDC = IntPtr.Zero;
    private IntPtr bitmapHandle = IntPtr.Zero;
    private IntPtr previousBitmap = IntPtr.Zero;
    private IntPtr pixelDataPtr = IntPtr.Zero;

    private Win32Types.BitmapInformation bitmapInfo;
    private Win32Types.Bounds windowBounds;
    private Win32Types.Bounds clientBounds;
    private int pixelDataSize;

    public bool Initialize(string targetWindowName)
    {
        var handle = Win32Functions.FindWindow(null, targetWindowName);
        if (handle.Equals(IntPtr.Zero))
        {
            return false;
        }

        return Initialize(handle);
    }

    public bool Initialize(IntPtr targetHandle)
    {
        windowHandle = targetHandle;

        if (!Win32Functions.GetWindowRect(windowHandle, out windowBounds) ||
            !Win32Functions.GetClientRect(windowHandle, out clientBounds))
        {
            return false;
        }

        pixelDataSize = clientBounds.Width * clientBounds.Height * 3;

        bitmapInfo = new Win32Types.BitmapInformation 
        { 
            Header = new Win32Types.BitmapInfoHeader() 
        };
        bitmapInfo.Header.Initialize();
        bitmapInfo.Header.ImageWidth = clientBounds.Width;
        bitmapInfo.Header.ImageHeight = clientBounds.Height;
        bitmapInfo.Header.ColorPlanes = 1;
        bitmapInfo.Header.BitsPerPixel = 24;
        bitmapInfo.Header.ImageSize = (uint)(clientBounds.Width * clientBounds.Height);
        bitmapInfo.Header.Compression = (uint)Win32Constants.CompressionType.NONE;

        sourceDC = Win32Functions.GetWindowDC(windowHandle);
        memoryDC = Win32Functions.CreateCompatibleDC(sourceDC);
        bitmapHandle = Win32Functions.CreateDIBSection(memoryDC, ref bitmapInfo,
            (uint)Win32Constants.ColorTableUsage.RGB_COLORS,
            out pixelDataPtr, IntPtr.Zero, 0);
        previousBitmap = Win32Functions.SelectObject(memoryDC, bitmapHandle);

        return true;
    }

    public void Cleanup()
    {
        if (bitmapHandle.Equals(IntPtr.Zero))
        {
            return;
        }

        Win32Functions.SelectObject(memoryDC, previousBitmap);
        Win32Functions.DeleteObject(bitmapHandle);
        Win32Functions.DeleteDC(memoryDC);
        Win32Functions.ReleaseDC(windowHandle, sourceDC);

        windowHandle = IntPtr.Zero;
        sourceDC = IntPtr.Zero;
        memoryDC = IntPtr.Zero;
        bitmapHandle = IntPtr.Zero;
        previousBitmap = IntPtr.Zero;
        pixelDataPtr = IntPtr.Zero;
    }

    public bool RefreshWindow()
    {
        var currentHandle = windowHandle;
        Cleanup();
        return Initialize(currentHandle);
    }

    public bool ChangeWindowTarget(string newWindowName)
    {
        Cleanup();
        return Initialize(newWindowName);
    }

    public bool ChangeWindowTarget(IntPtr newHandle)
    {
        Cleanup();
        return Initialize(newHandle);
    }

    public IntPtr CaptureWindow()
    {
        if (bitmapHandle.Equals(IntPtr.Zero) || memoryDC.Equals(IntPtr.Zero) || sourceDC.Equals(IntPtr.Zero))
        {
            return IntPtr.Zero;
        }

        var success = Win32Functions.BitBlt(
            memoryDC, 0, 0, clientBounds.Width, clientBounds.Height,
            sourceDC, 0, 0,
            (uint)Win32Constants.RasterOperations.SOURCE_COPY);

        return success ? pixelDataPtr : IntPtr.Zero;
    }

    public bool CaptureWindow(out IntPtr pixelData, out int dataSize, out Win32Types.Bounds captureBounds)
    {
        pixelData = pixelDataPtr;
        dataSize = pixelDataSize;
        captureBounds = clientBounds;

        if (bitmapHandle.Equals(IntPtr.Zero) || memoryDC.Equals(IntPtr.Zero) || sourceDC.Equals(IntPtr.Zero))
        {
            return false;
        }

        var success = Win32Functions.BitBlt(
            memoryDC, 0, 0, clientBounds.Width, clientBounds.Height,
            sourceDC, 0, 0,
            (uint)Win32Constants.RasterOperations.SOURCE_COPY);

        return success;
    }
}

Tags: BitBlt Win32API C# ScreenCapture DIB

Posted on Sun, 20 Sep 2026 16:53:18 +0000 by MachineHead933