C#/。Net代码以缩放方式捕获多个监视器

时间:2018-09-21 12:05:00

标签: c# winforms screenshot scaling multiple-monitors

我们有一个大型WinForm C#.Net 4.6程序,该程序有时需要获取屏幕截图以进行调试。我们目前使用以下代码:

private static void DoScreenCapture(string filename)
{
    // Determine the size of the "virtual screen", including all monitors.
    int screenLeft = SystemInformation.VirtualScreen.Left;
    int screenTop = SystemInformation.VirtualScreen.Top;
    int screenWidth = SystemInformation.VirtualScreen.Width;
    int screenHeight = SystemInformation.VirtualScreen.Height;

    // Create a bitmap of the appropriate size to receive the screenshot.
    using (Bitmap bmp = new Bitmap(screenWidth, screenHeight))
    {
        // Draw the screenshot into our bitmap.
        using (Graphics g = Graphics.FromImage(bmp))
        {
            g.CopyFromScreen(screenLeft, screenTop, 0, 0, bmp.Size);
        }

        // Stuff the bitmap into a file
        bmp.Save(filename, Imaging.ImageFormat.Png);
    }
}

此代码完成了我们想要的一切,除非用户缩放了显示器的大小。

我看过很多有关Stack Overflow的文章。它们中的大多数都提供了我们已经拥有的代码,但是不能解决监视器缩放问题。例如:

Take screenshot of multiple desktops of all visible applications and forms

一些堆栈溢出文章表明,使我们的应用程序了解DPI将解决此问题。是的,那会的,但那已经超出了我们今天所能解决的范围。例如:

Windows screenshot with scaling

还有一些代码可以一次捕获所有监视器,但是我们更喜欢将所有监视器捕获在同一张图像中。

有人可以给我一个C#代码片段,该片段将截取比例因子不同的多台显示器的屏幕截图吗?

例如,如果我有三个相同的1920x1080显示器,并将它们从左到右排列,最左边的显示器为175%,中间的显示器为100%,最右边的显示器为150%,那么这就是我的屏幕截图想要

Expected screenshot

但这是我当前代码生成的屏幕截图。请注意,最右边的监视器缺少最右边的一块。

Actual screenshot

2 个答案:

答案 0 :(得分:0)

我们需要一个解决方案,所以我做了一些实验。首先,我们需要一些Windows方法的C#类。该代码是被盗的,不是原始的。

class NativeUtilities
{
    [Flags()]
    public enum DisplayDeviceStateFlags : int
    {
        /// <summary>The device is part of the desktop.</summary>
        AttachedToDesktop = 0x1,
        MultiDriver = 0x2,
        /// <summary>This is the primary display.</summary>
        PrimaryDevice = 0x4,
        /// <summary>Represents a pseudo device used to mirror application drawing for remoting or other purposes.</summary>
        MirroringDriver = 0x8,
        /// <summary>The device is VGA compatible.</summary>
        VGACompatible = 0x16,
        /// <summary>The device is removable; it cannot be the primary display.</summary>
        Removable = 0x20,
        /// <summary>The device has more display modes than its output devices support.</summary>
        ModesPruned = 0x8000000,
        Remote = 0x4000000,
        Disconnect = 0x2000000
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct DisplayDevice
    {
        [MarshalAs(UnmanagedType.U4)]
        public int cb;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string DeviceName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceString;
        [MarshalAs(UnmanagedType.U4)]
        public DisplayDeviceStateFlags StateFlags;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceID;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceKey;
    }

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

    [DllImport("user32.dll")]
    public static extern bool EnumDisplaySettings(string deviceName, int modeNum, ref DEVMODE devMode);

    public const int ENUM_CURRENT_SETTINGS = -1;
    const int ENUM_REGISTRY_SETTINGS = -2;

    [DllImport("User32.dll")]
    public static extern int EnumDisplayDevices(string lpDevice, int iDevNum, ref DisplayDevice lpDisplayDevice, int dwFlags);
}

然后,我使用上面的Windows方法编写了一种调用此代码的方法,而不是我们一直使用的.Net方法:

