如何以最快的方式获取屏幕像素颜色(C#)

时间:2014-11-26 23:37:26

标签: c# winforms screenshot screen-scraping

我知道这些“如何获得屏幕像素颜色很少?”问题,但是当我尝试他们的解决方案时,我得不到足够好的结果。

我正在制作一个应用程序,一次又一次地检测4个不同像素的颜色并处理结果。问题是,当我尝试使用代码时,它每秒只能运行几个循环,每秒至少需要100个循环(这意味着每秒400个像素检测)。

[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetWindowDC(IntPtr window);
[DllImport("gdi32.dll", SetLastError = true)]
public static extern uint GetPixel(IntPtr dc, int x, int y);
[DllImport("user32.dll", SetLastError = true)]
public static extern int ReleaseDC(IntPtr window, IntPtr dc);

private Color GetColorAt(int x, int y)
    {
        IntPtr desk = GetDesktopWindow();
        IntPtr dc = GetWindowDC(desk);
        int a = (int)GetPixel(dc, x, y);
        ReleaseDC(desk, dc);
        return Color.FromArgb(255, (a >> 0) & 0xff, (a >> 8) & 0xff, (a >> 16) & 0xff);
    }

private void record()
    {
        sw.Start();
        while(isRunning)
        {
            Color cK1 = GetColorAt(1857, 488);
            Color cK2 = GetColorAt(1857, 556);
            Color cM1 = GetColorAt(1857, 624);
            Color cM2 = GetColorAt(1857, 692);

            if (cK1.R + cK1.G + cK1.B > 750 && !k1pressed)
            {
                k1pressed = true;
                addEvent("DOWN", "K1");
            }
            else if (cK1.R + cK1.G + cK1.B < 30 && k1pressed)
            {
                k1pressed = false;
                addEvent("UP", "K1");
            }

            if (cK2.R + cK2.G + cK2.B > 750 && !k2pressed)
            {
                k2pressed = true;
                addEvent("DOWN", "K2");
            }
            else if (cK2.R + cK2.G + cK2.B < 30 && k2pressed)
            {
                k2pressed = false;
                addEvent("UP", "K2");
            }

            if (cM1.R + cM1.G + cM1.B > 750 && !m1pressed)
            {
                m1pressed = true;
                addEvent("DOWN", "M1");
            }
            else if (cM1.R + cM1.G + cM1.B < 30 && m1pressed)
            {
                m1pressed = false;
                addEvent("UP", "M1");
            }

            if (cM2.R + cM2.G + cM2.B > 750 && !m2pressed)
            {
                m2pressed = true;
                addEvent("DOWN", "M2");
            }
            else if (cM2.R + cM2.G + cM2.B < 30 && m2pressed)
            {
                m2pressed = false;
                addEvent("UP", "M2");
            }
            labelStatus.Text = "Recording: " + sw.ElapsedMilliseconds.ToString();
            Application.DoEvents();
        }
    }

为了便于说明,我的应用程序捕获4个像素,每个像素代表一个(虚拟)键盘键或鼠标按钮(比方说A,B,LMB,RMB)和addEvent(str,str)只显示关键字的信息按下或释放到字符串中并在记录停止后将字符串保存到文件中。

我有什么方法可以做到每秒100次这样的事情?因为我认为只用4个像素操作应该非常快..

1 个答案:

答案 0 :(得分:2)

您的代码存在很多问题。

  1. 您不应该使用DoEvents循环。尝试async函数等待Task.Yield然后调用自身,或者在旧版本的.NET中,使用BeginInvoke本身等的函数。

  2. 不要经常更新标签,这真的很慢。

  3. 获取桌面窗口并仅创建一次DC,然后根据需要获取所有像素值。只有在您观看完屏幕后才能释放DC。

  4. 例如:

    private Color GetColorAt(IntPtr dc, int x, int y)
    {
        int a = (int)GetPixel(dc, x, y);
        return Color.FromArgb(a | 0xFF000000);
    }
    
    double lastTextBoxUpdate;
    private async void record()
    {
        IntPtr desk = GetDesktopWindow();
        IntPtr dc = GetWindowDC(desk);
    
        sw.Start();
        lastTextBoxUpdate = 0.0;
        while(isRunning)
        {
            Color cK1 = GetColorAt(dc, 1857, 488);
            Color cK2 = GetColorAt(dc, 1857, 556);
            Color cM1 = GetColorAt(dc, 1857, 624);
            Color cM2 = GetColorAt(dc, 1857, 692);
    
            // ...
    
            double currentElapsed = sw.ElapsedMilliseconds;
            if (currentElapsed > lastTextBoxUpdate + 500) {
                labelStatus.Text = "Recording: " + currentElapsed.ToString();
                lastTextBoxUpdate = currentElapsed;
            }
            await Task.Yield();
        }
        ReleaseDC(desk, dc);
    }
    

    此外,将边界矩形blit到局部位图,在位图上调用LockBits并从那里读取值可能会更快。这是因为从CPU访问图形内存设置传输成本高,发送附加数据的成本低。