C# 使用DirectX GetFrontBufferData捕获所有屏幕
我正在尝试在我的电脑上创建所有屏幕的屏幕截图。过去我一直使用GDI方法,但由于性能问题,我尝试使用DirectX方法 我可以拍摄一个没有问题的屏幕截图,代码如下:C# 使用DirectX GetFrontBufferData捕获所有屏幕,c#,.net,directx,C#,.net,Directx,我正在尝试在我的电脑上创建所有屏幕的屏幕截图。过去我一直使用GDI方法,但由于性能问题,我尝试使用DirectX方法 我可以拍摄一个没有问题的屏幕截图,代码如下: using Microsoft.DirectX; using Microsoft.DirectX.Direct3D; using System.Windows.Forms; using System.Drawing; class Capture : Form { private Device device;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using System.Windows.Forms;
using System.Drawing;
class Capture : Form
{
private Device device;
private Surface surface;
public Capture()
{
PresentParameters p = new PresentParameters();
p.Windowed = true;
p.SwapEffect = SwapEffect.Discard;
device = new Device(0, DeviceType.Hardware, this, CreateFlags.HardwareVertexProcessing, p);
surface = device.CreateOffscreenPlainSurface(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, Format.A8B8G8R8, Pool.Scratch);
}
public Bitmap Frame()
{
GraphicsStream gs = SurfaceLoader.SaveToStream(ImageFileFormat.Jpg, surface);
return new Bitmap(gs);
}
}
(对于此问题,忽略从内存中删除位图)
有了这些代码,我可以拍摄我的主屏幕截图。将设备
构造函数的第一个参数更改为不同的数字对应于不同的屏幕。如果我有3个屏幕,并将2
作为一个参数传递,我将获得第三个屏幕的屏幕截图
我遇到的问题是如何处理捕获所有屏幕。我得出了以下结论:
class CaptureScreen : Form
{
private int index;
private Screen screen;
private Device device;
private Surface surface;
public Rectangle ScreenBounds { get { return screen.Bounds; } }
public Device Device { get { return device; } }
public CaptureScreen(int index, Screen screen, PresentParameters p)
{
this.screen = screen; this.index = index;
device = new Device(index, DeviceType.Hardware, this, CreateFlags.HardwareVertexProcessing, p);
surface = device.CreateOffscreenPlainSurface(screen.Bounds.Width, screen.Bounds.Height, Format.A8R8G8B8, Pool.Scratch);
}
public Bitmap Frame()
{
device.GetFrontBufferData(0, surface);
GraphicsStream gs = SurfaceLoader.SaveToStream(ImageFileFormat.Jpg, surface);
return new Bitmap(gs);
}
}
class CaptureDirectX : Form
{
private CaptureScreen[] screens;
private int width = 0;
private int height = 0;
public CaptureDirectX()
{
PresentParameters p = new PresentParameters();
p.Windowed = true;
p.SwapEffect = SwapEffect.Discard;
screens = new CaptureScreen[Screen.AllScreens.Length];
for (int i = 0; i < Screen.AllScreens.Length; i++)
{
screens[i] = new CaptureScreen(i, Screen.AllScreens[i], p);
//reset previous devices
if (i > 0)
{
for(int j = 0; j < i; j++)
{
screens[j].Device.Reset(p);
}
}
width += Screen.AllScreens[i].Bounds.Width;
if (Screen.AllScreens[i].Bounds.Height > height)
{
height = Screen.AllScreens[i].Bounds.Height;
}
}
}
public Bitmap Frame()
{
Bitmap result = new Bitmap(width, height);
using (var g = Graphics.FromImage(result))
{
for (int i = 0; i < screens.Length; i++)
{
Bitmap frame = screens[i].Frame();
g.DrawImage(frame, screens[i].Bounds);
}
}
return result;
}
}
我对此进行了一些研究,但没有取得很大的成功。我真的不确定问题出在哪里。
我发现了一个解决方案,它提供了重置设备
对象的解决方案。但正如您在我上面的代码中所看到的,我一直在尝试重置所有以前创建的设备
对象,可惜没有成功
因此,我的问题是:
- 我试图通过这种方法(即GetFrontBufferData)实现的目标可能实现吗
- 我做错了什么?我错过了什么
- 在以高速率(比如每秒30帧)捕获屏幕时,您是否看到任何性能问题?(拍摄一个目标为30fps的单屏时,我的速度约为25-30fps,而GDI方法有时会下降到15fps左右)
编辑:我应该提到,我知道
IDXGI\u desktopreplication
,但遗憾的是,它不符合我的要求。据我所知,该API仅在Windows 8以后可用,但由于我的客户端,我正试图获得一个从Windows 7以后可用的解决方案。好吧,最终解决方案是完全不同的。System.Windows.Forms.Screen
类不能很好地与DirectX
类配合使用。为什么?因为索引不匹配。AllScreens
中的第一个对象不必是设备安装中的索引0
现在通常这不是问题,除非你有一个像我这样的“奇怪的”监视器设置。在桌子上我有三个屏幕,一个垂直(12001920),一个水平(19201200)和另一个水平笔记本电脑屏幕(19201080)
在我的例子中发生了什么:AllScreens
中的第一个对象是左侧的垂直监视器。我尝试为索引0、1200宽和1920高创建一个设备。索引0对应于我的主监视器,即中间的水平监视器。所以我的Instantiation基本上超出了屏幕范围。Instantiation不会抛出异常,稍后我会尝试读取前端缓冲区数据。Bam,例外,因为我正在尝试拍摄一个1200x1920屏幕截图,屏幕截图是1920x1200
可悲的是,即使在我完成这项工作后,表演也不好。所有3个监视器的单个帧大约需要300到500毫秒。即使只有一个监视器,执行时间也大约为100毫秒。对于我的用例来说还不够好。
也没有让Backbuffer工作,它只是生成黑色图像
我回到GDI方法,并通过在每次调用Frame()
时只更新位图的特定块来增强它。你想捕捉一个1920x1200的区域,它被切割成480x300个矩形。@VuVirt是的,我见过这个问题,但他的解决方案对我不起作用。我可以很好地创建多个设备,但问题是如何从它们获取前端缓冲区。我认为您应该分别为每个屏幕调用GetFrontBufferData(提供正确的坐标)。@VuVirt这不是我正在做的吗?我通过CaptureScreen
对象进行迭代,每个对象都有自己的Device
和Surface
分配。尽管根据我发布的链接,注册第二个设备实际上会破坏我之前创建的设备。你在每次迭代中都会创建一个新设备。在下一个设备创建之前,旧设备似乎不会被销毁。在screens[i]=new CaptureScreen(i,Screen.AllScreens[i],p)创建新设备之前,您可能需要在所有屏幕上执行screens[j].Device.Dispose;您可能还需要在每次Dispose之后或创建新设备之前调用GC.Collect。
device.GetFrontBufferData(0, surface);