    public static void ScreenCapture(string filename)
    {
        // Initialize the virtual screen to dummy values
        int screenLeft = int.MaxValue;
        int screenTop = int.MaxValue;
        int screenRight = int.MinValue;
        int screenBottom = int.MinValue;

        // Enumerate system display devices
        int deviceIndex = 0;
        while (true)
        {
            NativeUtilities.DisplayDevice deviceData = new NativeUtilities.DisplayDevice{cb = Marshal.SizeOf(typeof(NativeUtilities.DisplayDevice))};
            if (NativeUtilities.EnumDisplayDevices(null, deviceIndex, ref deviceData, 0) != 0)
            {
                // Get the position and size of this particular display device
                NativeUtilities.DEVMODE devMode = new NativeUtilities.DEVMODE();
                if (NativeUtilities.EnumDisplaySettings(deviceData.DeviceName, NativeUtilities.ENUM_CURRENT_SETTINGS, ref devMode))
                {
                    // Update the virtual screen dimensions
                    screenLeft = Math.Min(screenLeft, devMode.dmPositionX);
                    screenTop = Math.Min(screenTop, devMode.dmPositionY);
                    screenRight = Math.Max(screenRight, devMode.dmPositionX + devMode.dmPelsWidth);
                    screenBottom = Math.Max(screenBottom, devMode.dmPositionY + devMode.dmPelsHeight);
                }
                deviceIndex++;
            }
            else
                break;
        }

        // Create a bitmap of the appropriate size to receive the screen-shot.
        using (Bitmap bmp = new Bitmap(screenRight - screenLeft, screenBottom - screenTop))
        {
            // Draw the screen-shot into our bitmap.
            using (Graphics g = Graphics.FromImage(bmp))
                g.CopyFromScreen(screenLeft, screenTop, 0, 0, bmp.Size);

            // Stuff the bitmap into a file
            bmp.Save(filename, System.Drawing.Imaging.ImageFormat.Png);
        }
    }

这有效,并且已从大型应用程序中撤出。我希望我已经包括了所有必要的部分。

答案 1 :(得分:-1)

最简单的方法是创建一个宽分辨率的图像,该分辨率是使用屏幕数量*屏幕宽度构建的,这样您就可以包含包含所有监视器屏幕截图的宽图像,而不必担心缩放。

这种情况的问题是一些空白,因为图像的高度和宽度基于最大的屏幕,因此某些区域对于小分辨率来说是空白的,并呈现为黑色。

您可以在下图中看到此问题:

enter image description here

此问题可以通过技巧解决,将图像的透明颜色更改为黑色,因此我们通过此技巧将黑色去除,最终图像为:

enter image description here

        int screenCount = Screen.AllScreens.Length;

        int screenTop = SystemInformation.VirtualScreen.Top;
        int screenLeft = SystemInformation.VirtualScreen.Left;
        int screenWidth = Screen.AllScreens.Max(m => m.Bounds.Width);
        int screenHeight = Screen.AllScreens.Max(m => m.Bounds.Height);

        bool isVertical = (SystemInformation.VirtualScreen.Height < SystemInformation.VirtualScreen.Width);

        if (isVertical)
            screenWidth *= screenCount;
        else
            screenHeight *= screenCount;

        // Create a bitmap of the appropriate size to receive the screenshot.
        using (Bitmap bmp = new Bitmap(screenWidth, screenHeight, PixelFormat.Format32bppPArgb))
        {
            // Draw the screenshot into our bitmap.
            using (Graphics g = Graphics.FromImage(bmp))
            {
                g.CopyFromScreen(screenLeft, screenTop, 0, 0, bmp.Size);
            }

            // Make black color transparent
            bmp.MakeTransparent(Color.Black);

            bmp.Save("TestImage.png", ImageFormat.Png);
        }
  • 可以通过调整图像大小来改进代码以减小最终图像的大小。
  • 您可以使用本机API获得每个监视器的分辨率和缩放比例,并根据此尺寸构建最终图像。