如何在c#中快速从屏幕的特定区域获取像素?

本文关键字:区域 获取 像素 屏幕 | 更新日期: 2023-09-27 17:49:24

我想计算屏幕特定区域的平均像素颜色。我正在为我的电视制作LED背光,所以它需要非常快。至少30fps。Bitmap.GetPixel()对于它来说太慢了。我发现了一个OpenGL方法Gl.ReadPixels(),但我不知道它是如何工作的。它接收int[]作为返回的数据。但是在调用这个方法之后,我只得到一个全是0的数组。我如何使用这个方法?或者也许有其他方法来完成这个任务?

如何在c#中快速从屏幕的特定区域获取像素?

到目前为止,在c#中处理图像的最快方法是通过使用Win32 Gdi32和User32功能的pinvoke

下面是一些捕捉当前桌面的c#代码。您还可以选择传递一个"掩码"映像的文件路径,该映像可用于隔离桌面捕获的一个区域。在蒙版中,黑色的纹理(alpha为0)将从屏幕抓取中被丢弃。这是相当初级的,因为它只支持32位的图像,并要求蒙版和屏幕截图的大小相同。欢迎您以自己的方式对其进行改进,或者您可以通过直接与pinvoke挂钩到Win32函数(参见构造函数)来了解如何在逐块的基础上完成图像处理。

