Самый быстрый способ получить цвет пикселя экрана в С#

Я ищу самый быстрый способ захватить цвет одного пикселя экрана в С#. Пока я использую методы 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);

но я не совсем уверен, как вообще использовать функцию CreateCompatibleDC в контексте С#, и действительно ли она делает что-то полезное на данный момент...

Я действительно открыт для любых предложений по оптимизации, включая методы вне библиотеки GDI+, если решения совместимы с C# и включают высоко ценимые образцы кода.

Кроме того, меня не очень беспокоит оптимизация таймера, но если у вас есть оптимизации в этом отношении, пожалуйста, поделитесь ими.


person TheeRFG    schedule 16.06.2013    source источник


Ответы (1)


Благодаря использованию класса 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,8144333333333333333333333333 тика за 3000 итераций.

в то время как управляемый DirectX выполнялся в среднем 489297,969, 496458,4 и 494268,281 тактов, что в среднем составляет 493 341,55 тактов за 3000 итераций.

Это означает, что установка Managed DirectX, которую я использовал, занимает примерно в 24 раза больше времени, чем установка GDI+.

Теперь, что нужно отметить... Вполне возможно, что существуют более эффективные способы извлечения данных экрана с помощью DirectX. Я попытался посмотреть на извлечение данных из заднего буфера вместо переднего буфера, но для этого конкретного примера задний буфер не дал ничего ценного (по сути, это был просто черный экран). Еще одна вещь, которую следует отметить, это то, как я реализую свой дескриптор устройства, я почти уверен, что он захватывает весь рабочий стол. Это может быть менее эффективно, чем простое получение данных из переднего буфера для любого конкретного окна, которое я пытаюсь захватить... исключение недопустимого вызова где-то в экземпляре устройства), и потому что никто, с кем я разговаривал из какого-либо ресурса, ничего не знал об управляемом DirectX, не говоря уже о том, как использовать его для моей цели.

Еще одно замечание: я слышал и читал, что можно подключиться к API DirectX уже запущенной программы. Это может дать гораздо более быстрые результаты и быть лучшим решением для других, но из-за часто злонамеренного характера такого хука и мер, которые программа, из которой я пытаюсь захватить, использует для предотвращения этого, это не было применимо к моему решение.

В конце концов, кажется, что для этого конкретного случая захвата только одного пикселя экрана BitBlt GDI+ быстрее, чем управляемый DirectX или, по крайней мере, моя его реализация.

person TheeRFG    schedule 22.07.2013