【问题标题】:Fast "for each pixel in bitmap" loop快速“位图中的每个像素”循环
【发布时间】:2015-08-25 10:00:40
【问题描述】:

我正在编写一个 Visual Basic 应用程序,它会截取桌面的屏幕截图并将其裁剪为围绕屏幕中心的 200 像素 x 200 像素的图像。应用程序的一部分将遍历每个像素并检查该像素的 RGB 是否是某种颜色(这意味着它需要不到一秒钟才能有效),不幸的是 Bitmap.Getpixel 对我没有任何好处是否通过 Bitmap.Lock 加载到内存中。

有没有更快(几乎是即时)的方法?谢谢。

【问题讨论】:

    标签: vb.net bitmap


    【解决方案1】:

    当然有。通常你要做的是:

    for each pixel
      Get device contex
      Read Pixel
      Release device contex (unless you want memory leak)
    

    为此,您需要很少的外部 Windows 库调用,例如:

            [DllImport("user32.dll")]
            static extern IntPtr GetDC(IntPtr hwnd);
    
            [DllImport("user32.dll")]
            static extern Int32 ReleaseDC(IntPtr hwnd, IntPtr hdc);
    
            [DllImport("gdi32.dll")]
            static extern uint GetPixel(IntPtr hdc, int nXPos, int nYPos);
    
            static public System.Drawing.Color getPixelColor(int x, int y) {
                IntPtr hdc = GetDC(IntPtr.Zero);
                uint pixel = GetPixel(hdc, x, y);
                ReleaseDC(IntPtr.Zero, hdc);
                Color color = Color.FromArgb((int)(pixel & 0x000000FF),
                             (int)(pixel & 0x0000FF00) >> 8,
                             (int)(pixel & 0x00FF0000) >> 16);
                return color;
            }
    

    这样会好很多

    GetDC
    for each pixel
      read pixel and store value
    ReleaseDC
    

    但是我发现获取像素方法本身很慢。因此,为了获得更好的性能,只需将整个屏幕抓取到位图中并从那里获取像素。

    这里是一些c#的示例代码,如果你想使用在线转换器,你可以在VB.net中转换它:

    var maxX=200;
    var maxY=200;
    var screensize = Screen.PrimaryScreen.Bounds;
    var xCenterSub100 = (screensize.X-maxX)/2;
    var yCenterSub100 = (screensize.Y-maxY)/2;
    Bitmap hc = new Bitmap(maxX, maxY);
    using (Graphics gf = Graphics.FromImage(hc)){
        gf.CopyFromScreen(xCenterSub100, yCenterSub100, 0, 0, new Size(maxX, maxY), CopyPixelOperation.SourceCopy);
        //...
        for (int x = 0; x < maxX; x++){
            for (int y = 0; y < maxY; y++){
                var pColor = hc.GetPixel(x, y);
                //do something with the color...
            }
        }
    }
    

    在 Vb.net 中(使用 http://converter.telerik.com/):

    Dim maxX = 200
    Dim maxY = 200
    Dim screensize = Screen.PrimaryScreen.Bounds
    Dim xCenterSub100 = (screensize.X - maxX) / 2
    Dim yCenterSub100 = (screensize.Y - maxY) / 2
    Dim hc As New Bitmap(maxX, maxY)
    Using gf As Graphics = Graphics.FromImage(hc)
        gf.CopyFromScreen(xCenterSub100, yCenterSub100, 0, 0, New Size(maxX, maxY), CopyPixelOperation.SourceCopy)
        '...
        For x As Integer = 0 To maxX - 1
            For y As Integer = 0 To maxY - 1
                Dim pColor = hc.GetPixel(x, y)
                'do something with the color...
            Next
        Next
    End Using
    

    在我的旧电脑上使用 c#,我得到了大约 30 fps,运行时间大约是 35 毫秒。有更快的方法,但他们开始滥用一些东西来达到那个速度。请注意,您不使用 getPixelColor,这里仅供参考。您改为使用屏幕抓取图像方法。

    【讨论】:

    • 前 6 行行应初始化并使用一次。
    【解决方案2】:

    如果您不想使用 p/invoke,可以使用 LockBits 方法。此代码将 PictureBox 中位图中心的 200 x 200 区域的每个组件设置为随机值。它的运行时间约为 100 毫秒(不包括 PictureBox 的刷新)。

    编辑:我意识到您正在尝试读取像素,所以我添加了一行来说明如何做到这一点。

    Private Sub DoGraphics()
        Dim x As Integer
        Dim y As Integer
    
        'PixelSize is 4 bytes for a 32bpp Argb image.
        'Change this value appropriately
        Dim PixelSize As Integer = 4
    
        Dim rnd As New Random()
    
        'This code uses a bitmap that is loaded in a picture box.  
        'Any bitmap should work.
        Dim bm As Bitmap = Me.PictureBox1.Image
    
        'lock an area of the bitmap for editing that is 200 x 200 pixels in the center.
        Dim bmd As BitmapData = bm.LockBits(New Rectangle((bm.Width - 200) / 2, (bm.Height - 200) / 2, 200, 200), System.Drawing.Imaging.ImageLockMode.ReadOnly, bm.PixelFormat)
    
        'loop through the locked area of the bitmap.
        For y = 0 To bmd.Height - 1
            For x = 0 To bmd.Width - 1
                'Get the various pixel locations  This calculation is for a 32bpp Argb bitmap
                Dim blue As Integer = (bmd.Stride * y) + (PixelSize * x)
                Dim green As Integer = blue + 1
                Dim red As Integer = green + 1
                Dim alpha As Integer = red + 1
    
                'Set each component of the pixel to a random rgb value.  
                'There are 4 bytes that make up each pixel (32bpp Argb)
                Marshal.WriteByte(bmd.Scan0, red, CByte(rnd.Next(0, 256)))
                Marshal.WriteByte(bmd.Scan0, blue, CByte(rnd.Next(0, 256)))
                Marshal.WriteByte(bmd.Scan0, green, CByte(rnd.Next(0, 256)))
                Marshal.WriteByte(bmd.Scan0, alpha, 255)
    
                'Use the ReadInt32() method to read back the entire pixel
                Dim intColor As Integer = Marshal.ReadInt32(bmd.Scan0)
                If intColor = Color.Blue.ToArgb() Then
                    'The pixel is blue
                Else
                    'The pixel is not blue
                End If
    
            Next
        Next
    
        'Important!
        bm.UnlockBits(bmd)
    
        Me.PictureBox1.Refresh()
    
    End Sub
    

    【讨论】:

      猜你喜欢
      • 2017-08-01
      • 2019-05-02
      • 2012-10-11
      • 1970-01-01
      • 2021-04-25
      • 1970-01-01
      • 2015-01-27
      • 2020-06-27
      • 1970-01-01
      相关资源
      最近更新 更多