如何获取桌面窗口物理坐标并截图?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
相关产品推荐
相关产品推荐

