You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何获取桌面窗口物理坐标并截图?C#+CEF窗口OCR需求

解决方案:处理DPI缩放并实现精准窗口/控件区域截图

1. 先解决应用的DPI感知问题

核心问题根源是DPI缩放不匹配:系统1.5倍缩放(144DPI)下,未标记为高DPI感知的C#应用会收到逻辑坐标而非物理屏幕坐标,导致GetWindowRect、ClientToScreen返回值与实际像素位置不符。

在C#中,开启高DPI感知的两种方式:

  • 修改应用清单:在项目app.manifest中取消注释并修改以下内容:
    <application xmlns="urn:schemas-microsoft-com:asm.v3">
      <windowsSettings>
        <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
        <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
      </windowsSettings>
    </application>
    
  • 代码动态设置:在程序启动最开头调用Windows API:
    [DllImport("user32.dll")]
    private static extern bool SetProcessDpiAwarenessContext(int dpiFlag);
    
    const int DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4;
    
    // Main方法首行执行
    SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
    

2. 准确获取窗口的物理屏幕坐标与尺寸

开启高DPI感知后,结合显示器DPI计算缩放比例,确保拿到物理像素级的窗口边界:

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

[DllImport("user32.dll")]
private static extern IntPtr MonitorFromWindow(IntPtr hWnd, uint dwFlags);

[DllImport("shcore.dll")]
private static extern uint GetDpiForMonitor(IntPtr hmonitor, uint dpiType, out uint dpiX, out uint dpiY);

public struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}

const uint MONITOR_DEFAULTTONEAREST = 0x00000002;
const uint MDT_EFFECTIVE_DPI = 0x00000000;

public static (int left, int top, int width, int height) GetWindowPhysicalBounds(IntPtr hWnd)
{
    GetWindowRect(hWnd, out var windowRect);
    var monitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
    GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, out var dpiX, out var dpiY);
    
    var scaleX = (double)dpiX / 96;
    var scaleY = (double)dpiY / 96;

    int physicalLeft = (int)Math.Round(windowRect.Left * scaleX);
    int physicalTop = (int)Math.Round(windowRect.Top * scaleY);
    int physicalWidth = (int)Math.Round((windowRect.Right - windowRect.Left) * scaleX);
    int physicalHeight = (int)Math.Round((windowRect.Bottom - windowRect.Top) * scaleY);

    return (physicalLeft, physicalTop, physicalWidth, physicalHeight);
}

调用此方法得到的坐标可直接用于截图,不会出现桌面背景混入或尺寸裁剪问题。

3. 窗口内客户端坐标转物理屏幕坐标

针对CEF控件返回的相对客户端坐标,转换为物理屏幕坐标的实现:

[DllImport("user32.dll")]
private static extern bool ClientToScreen(IntPtr hWnd, ref POINT lpPoint);

public struct POINT
{
    public int X;
    public int Y;
}

public static (int screenX, int screenY) ClientToPhysicalScreen(IntPtr hWnd, int clientX, int clientY)
{
    var point = new POINT { X = clientX, Y = clientY };
    ClientToScreen(hWnd, ref point);
    
    var monitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
    GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, out var dpiX, out var dpiY);
    var scaleX = (double)dpiX / 96;
    var scaleY = (double)dpiY / 96;

    return ((int)Math.Round(point.X * scaleX), (int)Math.Round(point.Y * scaleY));
}

若需截取控件区域,用上述方法拿到控件左上角物理坐标后,再将控件客户端宽高乘以缩放比例得到物理尺寸,即可确定截图范围。

4. 精准截图实现(避免桌面背景)

使用BitBlt直接从窗口DC捕获图像,适配CEF这类内嵌控件:

[DllImport("user32.dll")]
private static extern IntPtr GetWindowDC(IntPtr hWnd);

[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("user32.dll")]
private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);

public static Bitmap CaptureWindowRegion(IntPtr hWnd, int x, int y, int width, int height)
{
    var bmp = new Bitmap(width, height);
    using (var g = Graphics.FromImage(bmp))
    {
        var hdcDest = g.GetHdc();
        var hdcSrc = GetWindowDC(hWnd);
        BitBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, 0x00CC0020); // SRCCOPY模式
        ReleaseDC(hWnd, hdcSrc);
        g.ReleaseHdc(hdcDest);
    }
    return bmp;
}
  • 截取整个窗口:调用GetWindowPhysicalBounds拿到宽高,传入x=0,y=0即可。
  • 截取控件区域:将控件的客户端相对坐标传入,注意若CEF返回的是自身控件内坐标,需加上CEF控件在主窗口内的客户端偏移。

5. 结合Tesseract OCR提取文本

将截取的Bitmap传入Tesseract,建议预处理图像提升识别率:

using Tesseract;

public static string ExtractTextFromBitmap(Bitmap bmp)
{
    // 灰度化预处理示例
    using var grayBmp = new Bitmap(bmp.Width, bmp.Height);
    using (var g = Graphics.FromImage(grayBmp))
    {
        var grayMatrix = new System.Drawing.Imaging.ColorMatrix(
            new float[][]
            {
                new float[] {0.299f, 0.299f, 0.299f, 0, 0},
                new float[] {0.587f, 0.587f, 0.587f, 0, 0},
                new float[] {0.114f, 0.114f, 0.114f, 0, 0},
                new float[] {0, 0, 0, 1, 0},
                new float[] {0, 0, 0, 0, 1}
            });
        var attributes = new System.Drawing.Imaging.ImageAttributes();
        attributes.SetColorMatrix(grayMatrix);
        g.DrawImage(bmp, new Rectangle(0, 0, bmp.Width, bmp.Height), 0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel, attributes);
    }

    using var engine = new TesseractEngine(@"./tessdata", "eng", EngineMode.Default);
    using var img = PixConverter.ToPix(grayBmp);
    using var page = engine.Process(img);
    return page.GetText();
}

内容的提问来源于stack exchange,提问作者James Young

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 03:43:16