从屏幕捕获图像并获取颜色

时间:2018-07-09 05:42:41

标签: c#

我正在制作一个程序,该程序可以捕获屏幕上的一小块区域,并且如果图像上有与目标颜色匹配的任何颜色,它将运行某些程序。我的程序按以下顺序运行:

  1. 从屏幕上的特定区域获取图像

  2. 保存到文件夹

  3. 使用CountPixel检测任何target_color 但是,我两次单击button5(而不是双击)之后,它通过下面一行的异常:

    b.Save(@“ C:\ Applications \ CaptureImage000.jpg”,ImageFormat.Jpeg);

例外:

  

类型的未处理异常   “ System.Runtime.InteropServices.ExternalException”发生在   System.Drawing.dll

其他信息:GDI +中发生了一般错误

我的问题是:

  1. 如何解决此异常?
  2. 我想使用另一种方法代替CountPixel()来提高性能,因为我只需要检测一种目标颜色即可引发事件。
  3. 步骤2很麻烦。我想知道是否可以跳过它并使用其他方式调用:(@"C:\Applications\CaptureImage000.jpg", ImageFormat.Jpeg),因为使用此长字符串不方便,并且在我尝试与GetPixel一起使用时出现错误,...或添加将其转化为互联网上的一些“价值示例”代码以进行改进。

    private int CountPixels(Bitmap bm, Color target_color)
    {
        // Loop through the pixels.
        int matches = 0;
        for (int y = 0; y < bm.Height; y++)
        {
            for (int x = 0; x < bm.Width; x++)
            {
                if (bm.GetPixel(x, y) == target_color) matches++;
            }
        }
        return matches;
    }
    private Bitmap CapturedImage(int x, int y)
    {
        Bitmap b = new Bitmap(XX, YY);
    
    
        Graphics g = Graphics.FromImage(b);
        g.CopyFromScreen(x, y, 0, 0, new Size(XX, YY));
    
        b.Save(@"C:\Applications\CaptureImage000.jpg", ImageFormat.Jpeg);
    
        /* Run 3 line below will lead to question 1 - through exception
        Bitmap bm = new Bitmap(@"C:\Applications\CaptureImage000.jpg");
        int black_pixels = CountPixels(b, Color.FromArgb(255, 0, 0, 0));
        textBox3.Text = black_pixels + " black pixels";
        */
    
        return b;
    }
    private void button5_Click(object sender, EventArgs e)// Do screen cap
    {
        Bitmap bmp = null;
        bmp = CapturedImage(X0, Y0);
    }
    

1 个答案:

答案 0 :(得分:0)

[EDIT]今晚与OP合作,做了一些改进 现在可以说明机器的字节顺序,并通过使用Color.ToArgb()函数将颜色转换为整数来正确比较颜色

下面的代码将起作用,为清楚起见,我添加了注释,并提供了一些选项。我在没有IDE的情况下编写了代码,但我相信它会很好。

在以下两种情况下,只需保留位图的句柄,无论是否需要复制,都无需保存并重新打开。

异常问题和对CapturedImage函数的改进

选项A(推荐)

不要保存位图,您已经有一个句柄,图形对象刚刚修改了BMP。只需将下面的代码保留为此功能,它将正常工作而不会取消注释其他选项之一。

