views:

1008

answers:

3

Okay, I am looking for a function or something that will read the color of a certain pixel on my monitor, and when that color is detected, another function will be enabled. I figure using RGB. All help appreciated. Thank You.

+1  A: 

As far as I know the easiest way to do this is to:

  1. take a screenshot
  2. look at the bitmap and get the pixel color

Edit

There is probably no way to "wait" until the pixel changes to a certain color. Your program will probably have to just loop and check it every so often until it sees the color.

For example:

while(!IsPixelColor(x, y, color))
{
    //probably best to add a sleep here so your program doesn't use too much CPU
}
DoAction();

EDIT 2

Here is some sample code you can modify. This code just changes the color of a label based on the current color in a given pixel. This code avoids the handle leak mentioned.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Runtime.InteropServices;

namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{

    [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
    public static extern int BitBlt(IntPtr hDC, int x, int y, int nWidth, int nHeight, IntPtr hSrcDC, int xSrc, int ySrc, int dwRop);


    Thread t;
    int x, y;

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        x = 20;
        y = 50;
        t = new Thread(update);
        t.Start();
    }

    private void update()
    {
        Bitmap screenCopy = new Bitmap(1, 1);
        using (Graphics gdest = Graphics.FromImage(screenCopy))
        {
            while (true)
            {
                //g.CopyFromScreen(new Point(0, 0), new Point(0, 0), new Size(256, 256));
                using (Graphics gsrc = Graphics.FromHwnd(IntPtr.Zero))
                {
                    IntPtr hSrcDC = gsrc.GetHdc();
                    IntPtr hDC = gdest.GetHdc();
                    int retval = BitBlt(hDC, 0, 0, 1, 1, hSrcDC, x, y, (int)CopyPixelOperation.SourceCopy);
                    gdest.ReleaseHdc();
                    gsrc.ReleaseHdc();
                }
                Color c = Color.FromArgb(screenCopy.GetPixel(0, 0).ToArgb());
                label1.ForeColor = c;
            }
        }
    }
}

}

tster
I would rather not do it this way, I have thought about it but it just isn't an option. The pixel has to be watched like constantly as it will find the pixel it is looking for in under a minute, constantly.
Brandon
Then don't put a sleep. This loop would probably take all of .1 seconds to loop through worst case.
tster
Note that you should not run .CopyFromScreen, it has a handle leakage, the best is to implement this code yourself using Win32 API
Lasse V. Karlsen
+8  A: 

This is the most efficient: It grabs a pixel at the location of the cursor, and doesn't rely on only having one monitor.

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Diagnostics;

namespace FormTest
{
    public partial class Form1 : Form
    {
        [DllImport("user32.dll")]
        static extern bool GetCursorPos(ref Point lpPoint);

        [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
        public static extern int BitBlt(IntPtr hDC, int x, int y, int nWidth, int nHeight, IntPtr hSrcDC, int xSrc, int ySrc, int dwRop);

        public Form1()
        {
            InitializeComponent();
        }

        private void MouseMoveTimer_Tick(object sender, EventArgs e)
        {
            Point cursor = new Point();
            GetCursorPos(ref cursor);

            var c = GetColorAt(cursor);
            this.BackColor = c;

            if (c.R == c.G && c.G < 64 && c.B > 128)
            {
                MessageBox.Show("Blue");
            }
        }

        Bitmap screenPixel = new Bitmap(1, 1, PixelFormat.Format32bppArgb);
        public Color GetColorAt(Point location)
        {
            using (Graphics gdest = Graphics.FromImage(screenPixel))
            {
                using (Graphics gsrc = Graphics.FromHwnd(IntPtr.Zero))
                {
                    IntPtr hSrcDC = gsrc.GetHdc();
                    IntPtr hDC = gdest.GetHdc();
                    int retval = BitBlt(hDC, 0, 0, 1, 1, hSrcDC, location.X, location.Y, (int)CopyPixelOperation.SourceCopy);
                    gdest.ReleaseHdc();
                    gsrc.ReleaseHdc();
                }
            }

            return screenPixel.GetPixel(0, 0);
        }
    }
}

Now, obviously, you don't have to use the cursor's current location, but this is the general idea.

EDIT:

Given the above GetColorAt function you can poll a certain pixel on the screen in a safe, performance friendly way like this:

private void PollPixel(Point location, Color color)
{
    while(true)
    {
        var c = GetColorAt(location);

        if (c.R == color.R && c.G == color.G && c.B == color.B)
        {
            DoAction();
            return;
        }

        // By calling Thread.Sleep() without a parameter, we are signaling to the
        // operating system that we only want to sleep long enough for other
        // applications.  As soon as the other apps yield their CPU time, we will
        // regain control.
        Thread.Sleep()
    }
}

You can wrap that in a Thread if you want, or execute it from a Console application. "Whatever suits your fancy," I guess.

John Gietzen
+1 for pointing out that CopyFromScreen can be used to just capture a small area of the screen.
tster
But note that CopyFormScreen will leak one handle each time you call it.
Lasse V. Karlsen
Updated to prevent the leak.
John Gietzen
Thank you John, this works wonderfully, another quick question. If I want this to continuously search for pixels, how would i achieve this?
Brandon
Well, My Timer Method Will Work, but that is only at intervals of 10ms. No matter what, you are going to rely on "Polling" the pixel's color. I will add an example of a thread with a better polling method.
John Gietzen
A: 

Please check this two different functions I have used in one of my previous projects :

1) This function takes snapshot of Desktop

private void CaptureScreenAndSave(string strSavePath)
        {

            //SetTitle("Capturing Screen...");

            Bitmap bmpScreenshot;

            Graphics gfxScreenshot;
            bmpScreenshot = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height,System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            gfxScreenshot = Graphics.FromImage(bmpScreenshot);
            gfxScreenshot.CopyFromScreen(Screen.PrimaryScreen.Bounds.X, Screen.PrimaryScreen.Bounds.Y, 0, 0, Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);
            MemoryStream msIn = new MemoryStream();
            bmpScreenshot.Save(msIn, System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders()[0], null);

            msIn.Close();

            byte[] buf = msIn.ToArray();

            MemoryStream msOut = new MemoryStream();

            msOut.Write(buf, 0, buf.Length);

            msOut.Position = 0;

            Bitmap bmpOut = new Bitmap(msOut);

            try
            {
                bmpOut.Save(strSavePath, System.Drawing.Imaging.ImageFormat.Bmp);
                //SetTitle("Capturing Screen Image Saved...");
            }

            catch (Exception exp)
            {

            }

            finally
            {
                msOut.Close();
            }
        }

2) This function takes an image in input and calculates RGB average of pixel range given.

double GetRGBAverageForPixelRange( int istartRange, int iEndRange,  Bitmap oBitmap )
     {
      double dRetnVal = 0 ;
      Color oTempColor ; 
      int i, j ;
      for( int iCounter = istartRange ; iCounter < iEndRange ; iCounter++ )
      {
       i = (iCounter % (oBitmap.Width));
       j = ( iCounter / ( oBitmap.Width ) ) ;
       if (i >= 0 && j >= 0 && i < oBitmap.Width && j < oBitmap.Height )
       {
        oTempColor = oBitmap.GetPixel(i, j);
        dRetnVal = dRetnVal + oTempColor.ToArgb();
       }

      }
      return dRetnVal ;
     }

This two functions together might solve your problem. Happy Coding :)

EDIT : Please note that GetPixel is very slow function. I will think twice befor using it.

Mahin