C#/.Net 代码,用于屏幕捕获具有缩放功能的多台显示器
C#/.Net code to screen capture multiple monitors with scaling
我们有一个大型 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
一些 Stack Overflow 文章指出让我们的应用程序了解 DPI 可以解决问题。是的,它会,但这超出了我们今天可以解决的范围。例如:
也有代码可以一次捕获所有监视器,但我们更喜欢在同一图像中捕获所有监视器。
谁能给我一个 C# 代码片段,它可以截取具有不同比例因子的多个显示器的屏幕截图?
例如,如果我有三个相同的 1920x1080 显示器并将它们从左到右排列,最左边的显示器为 175%,中间的显示器为 100%,最右边的显示器为 150%,那么屏幕截图就是这样我想要的:
Expected screenshot
但这是我当前代码生成的屏幕截图。注意最右边的显示器最右边少了一块
Actual screenshot
最简单的方法是创建一个宽图像,其分辨率是使用屏幕数 * 屏幕宽度构建的,通过这种方式,您可以获得包含所有显示器屏幕截图的宽图像,而不关心缩放。
这个场景的问题是一些空白区域,因为图像的高度和宽度是基于最大屏幕的,所以一些区域是空白的小分辨率,呈现为黑色。
你可以在下图中看到这个问题:
这个问题可以通过一个技巧来解决,将图像透明颜色更改为黑色,所以我们通过这个技巧去除黑色,最终图像是:
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 获得每个显示器的分辨率和比例,并根据此尺寸构建最终图像。
我们需要一个解决方案,所以我做了一些试验。首先,我们需要一些 Windows 方法的 C# class。此代码是盗用的,非原创。
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);
}
}
这有效并且已从大型应用程序中提取。我希望我已经包括了所有必要的部分。
我们有一个大型 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
一些 Stack Overflow 文章指出让我们的应用程序了解 DPI 可以解决问题。是的,它会,但这超出了我们今天可以解决的范围。例如:
也有代码可以一次捕获所有监视器,但我们更喜欢在同一图像中捕获所有监视器。
谁能给我一个 C# 代码片段,它可以截取具有不同比例因子的多个显示器的屏幕截图?
例如,如果我有三个相同的 1920x1080 显示器并将它们从左到右排列,最左边的显示器为 175%,中间的显示器为 100%,最右边的显示器为 150%,那么屏幕截图就是这样我想要的:
Expected screenshot
但这是我当前代码生成的屏幕截图。注意最右边的显示器最右边少了一块
Actual screenshot
最简单的方法是创建一个宽图像,其分辨率是使用屏幕数 * 屏幕宽度构建的,通过这种方式,您可以获得包含所有显示器屏幕截图的宽图像,而不关心缩放。
这个场景的问题是一些空白区域,因为图像的高度和宽度是基于最大屏幕的,所以一些区域是空白的小分辨率,呈现为黑色。
你可以在下图中看到这个问题:
这个问题可以通过一个技巧来解决,将图像透明颜色更改为黑色,所以我们通过这个技巧去除黑色,最终图像是:
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 获得每个显示器的分辨率和比例,并根据此尺寸构建最终图像。
我们需要一个解决方案,所以我做了一些试验。首先,我们需要一些 Windows 方法的 C# class。此代码是盗用的,非原创。
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);
}
}
这有效并且已从大型应用程序中提取。我希望我已经包括了所有必要的部分。