C#中使用CopyFromScreen按XY坐标截取区域时宽度异常问题
屏幕区域截取宽度不符问题
我尝试通过两组X/Y坐标截取屏幕特定区域,但截取图像的宽度与预期不符。已确认坐标完全符合需求,但生成的图像尺寸错误,缺失了部分目标区域。
代码示例
using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace CropTest { internal class Program { static void Main(string[] args) { string testDir = @"C:\test"; string testFile = Path.Combine(testDir, $"test.png"); int startX = 5; int startY = 50; int endX = 115; int endY = 150; SaveScreenshot(Screen.PrimaryScreen, testFile, startX, startY, endX, endY); } public static void SaveScreenshot(Screen screen, string file, int startX, int startY, int endX, int endY) { Rectangle rect = ScreenResolutionHandler.GetRealRectangle(screen); int x = rect.X; int y = rect.Y; int w = rect.Width; int h = rect.Height; int newWidth = endX - startX; int newHeight = endY - startY; using (Bitmap bitmap = new Bitmap(w, h)) { using (Graphics g = Graphics.FromImage(bitmap)) { g.CopyFromScreen(startX, startY, 0, 0, new Size(newWidth, newHeight)); //g.CopyFromScreen(sourceX, sourceY, 0, 0, new Size(destX-sourceX, destY-sourceY), CopyPixelOperation.SourceCopy); } if (File.Exists(file)) File.Delete(file); bitmap.Save(file); } string origDir = Path.GetDirectoryName(file); string origFileName = Path.GetFileName(file); file = Path.Combine(origDir, "orig_" + origFileName); using (Bitmap bitmap = new Bitmap(w, h)) { using (Graphics g = Graphics.FromImage(bitmap)) { g.CopyFromScreen(startX, startY, 0, 0, new Size(w, h)); //g.CopyFromScreen(sourceX, sourceY, 0, 0, new Size(destX-sourceX, destY-sourceY), CopyPixelOperation.SourceCopy); } if (File.Exists(file)) File.Delete(file); bitmap.Save(file); } } } class ScreenResolutionHandler { const int ENUM_CURRENT_SETTINGS = -1; public static Rectangle GetRealRectangle(Screen screen) { DEVMODE dm = new DEVMODE(); dm.dmSize = (short)Marshal.SizeOf(typeof(DEVMODE)); EnumDisplaySettings(screen.DeviceName, ENUM_CURRENT_SETTINGS, ref dm); return new Rectangle(dm.dmPositionX, dm.dmPositionY, dm.dmPelsWidth, dm.dmPelsHeight); } [DllImport("user32.dll")] public static extern bool EnumDisplaySettings(string lpszDeviceName, int iModeNum, ref DEVMODE lpDevMode); [StructLayout(LayoutKind.Sequential)] public struct DEVMODE { private const int CCHDEVICENAME = 0x20; private const int CCHFORMNAME = 0x20; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] public string dmDeviceName; public short dmSpecVersion; public short dmDriverVersion; public short dmSize; public short dmDriverExtra; public int dmFields; public int dmPositionX; public int dmPositionY; public ScreenOrientation dmDisplayOrientation; public int dmDisplayFixedOutput; public short dmColor; public short dmDuplex; public short dmYResolution; public short dmTTOption; public short dmCollate; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] public string dmFormName; public short dmLogPixels; public int dmBitsPerPel; public int dmPelsWidth; public int dmPelsHeight; public int dmDisplayFlags; public int dmDisplayFrequency; public int dmICMMethod; public int dmICMIntent; public int dmMediaType; public int dmDitherType; public int dmReserved1; public int dmReserved2; public int dmPanningWidth; public int dmPanningHeight; } } }
截图说明
- 全屏截图:

- 实际截取结果:

- 预期截取结果:

问题原因及解决方法
核心问题1:Bitmap尺寸错误
当前代码创建的Bitmap是全屏分辨率(w, h),但实际只需要截取newWidth × newHeight的区域。保存全屏Bitmap时,只有左上角的目标区域有内容,其余部分为空白,导致视觉上呈现“缺失部分区域”的效果。
核心问题2:DPI缩放未适配
Windows高DPI缩放会导致屏幕坐标分为逻辑坐标与物理坐标。你定义的startX/startY是逻辑坐标,但CopyFromScreen使用的是物理坐标,同时ScreenResolutionHandler.GetRealRectangle获取的是物理分辨率,两者不匹配会直接导致截取位置和尺寸偏差。
修复后的代码
public static void SaveScreenshot(Screen screen, string file, int startX, int startY, int endX, int endY) { // 获取系统DPI缩放比例 using (Graphics g = Graphics.FromHwnd(IntPtr.Zero)) { float dpiScaleX = g.DpiX / 96f; float dpiScaleY = g.DpiY / 96f; // 将逻辑坐标转换为物理坐标 int physicalStartX = (int)(startX * dpiScaleX); int physicalStartY = (int)(startY * dpiScaleY); int physicalEndX = (int)(endX * dpiScaleX); int physicalEndY = (int)(endY * dpiScaleY); int newWidth = physicalEndX - physicalStartX; int newHeight = physicalEndY - physicalStartY; // 创建与目标区域尺寸匹配的Bitmap using (Bitmap bitmap = new Bitmap(newWidth, newHeight)) { using (Graphics bitmapG = Graphics.FromImage(bitmap)) { // 从物理坐标位置截取对应尺寸的区域 bitmapG.CopyFromScreen(physicalStartX, physicalStartY, 0, 0, new Size(newWidth, newHeight)); } if (File.Exists(file)) File.Delete(file); bitmap.Save(file); } } }
额外说明
- 移除了不必要的全屏截图代码,专注目标区域截取逻辑;
- 通过
Graphics.DpiX/DpiY获取系统缩放比例,将逻辑坐标转换为物理坐标,确保截取位置精准; - 创建的Bitmap尺寸与目标区域完全一致,避免多余空白区域干扰。
内容的提问来源于stack exchange,提问作者user1257758
相关产品推荐
相关产品推荐

