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

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;
        }
    }
}

截图说明

  • 全屏截图:Full Screen shot
  • 实际截取结果:Code Result
  • 预期截取结果:Expected

问题原因及解决方法

核心问题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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 02:50:57