public class ScreenCapture
{
    private System.Drawing.Bitmap capture;
    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="maskFile">Optional mask file, use "" for no mask</param>
    public ScreenCapture( string maskFile )
    {
         // capture the screen
         capture = CaptureWindow(User32.GetDesktopWindow());
         // if there is a mask file, load it and apply it to the capture
         if (maskFile != "")
         {
             string maskfilename = maskFile + ".png";
             System.Drawing.Bitmap maskImage = System.Drawing.Image.FromFile(maskfilename) as Bitmap;
             // ensure mask is same dim as capture...
             if ((capture.Width != maskImage.Width) || (capture.Height != maskImage.Height))
                throw new System.Exception("Mask image is required to be " + capture.Width + " x " + capture.Height + " RGBA for this screen capture");
             Rectangle rectCapture = new Rectangle(0, 0, capture.Width, capture.Height);
             Rectangle rectMask = new Rectangle(0, 0, maskImage.Width, maskImage.Height);
             System.Drawing.Imaging.BitmapData dataCapture = capture.LockBits(rectCapture, System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
             System.Drawing.Imaging.BitmapData dataMask = maskImage.LockBits(rectCapture, System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
             IntPtr ptrCapture = dataCapture.Scan0;
             IntPtr ptrMask = dataMask.Scan0;
             int size = Math.Abs(dataCapture.Stride) * capture.Height;
             byte[] bitsCapture = new byte[size];
             byte[] bitsMask = new byte[size];
             System.Runtime.InteropServices.Marshal.Copy(ptrCapture, bitsCapture, 0, size);
             System.Runtime.InteropServices.Marshal.Copy(ptrMask, bitsMask, 0, size);
             // check each pixel of the image... if the mask is 0 for each channel, set the capture pixel to 0.
             for (int n = 0; n < size; n += 4)
             {
                 bool clearPixel = true;
                 for ( int c = 0; c < 4; ++c )
                 {
                     // if any pixel of the mask is not black, do not clear the capture pixel
                     if (bitsMask[n + c] != 0)
                     {
                         clearPixel = false;
                         break;
                     }      
                }
                if ( clearPixel )
                {
                    bitsCapture[n] = 0;
                    bitsCapture[n + 1] = 0;
                    bitsCapture[n + 2] = 0;
                    bitsCapture[n + 3] = 0;
                }
            }
            System.Runtime.InteropServices.Marshal.Copy(bitsCapture, 0, ptrCapture, size);
            System.Runtime.InteropServices.Marshal.Copy(bitsMask, 0, ptrMask, size);
            capture_.UnlockBits(dataCapture);
            maskImage.UnlockBits(dataMask);
            }
        }
        /// <summary>
        /// Creates an Image object containing a screen shot of a specific window
        /// </summary>
        /// <param name="handle">The handle to the window. 
        /// (In windows forms, this is obtained by the Handle property)</param>
        /// <returns>Screen grab image</returns>
        private System.Drawing.Bitmap CaptureWindow(IntPtr handle)
        {
            // get te hDC of the target window
            IntPtr hdcSrc = User32.GetWindowDC(handle);
            // get the size
            User32.RECT windowRect = new User32.RECT();
            User32.GetWindowRect(handle, ref windowRect);
            int width = windowRect.right - windowRect.left;
            int height = windowRect.bottom - windowRect.top;
            // create a device context we can copy to
            IntPtr hdcDest = GDI32.CreateCompatibleDC(hdcSrc);
            // create a bitmap we can copy it to,
            // using GetDeviceCaps to get the width/height
            IntPtr hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc, width, height);
            // select the bitmap object
            IntPtr hOld = GDI32.SelectObject(hdcDest, hBitmap);
            // bitblt over
            GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, GDI32.SRCCOPY);
            // restore selection
            GDI32.SelectObject(hdcDest, hOld);
            // clean up
            GDI32.DeleteDC(hdcDest);
            User32.ReleaseDC(handle, hdcSrc);
            // get a .NET image object for it
            System.Drawing.Bitmap img = System.Drawing.Image.FromHbitmap(hBitmap);
            // free up the Bitmap object
            GDI32.DeleteObject(hBitmap);
            return img;
        }
        /// <summary>
        /// Save this capture as a Png image.
        /// </summary>
        /// <param name="filename">File path and name not including extension</param>
        public void SaveCapture( string filename )
        {
            capture.Save(filename + ".png", System.Drawing.Imaging.ImageFormat.Png);
        }
        /// <summary>
        /// Helper class containing Gdi32 API functions
        /// </summary>
        private class GDI32
        {
            public const int SRCCOPY = 0x00CC0020; // BitBlt dwRop parameter
            [DllImport("gdi32.dll")]
            public static extern bool BitBlt(IntPtr hObject, int nXDest, int nYDest,
                int nWidth, int nHeight, IntPtr hObjectSource,
                int nXSrc, int nYSrc, int dwRop);
            [DllImport("gdi32.dll")]
            public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int nWidth,
                int nHeight);
            [DllImport("gdi32.dll")]
            public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
            [DllImport("gdi32.dll")]
            public static extern bool DeleteDC(IntPtr hDC);
            [DllImport("gdi32.dll")]
            public static extern bool DeleteObject(IntPtr hObject);
            [DllImport("gdi32.dll")]
            public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
        }
        /// <summary>
        /// Helper class containing User32 API functions
        /// </summary>
        private class User32
        {
            [StructLayout(LayoutKind.Sequential)]
            public struct RECT
            {
                public int left;
                public int top;
                public int right;
                public int bottom;
            }
            [DllImport("user32.dll")]
            public static extern IntPtr GetDesktopWindow();
            [DllImport("user32.dll")]
            public static extern IntPtr GetActiveWindow();
            [DllImport("user32.dll")]
            public static extern IntPtr GetWindowDC(IntPtr hWnd);
            [DllImport("user32.dll")]
            public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC);
            [DllImport("user32.dll")]
            public static extern IntPtr GetWindowRect(IntPtr hWnd, ref RECT rect);
        }
    }

会抛出异常,所以一定要捕获它们。使用…

ScreenCapture grab = new ScreenCapture( "screenGrabMask.png" );
grab.SaveCapture( "screenGrab.png" );

看一下Bitmap。LockBitshttps://msdn.microsoft.com/de-de/library/5ey6h79d (v = vs.110) . aspx

提供一些基本的想法

    private unsafe void demo(Bitmap bm)
    {
        System.Drawing.Imaging.BitmapData D = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
        int stride = Math.Abs(D.Stride);
        byte* pImg = (byte*)D.Scan0.ToPointer();
        for(int y = 0; y < bm.Height; y++)
        {
            byte* pRow = (byte*)(pImg + y * stride);
            for(int x = 0; x < bm.Width; x++)
            {
                uint b = *pRow++;
                uint g = *pRow++;
                uint r = *pRow++;
                pRow++; // skip alpha
                // do something with r g b
            }
        }
        bm.UnlockBits(D);
    }

允许不安全的代码(小心)来避免额外的内存拷贝