首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 屏幕截圖技術:GDI API 實現全屏/區域捕獲

     編輯:關於C語言

核心原理

調用 Windows GDI 函數:

  1. CreateDC("DISPLAY") 獲取屏幕設備上下文 (DC)
  2. CreateCompatibleDC 創建內存兼容 DC
  3. CreateCompatibleBitmap 創建兼容位圖
  4. SelectObject 將位圖選入內存 DC
  5. BitBlt 將屏幕 DC 內容位塊傳輸到內存 DC
  6. Bitmap.FromHbitmap 將 GDI 句柄轉為 .NET Bitmap 對象

完整封裝類


using System;
using System.Drawing;
using System.Runtime.InteropServices;

public static class ScreenCapture
{
    #region GDI P/Invoke
    [DllImport("gdi32.dll")]
    private static extern IntPtr CreateDC(string lpszDriver, string lpszDevice, string lpszOutput, IntPtr lpInitData);

    [DllImport("gdi32.dll")]
    private static extern IntPtr CreateCompatibleDC(IntPtr hdc);

    [DllImport("gdi32.dll")]
    private static extern int GetDeviceCaps(IntPtr hdc, int nIndex);

    [DllImport("gdi32.dll")]
    private static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);

    [DllImport("gdi32.dll")]
    private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);

    [DllImport("gdi32.dll")]
    private static extern bool BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight,
        IntPtr hdcSrc, int nXSrc, int nYSrc, uint dwRop);

    [DllImport("gdi32.dll")]
    private static extern bool DeleteDC(IntPtr hdc);

    [DllImport("gdi32.dll")]
    private static extern bool DeleteObject(IntPtr hObject);

    private const int HORZRES = 8;   // 水平分辨率
    private const int VERTRES = 10;  // 垂直分辨率
    private const uint SRCCOPY = 0x00CC0020; // BitBlt 漫游操作碼
    #endregion

    /// 
    /// 截取指定矩形區域
    /// 
    public static Bitmap CaptureRegion(Rectangle rect)
    {
        IntPtr hScreenDC = IntPtr.Zero;
        IntPtr hMemDC = IntPtr.Zero;
        IntPtr hBitmap = IntPtr.Zero;
        IntPtr hOldBitmap = IntPtr.Zero;

        try
        {
            // 1. 屏幕 DC
            hScreenDC = CreateDC("DISPLAY", null, null, IntPtr.Zero);
            if (hScreenDC == IntPtr.Zero) throw new Win32Exception("CreateDC failed");

            // 2. 獲取屏幕尺寸 (用於邊界檢查)
            int screenWidth = GetDeviceCaps(hScreenDC, HORZRES);
            int screenHeight = GetDeviceCaps(hScreenDC, VERTRES);

            // 3. 修正矩形邊界
            rect.X = Math.Max(0, rect.X);
            rect.Y = Math.Max(0, rect.Y);
            rect.Width = Math.Min(rect.Width, screenWidth - rect.X);
            rect.Height = Math.Min(rect.Height, screenHeight - rect.Y);
            if (rect.Width <= 0 || rect.Height <= 0) return null;

            // 4. 內存 DC 與位圖
            hMemDC = CreateCompatibleDC(hScreenDC);
            hBitmap = CreateCompatibleBitmap(hScreenDC, rect.Width, rect.Height);
            hOldBitmap = SelectObject(hMemDC, hBitmap);

            // 5. 核心複製
            bool ok = BitBlt(hMemDC, 0, 0, rect.Width, rect.Height,
                hScreenDC, rect.X, rect.Y, SRCCOPY);
            if (!ok) throw new Win32Exception("BitBlt failed");

            // 6. 恢復選對象 (釋放 hBitmap 供 FromHbitmap 使用)
            SelectObject(hMemDC, hOldBitmap);
            hOldBitmap = IntPtr.Zero;

            // 7. 轉為 .NET Bitmap
            return Bitmap.FromHbitmap(hBitmap);
        }
        finally
        {
            // 8. 嚴格釋放順序
            if (hOldBitmap != IntPtr.Zero) SelectObject(hMemDC, hOldBitmap);
            if (hBitmap != IntPtr.Zero) DeleteObject(hBitmap);
            if (hMemDC != IntPtr.Zero) DeleteDC(hMemDC);
            if (hScreenDC != IntPtr.Zero) DeleteDC(hScreenDC);
        }
    }

    /// 
    /// 全屏截圖
    /// 
    public static Bitmap CaptureFullScreen()
    {
        // 更簡單:Graphics.CopyFromScreen (下文介紹)
        return CaptureRegion(new Rectangle(0, 0, 
            GetSystemMetrics(0), GetSystemMetrics(1)));
    }

    [DllImport("user32.dll")]
    private static extern int GetSystemMetrics(int nIndex);
}