代码和其他选项:

    private Bitmap CapturedImage(Bitmap bm, int x, int y)
    {
        Bitmap b = new Bitmap(XX, YY);

        Graphics g = Graphics.FromImage(b);
        g.CopyFromScreen(x, y, 0, 0, new Size(XX, YY));


        //option B - If you DO need to keep a copy of the image use PNG and delete the old image
        /*
        try
        {
            if(System.IO.File.Exists(@"C:\Applications\CaptureImage.png"))
            {
                System.IO.File.Delete(@"C:\Applications\CaptureImage.png");
            }
            b.Save(@"C:\Applications\CaptureImage.png", ImageFormat.Png);
        }
        catch (System.Exception ex)
        {
            MessageBox.Show("There was a problem trying to save the image, is the file in open in another program?\r\nError:\r\n\r\n" + ex.Message);
        }
        */

        //option C - If you DO need to keep a copy of the image AND keep all copies of all images when you click the button use PNG and generate unique filename
        /*
        int id = 0;
        while(System.IO.File.Exists(@"C:\Applications\CaptureImage" + id.ToString().PadLeft('0',4) + ".png"))
        {
            //increment the id until a unique file name is found
            id++;
        }
        b.Save(@"C:\Applications\CaptureImage" + id.ToString().PadLeft('0',4) + ".png", ImageFormat.Png);
        */


        int black_pixels = CountPixels(b, Color.FromArgb(255, 0, 0, 0));
        textBox3.Text = black_pixels + " black pixels";

        return b;
    }

现在使用CountPixels函数,您有3个选项,但是实际上,您有一个非常可靠的选项,因此我省略了其他选项。

这将锁定BMP中的位,使用编组将数据复制到阵列中并非常非常快地扫描阵列中的数据,您甚至可能不需要删除计数。如果仍然要删除计数,则只需添加“ return 1;”即可。就在将matchs变量递增的下方。

速度问题和CountPixels功能的改进

    private int CountPixels(Bitmap bm, Color target_color)
    {
        int matches = 0;
        Bitmap bmp = (Bitmap)bm.Clone();
        BitmapData bmpDat = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, bmp.PixelFormat);
        int size = bmpDat.Stride * bmpDat.Height;
        byte[] subPx = new byte[size];
        System.Runtime.InteropServices.Marshal.Copy(bmpDat.Scan0, subPx, 0, size);

        //change the 4 (ARGB) to a 3 (RGB) if you don't have an alpha channel, this is for 32bpp images

        //ternary operator to check endianess of machine and organise pixel colors as A,R,G,B or B,G,R,A (little endian is reversed);
        Color temp = BitConverter.IsLittleEndian ? Color.FromArgb(subPx[i + 2], subPx[i + 1], subPx[i]) : Color.FromArgb(subPx[i + 1], subPx[i + 2], subPx[i + 3]);
        for (int i = 0; i < size; i += 4 ) //4 bytes per pixel A, R, G, B
        {
            if(temp.ToArgb() == target_color.ToArgb())
            {
                matches++;
            }
        }
        System.Runtime.InteropServices.Marshal.Copy(subPx, 0, bmpDat.Scan0, subPx.Length);
        bmp.UnlockBits(bmpDat);

        return matches;
    }

最后一个功能相同,但允许公差百分比

    private int CountPixels(Bitmap bm, Color target_color, float tolerancePercent)
    {
        int matches = 0;
        Bitmap bmp = (Bitmap)bm.Clone();
        BitmapData bmpDat = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, bmp.PixelFormat);
        int size = bmpDat.Stride * bmpDat.Height;
        byte[] subPx = new byte[size];
        System.Runtime.InteropServices.Marshal.Copy(bmpDat.Scan0, subPx, 0, size);

        for (int i = 0; i < size; i += 4 )
        {
            byte r = BitConverter.IsLittleEndian ? subPx[i+2] : subPx[i+3];
            byte g = BitConverter.IsLittleEndian ? subPx[i+1] : subPx[i+2];
            byte b = BitConverter.IsLittleEndian ? subPx[i] : subPx[i+1];
            float distancePercent = (float)Math.Sqrt(
                Math.Abs(target_color.R-r) + 
                Math.Abs(target_color.G-g) + 
                Math.Abs(target_color.B-b)
            ) / 7.65f;

            if(distancePercent < tolerancePercent)
            {
                matches++;
            }
        }
        System.Runtime.InteropServices.Marshal.Copy(subPx, 0, bmpDat.Scan0, subPx.Length);
        bmp.UnlockBits(bmpDat);

        return matches;
    }