Windows缩放比例下屏幕截图区域偏移问题求助
解决Windows DPI缩放时截图偏移的问题
我之前也碰到过一模一样的问题!Windows的DPI缩放确实会给GDI类的截图操作添不少麻烦,核心问题在于逻辑像素和物理像素不匹配,加上默认WinForms应用没开启DPI感知,导致你传入CopyFromScreen的坐标是逻辑层面的,但这个方法实际需要的是屏幕物理像素坐标,自然就出现偏移了。
下面是我亲测有效的解决方案,分步骤来:
1. 给应用开启DPI感知(最关键的一步)
默认新建的WinForms应用是不感知DPI的,这会导致系统自动缩放整个应用窗口,同时所有坐标计算都用逻辑像素。我们需要修改app.manifest文件让它支持DPI感知:
找到项目里的app.manifest(没有的话就添加一个),取消注释下面这段配置:
<application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <!-- 启用每显示器DPI感知,适配多显示器不同缩放的场景 --> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware> </windowsSettings> </application>
如果你的项目比较旧,也可以用旧版的dpiAware设置:
<asmv3:application> <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings"> <dpiAware>true</dpiAware> </asmv3:windowsSettings> </asmv3:application>
改完后重新编译项目,这一步是基础,不做的话后面的坐标转换可能还是有问题。
2. 计算当前的DPI缩放因子
开启DPI感知后,我们可以准确获取当前窗口/屏幕的缩放比例:
方法1:用Graphics对象获取(WinForms常用)
using (Graphics g = this.CreateGraphics()) { // 96是默认100%缩放的DPI值 float scaleX = g.DpiX / 96f; float scaleY = g.DpiY / 96f; }
方法2:用Win32 API获取(更精准,适合多显示器)
如果你的应用需要支持多显示器不同缩放,用GetDpiForWindow API更准确:
using System.Runtime.InteropServices; [DllImport("user32.dll")] private static extern int GetDpiForWindow(IntPtr hwnd); // 在你的窗口类中调用 int currentDpi = GetDpiForWindow(this.Handle); float scaleFactor = currentDpi / 96f;
3. 修改CopyFromScreen的坐标和尺寸
现在你需要把所有传入CopyFromScreen的逻辑坐标、宽度、高度都转换成物理像素:
假设你之前的代码是这样的(用逻辑坐标):
// 比如从用户选择的区域获取的逻辑坐标和尺寸 int sourceX = (int)selectionRect.X; int sourceY = (int)selectionRect.Y; int captureWidth = (int)selectionRect.Width; int captureHeight = (int)selectionRect.Height; using (Graphics g = Graphics.FromImage(captureImage)) { g.CopyFromScreen(sourceX, sourceY, 0, 0, new Size(captureWidth, captureHeight)); }
修改后变成:
// 先获取缩放因子 float scaleX; float scaleY; using (Graphics g = this.CreateGraphics()) { scaleX = g.DpiX / 96f; scaleY = g.DpiY / 96f; } // 转换为物理像素 int physicalX = (int)(sourceX * scaleX); int physicalY = (int)(sourceY * scaleY); int physicalWidth = (int)(captureWidth * scaleX); int physicalHeight = (int)(captureHeight * scaleY); using (Graphics g = Graphics.FromImage(captureImage)) { g.CopyFromScreen(physicalX, physicalY, 0, 0, new Size(physicalWidth, physicalHeight)); }
额外注意点
- 如果是用
Cursor.Position获取鼠标坐标,开启DPI感知后它返回的就是物理像素,不需要再转换;如果没开启的话,它返回的是逻辑像素,需要乘缩放因子。 - 测试的时候一定要覆盖不同缩放比例(125%、150%、200%),还有多显示器不同缩放的场景,确保所有情况都正常。
按照这个流程改完,应该就能解决截图偏移的问题了,我之前就是这么搞定的!
内容的提问来源于stack exchange,提问作者Wagner Alice




