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

如何以最快的方式获得屏幕像素颜色(C#),c#,winforms,screenshot,screen-scraping,C#,Winforms,Screenshot,Screen Scraping,我知道这些“如何获得屏幕像素颜色”的问题很少,但当我尝试他们的解决方案时,我没有得到足够好的结果 我正在制作一个应用程序,一次又一次地检测4个不同像素的颜色,并处理结果。问题是,当我尝试下面的代码时,它每秒只能运行几个循环,我至少需要每秒100个循环(这意味着每秒400个像素检测) [DllImport(“user32.dll”,SetLastError=true)] 公共静态外部IntPtr GetDesktopWindow(); [DllImport(“user32.dll”,SetLast

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

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

[DllImport(“user32.dll”,SetLastError=true)]
公共静态外部IntPtr GetDesktopWindow();
[DllImport(“user32.dll”,SetLastError=true)]
公共静态外部IntPtr GetWindowDC(IntPtr窗口);
[DllImport(“gdi32.dll”,SetLastError=true)]
公共静态外部单元GetPixel(IntPtr dc、intx、inty);
[DllImport(“user32.dll”,SetLastError=true)]
公共静态外部intreleasedc(IntPtr窗口,IntPtr dc);
专用颜色GetColorAt(整数x,整数y)
{
IntPtr desk=GetDesktopWindow();
IntPtr dc=GetWindowDC(桌面);
inta=(int)GetPixel(dc,x,y);
释放dc(桌面,dc);
返回颜色。从argb(255,(a>>0)和0xff,(a>>8)和0xff,(a>>16)和0xff);
}
私人无效记录()
{
sw.Start();
同时(正在运行)
{
Color cK1=GetColorAt(1857488);
Color cK2=GetColorAt(1857556);
Color cM1=GetColorAt(1857624);
Color cM2=GetColorAt(1857692);
如果(cK1.R+cK1.G+cK1.B>750&&!K1按下)
{
k1=真;
附录(“向下”、“K1”);
}
else if(cK1.R+cK1.G+cK1.B<30&&k1按下)
{
k1=假;
附录(“UP”、“K1”);
}
如果(cK2.R+cK2.G+cK2.B>750&&!按下)
{
k2=true;
附录(“向下”、“K2”);
}
否则如果(cK2.R+cK2.G+cK2.B<30&&K2按下)
{
k2=false;
附录(“UP”、“K2”);
}
如果(cM1.R+cM1.G+cM1.B>750&&!m1按下)
{
m1=真;
addEvent(“向下”、“M1”);
}
否则如果(cM1.R+cM1.G+cM1.B<30&&m1按下)
{
m1=假;
附录(“UP”、“M1”);
}
如果(cM2.R+cM2.G+cM2.B>750&&!m2按下)
{
m2pressed=true;
附录(“向下”、“M2”);
}
否则,如果(cM2.R+cM2.G+cM2.B<30&&m2按下)
{
m2pressed=false;
附录(“UP”、“M2”);
}
labelStatus.Text=“录制:”+sw.elapsedmillyses.ToString();
Application.DoEvents();
}
}
为了便于解释,我的应用程序捕获了4个像素,每个像素代表一个(虚拟)键盘键或鼠标按钮(比如A、B、LMB、RMB),addEvent(str、str)只将有关按键被按下或释放的信息放入字符串中,并在停止录制后将字符串保存到文件中


有没有办法让我每秒做100次这样的事情?因为我认为仅使用4像素的操作应该非常快。

您的代码有很多问题

  • 不应将循环与DoEvents一起使用。尝试一个等待
    任务的
    async
    函数。Yield
    然后调用自身,或者在.NET的旧版本中,调用一个在自身上使用
    BeginInvoke
    的函数,等等

  • 不要经常更新标签,那太慢了

  • 获取桌面窗口并只创建一次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到本地位图中,在位图上调用
    锁位
    ,并从中读取值,实际上可能会更快。这是因为从CPU访问图形内存设置传输的成本很高,发送额外数据的成本很低。

    以高于视频刷新率的速度进行操作是没有意义的。我知道DoEvents()在这种情况下没有正确使用,并且更改了标签。文本也经常出现,但我需要跟踪程序运行的时间。。您能否解释或链接如何制作“异步fynction”以及如何使用锁位?我找不到任何简单的方法来使用LockBits并返回System.Drawing.Color…@user3043260:LockBits返回一个带有指向数据指针的结构。如果将该指针转换为
    int*
    ,可以像数组一样对其下标,并且会得到与调用GetPixel时得到的值相同的值。@user3043260:我添加了一个如何实现这三个要点的示例。我明白了,它看起来可能工作得非常好,感谢您提供了这个示例。你是对的,我通过生成4个DC(而不是1个)不必要地减慢了函数的速度。。但是秒表的时间不是很长吗?你有什么建议吗?我怎样才能直接从显卡存储器中获取关于某个像素的信息?因为这应该比从屏幕上截取要快得多…@user3043260:你可以使用
    double
    或long,没关系。秒表应该能够测量毫秒的分数,但您需要使用不同的属性,而且它对我们的目的并不重要。当人们谈论“从屏幕上阅读”时,他们指的是GPU内存。。。您根本无法从实际显示中读取数据,只能从产生信号到显示的GPU缓冲区读取数据。但是GPU内存连接到GPU,而不是CPU,因此从CPU读取内存的开销很高。这就是为什么在CPU内存中进行复制可能是有利的。
    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);
    }