在C#中获取屏幕像素颜色的最快方法

时间:2013-06-16 04:15:02

标签: c# windows optimization screen-scraping screen-capture

我正在寻找捕获c#中单个屏幕像素颜色的最快方法 到目前为止,我正在使用GDI +方法和System.Threading.Timer调用捕获函数的回调,但我正在寻找实现目标的最佳方法

我目前的代码就像这样运行

System.Threading.Timer stTimer = new System.Threading.Timer(timerFired, null, 0, 1);

调用包含此方法的函数

[DllImport("gdi32.dll")]
private static extern int BitBlt(IntPtr srchDC, int srcX, int srcY, int srcW, int srcH, IntPtr desthDC, int destX, int destY, int op);

[DllImport("user32.dll")]
static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);

Bitmap screenPixel = new Bitmap(1, 1);
IntPtr hdcMem = CreateCompatibleDC(IntPtr.Zero);

using (Graphics gdest = Graphics.FromImage(screenPixel))
{
    using (Graphics gsrc = Graphics.FromHwnd(appWindow))
    {
        int y = 540;
        Point loc = new Point(xVal, y);

        IntPtr hSrcDC = gsrc.GetHdc();
        IntPtr hDC = gdest.GetHdc();
        int retval = BitBlt(hDC, 0, 0, 1, 1, hSrcDC, loc.X, loc.Y, (int)CopyPixelOperation.SourceCopy);
        gdest.ReleaseHdc();
        gsrc.ReleaseHdc();

    }
}
Color c = screenPixel.GetPixel(0, 0);

但我也想知道是否有GetPixel方法 ...

[DllImport("gdi32.dll")]
static extern uint GetPixel(IntPtr hdc, int nXPos, int nYPos);

... 在这种情况下实际上可能更快,只获得单个像素的颜色

我也在尝试

[DllImport("user32.dll")]
static extern IntPtr GetWindowDC(IntPtr hWnd);

IntPtr hDC = GetWindowDC(appWindow);
int retval = BitBlt(hDC, 0, 0, 1, 1, hSrcDC, loc.X, loc.Y, (int)CopyPixelOperation.SourceCopy);

甚至尝试

[DllImport("gdi32.dll")]
private static extern int BitBlt(IntPtr srchDC, int srcX, int srcY, int srcW, int srcH, IntPtr desthDC, int destX, int destY, int op);

[DllImport("gdi32.dll", SetLastError = true)]
static extern IntPtr CreateCompatibleDC(IntPtr hdc);

[DllImport("user32.dll")]
static extern IntPtr GetWindowDC(IntPtr hWnd);

IntPtr hDC = CreateCompatibleDC(GetWindowDC(appWindow));
int retval = BitBlt(hDC, 0, 0, 1, 1, hSrcDC, loc.X, loc.Y, (int)CopyPixelOperation.SourceCopy);

但是我不确定如何在C#上下文中使用CreateCompatibleDC函数,或者它实际上是在做什么有用的......

只要解决方案与C#兼容并且包含非常受欢迎的代码示例,我对包括GDI +库之外的方法在内的优化方面的任何建议都持开放态度

另外,我并不太关心计时器的优化,但如果你确实在这方面有优化,请随时分享它们

1 个答案:

答案 0 :(得分:3)

通过使用timepan类记录从GDI +和Managed DirectX之间的屏幕上抓取一个单个像素所花费的时间,结果证明GDI +实际上要快得多。

使用以下方法测试了两种方法:

TimeSpan ts = new TimeSpan();

for (int tCount = 0; tCount < 1001; tCount++)
{
    DateTime then = DateTime.Now;

    METHOD_TO_TEST()

    DateTime nNow = DateTime.Now;
    TimeSpan tt = nNow - then;
    ts += tt;
}

return (float)ts.Ticks / (float)(tCount - 1);

将报告每次操作将超过1000次迭代的平均滴答次数

比较时:

//global scope
Bitmap screenPixel = new Bitmap(1, 1);
Color c = Color.Black 

//method to test
using (Graphics gdest = Graphics.FromImage(screenPixel))
{
    using (Graphics gsrc = Graphics.FromHwnd(hWnd))
    {
        IntPtr hSrcDC = gsrc.GetHdc();
        IntPtr hDC = gdest.GetHdc();
        int retval = BitBlt(hDC, 0, 0, 1, 1, hSrcDC, xVal, 540, (int)CopyPixelOperation.SourceCopy);
        gdest.ReleaseHdc();
        gsrc.ReleaseHdc();

    }
}
c = screenPixel.GetPixel(0, 0);

GDI +,来:

//global scope
Color c = Color.Black
PresentParameters parameters = new PresentParameters();
parameters.Windowed = true;
parameters.SwapEffect = SwapEffect.Discard;
Device d = new Device(0, DeviceType.Hardware, hWnd, CreateFlags.HardwareVertexProcessing, parameters);
Surface s = d.CreateOffscreenPlainSurface(Manager.Adapters.Default.CurrentDisplayMode.Width,  Manager.Adapters.Default.CurrentDisplayMode.Height, Format.A8R8G8B8,
                                              Pool.Scratch);

//method to test
d.GetFrontBufferData(0, s);

GraphicsStream gs = s.LockRectangle(LockFlags.None);
byte[] bu = new byte[4];
gs.Position = readPos;
gs.Read(bu, 0, 4);
int r = bu[2];
int g = bu[1];
int b = bu[0];
c = return Color.FromArgb(r, g, b);

s.UnlockRectangle();
s.ReleaseGraphics();

托管DirectX

GDI +的平均值为20831.1953,18611.0566和20761.1914,共计平均值为20,067.814433333333333333333333333,超过3000次迭代

而Managed DirectX的平均值为489297.969,496458.4和494268.281,共计平均值为493,341.55,超过3000次迭代

意思是,我使用的Managed DirectX设置所需的时间大约是GDI +设置的24倍

现在,需要注意的事项...... 完全有可能使用DirectX提取屏幕数据的更有效方法。我试图从后台缓冲区而不是前台缓冲区中提取数据,但对于这个特殊的例子,后台缓冲区没有任何价值(它基本上只是一个黑屏)。另外需要注意的是我实现设备句柄的方式,我很确定它可以捕获整个桌面。这可能不如仅仅为我想要捕获的任何特定窗口抓取前缓冲区数据效率低......我没有这样做的唯一原因是因为所有尝试自己解决这个问题导致失败(DirectX在设备实例化的某处无效的调用异常),并且因为我从任何资源上与任何资源交谈的人都不知道管理DirectX的事情,更不用说如何将它用于我的目的。

还有一点需要注意,我已经听说过并且已经知道可以挂钩已经运行的程序的DirectX api。这可能会产生更快的结果并且对其他人来说是更好的解决方案,但是由于这种钩子经常具有恶意性质以及我试图捕获的程序用来阻止它的措施,这不适用于我的溶液

最后,对于这种仅捕获单个屏幕像素GDI +的BitBlt的特殊情况,似乎比Managed DirectX或至少我的实现更快。