現代簡化方案:Graphics.CopyFromScreen (.NET 2.0+)

無需 P/Invoke,框架封裝了上述 GDI 邏輯:


public static Bitmap CaptureFullScreenSimple()
{
    Rectangle bounds = Screen.PrimaryScreen.Bounds;
    Bitmap bmp = new Bitmap(bounds.Width, bounds.Height);
    using (Graphics g = Graphics.FromImage(bmp))
    {
        g.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size);
    }
    return bmp;
}

public static Bitmap CaptureRegionSimple(Rectangle rect)
{
    Bitmap bmp = new Bitmap(rect.Width, rect.Height);
    using (Graphics g = Graphics.FromImage(bmp))
    {
        g.CopyFromScreen(rect.Location, Point.Empty, rect.Size);
    }
    return bmp;
}

多顯示器支持


public static Bitmap CaptureAllScreens()
{
    Rectangle totalBounds = Rectangle.Empty;
    foreach (Screen screen in Screen.AllScreens)
        totalBounds = Rectangle.Union(totalBounds, screen.Bounds);

    Bitmap bmp = new Bitmap(totalBounds.Width, totalBounds.Height);
    using (Graphics g = Graphics.FromImage(bmp))
    {
        foreach (Screen screen in Screen.AllScreens)
        {
            g.CopyFromScreen(screen.Bounds.Location, 
                new Point(screen.Bounds.X - totalBounds.X, screen.Bounds.Y - totalBounds.Y),
                screen.Bounds.Size);
        }
    }
    return bmp;
}

高性能/實時場景:DirectX/Duplication API

  • Desktop Duplication API (Windows 8+):桌面窗口管理器 (DWM) 提供桌面幀變更流,極低延遲、無閃爍、支持硬件加速。需 C++/SharpDX/Windows.Graphics.Capture (UWP/WinUI 3)。
  • Windows.Graphics.Capture (WinRT):現代統一捕獲 API,支持 Win32/UWP,可捕獲特定窗口、排除敏感內容,推薦新項目使用。

常見問題與優化

問題解決
截圖為黑/空白顯卡驅動/硬件加速衝突 → 嘗試禁用硬件加速或用 Duplication API
高 DPI 模糊設置進程 DPI 感知 (SetProcessDpiAwareness) 或在清單聲明 dpiAware
多屏偏移錯誤使用 Screen.AllScreens 計算虛擬屏座標,而非假設主屏在 (0,0)
頻繁截圖 GC 壓力複用 Bitmap、使用 Bitmap.LockBits 直接操作像素、或用非托管內存池
UAC 提升窗口無法捕獲需進程同樣以管理員運行,或用 Duplication API (系統級)

總結

  • 簡單需求:Graphics.CopyFromScreen 最省事、跨平台 (Core 支持)
  • 精確控制/教學/舊系統:GDI BitBlt 理解底層原理
  • 高性能/錄屏/直播:Desktop Duplication / Windows.Graphics.Capture
  • 務必在 finally 中按逆序釋放 GDI 句柄,防止 GDI 泄漏導致系統資源耗盡
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved