質問

I am looking for the fastest way to capture the color of a single screen pixel in c# So far I am using GDI+ methods with a System.Threading.Timer that calls the capture function in it's call back, but I'm looking for the most optimal way to achieve my goal

My current code runs like this

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

which calls a function containing this method

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

but I'm also wondering if the GetPixel method ...

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

... may actually be faster in this case of only getting the color for a single pixel

I'm also looking at trying

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

and even trying

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

but I'm not exactly sure how to even use the CreateCompatibleDC function in a C# context, or if it's actually doing anything useful at this point...

I'm really open to any suggestions as far as optimizations including methods outside of the GDI+ library as long as the solutions are compatible with C# and include much appreciated code samples

Also, I'm not so much concerned about the optimization of the timer, but if you do have optimizations in that respect please feel free to share them

役に立ちましたか?

解決

Via use of the timespan class recording the time it takes to grab just ONE SINGLE pixel from the screen between GDI+ and Managed DirectX it turns out that GDI+ is actually a lot faster.

Both methods were tested by using:

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

which would report the average amount of ticks that each operation would take over 1000 iterations

When comparing :

//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+, to :

//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();

Managed DirectX

GDI+ ran for average(s) of 20831.1953, 18611.0566, and 20761.1914 ticks for a total average of 20,067.814433333333333333333333333 ticks over 3000 iterations

while Managed DirectX ran for average(s) of 489297.969, 496458.4, and 494268.281 ticks for a total average of 493,341.55 ticks over 3000 iterations

Meaning, that the Managed DirectX setup I was using takes about 24 times longer to do what the GDI+ setup does

Now, things to note... It is entirely possible there are more efficient ways to pull screen data with DirectX. I tried looking at pulling data from the back buffer instead of the front buffer, but for this particular example, the back buffer yielding nothing of value (it was essentially just a black screen). Another thing to note is the way that I implement my device handle, I'm pretty sure it captures the whole desktop. This might be less efficient than just grabbing the front buffer data for whatever specific window I am trying to capture... The only reasons I didn't do this were because all attempts to figure this out on my own resulted in failure (a DirectX invalid call exception somewhere in device instantiation), and because nobody I talked to from any resource knew a thing about managed DirectX, let alone how to use it for my purpose.

One more thing to note, I've heard and read that it is possible to hook into the DirectX api of an already running program. This may yield much faster results and be a better solution for others, but because of the often malicious nature of such a hook and the measures that the program from which I'm trying to capture uses to prevent it, this was not applicable to my solution.

In the end, it seems that for this particular case of capturing only a single screen pixel GDI+'s BitBlt, is faster that Managed DirectX or at least my implementation of it.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top