【问题标题】:How to get screen pixel color the fastest way (C#)如何以最快的方式获取屏幕像素颜色(C#)
【发布时间】:2023-04-05 00:22:01
【问题描述】:

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

我正在制作一个应用程序,一次又一次地检测 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 个像素进行操作应该非常快..

【问题讨论】:

  • 以高于视频刷新率的速度进行操作是没有意义的。

标签: c# winforms screenshot screen-scraping


【解决方案1】:

你的代码有很多问题。

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

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

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

例如:

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 访问图形内存设置传输的成本很高,而发送额外数据的成本很低。

【讨论】:

  • 我知道 DoEvents() 在这种情况下没有正确使用并且更改标签。Text 也很常见,但我需要跟踪我的程序运行的时间。你能解释一下或链接吗如何制作“异步功能”以及如何使用 LockBits?我找不到任何简单的方法来使用 LockBits 并返回 System.Drawing.Color...
  • @user3043260:LockBits 返回一个带有指向数据的指针的结构。如果你将该指针转换为int*,你可以像数组一样下标它,你会得到与p/调用GetPixel相同的值。
  • @user3043260:我添加了一个示例,说明如何实现三个要点。
  • 我明白了,看起来它可能工作得很好,谢谢你把例子放在那里。你说得对,我通过生成 4 个 DC 而不是 1 个,不必要地减慢了函数的速度。但是 Stopwatch.ElapsedMilliseconds 不会返回很长时间吗?你有什么建议我怎么可能直接从显卡内存中获取有关某些像素的信息?因为这应该比从屏幕上获取要快得多......
  • @user3043260:你可以使用double或者long,没关系。秒表应该能够测量毫秒的分数,但是您需要使用不同的属性,而且这对我们的目的并不重要。当人们谈论“从屏幕读取”时,他们指的是 GPU 内存……您根本无法从实际显示器中读取,只能从向显示器产生信号的 GPU 缓冲区中读取。但是 GPU 内存连接到 GPU,而不是 CPU,因此从 CPU 读取它具有很高的开销。这就是为什么在 CPU 内存中复制可能是有利的。
猜你喜欢
  • 1970-01-01
  • 2013-11-02
  • 2012-05-17
  • 1970-01-01
  • 1970-01-01
  • 2014-12-28
  • 2015-02-08
  • 1970-01-01
  • 2017-08-17
相关资源
最近更新 更多