45

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.

Henk Holterman
  • 236,989
  • 28
  • 287
  • 464
Brandon
  • 475
  • 1
  • 6
  • 7
  • 1
    Do you want to monitor the entire desktop or a specific application for this pixel change? – Eric J. Sep 27 '09 at 16:53
  • a certain pixel. Like say the pixel at 125, 130 I need it to wait until it detects the RGB of that pixel go to a certain RGB. – Brandon Sep 27 '09 at 16:59
  • 3
    Why do you need to do this? It seems like there is some sort of underlying reason that if you give us just a bit more information we might be able to give you a better way to achieve the same thing. Are you writing a game and need to determine when two objects collide? Are you trying to figure out if a certain program has started? – Lee Sep 27 '09 at 17:08
  • It is for a game, it is two check if it is at a certain screen, and when it is, it starts a function. – Brandon Sep 27 '09 at 17:16
  • 3
    But you should know what screen the game's at as you're in control of the code... – ChrisF Sep 27 '09 at 17:46

6 Answers6

47

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
  • 45,925
  • 29
  • 140
  • 183
  • +1 for pointing out that CopyFromScreen can be used to just capture a small area of the screen. – tster Sep 27 '09 at 17:21
  • 1
    But note that CopyFormScreen will leak one handle each time you call it. – Lasse V. Karlsen Sep 27 '09 at 17:36
  • Thank you John, this works wonderfully, another quick question. If I want this to continuously search for pixels, how would i achieve this? – Brandon Sep 28 '09 at 04:31
  • I know this is 10 years late, but in case anyone is interested in the answer to @Brandon's question: I used a timer, set an interval for every 250ms (or however fast you want it to search), and then on the timer_tick do the actual read. [link](https://docs.microsoft.com/en-us/dotnet/api/system.timers.timer?view=netframework-4.7.2) – TulsaNewbie Mar 22 '19 at 15:44
16

Most answers here use the very same source of that pixel (desktop dc).
The key function is GetPixel.

[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetWindowDC(IntPtr window);
[DllImport("gdi32.dll", SetLastError = true)]
public static extern uint GetPixel(IntPtr dc, int x, int y);
[DllImport("user32.dll", SetLastError = true)]
public static extern int ReleaseDC(IntPtr window, IntPtr dc);

public static Color GetColorAt(int x, int y)
{
    IntPtr desk = GetDesktopWindow();
    IntPtr dc = GetWindowDC(desk);
    int a = (int) GetPixel(dc, x, y);
    ReleaseDC(desk, dc);
    return Color.FromArgb(255, (a >> 0) & 0xff, (a >> 8) & 0xff, (a >> 16) & 0xff);
}

I think this is the cleanest and quickest way.

Note:

If you have modified the default text size among the Display Settings on Windows to increase readability on a high resolution display, the coordinate parameters of GetPixel() need to be adjusted the same way. For example, if the cursor location is (x,y) with 150% of text size on Windows 7, you need to call GetPixel(x*1.5, y*1.5) to get the color of the pixel under the cursor.

Bitterblue
  • 9,526
  • 12
  • 71
  • 114
  • 1
    Habib, the note you added is not part of my answer. It's not my know-how, it's yours. Please make a comment, if you can. Then you can get upvotes on the comment. Besides I think adjusting x and y at GetPixel is misplaced. It's better to check for resolution adjustments and cursor position somewhere outside the GetColorAt-method. – Bitterblue Sep 15 '16 at 09:25
  • +1 for the note about the text size on the display. If there's a different text size other than 100% and it's not properly adjusted, the method will get colors from the wrong pixels. – Cosmin Cretu Oct 25 '17 at 15:23
9

This function is shorter and can achieve the same result using System.Drawing, without Pinvoke.

Bitmap bmp = new Bitmap(1, 1);
Color GetColorAt(int x, int y)
{
    Rectangle bounds = new Rectangle(x, y, 1, 1);
    using (Graphics g = Graphics.FromImage(bmp))
        g.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size);
    return bmp.GetPixel(0, 0);
}
J3soon
  • 2,371
  • 1
  • 23
  • 42
6

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.

MRG
  • 3,219
  • 1
  • 24
  • 35
4

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
  • 16,762
  • 5
  • 49
  • 70
  • 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 Sep 27 '09 at 17:03
  • Then don't put a sleep. This loop would probably take all of .1 seconds to loop through worst case. – tster Sep 27 '09 at 17:19
  • 1
    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 Sep 27 '09 at 17:31
  • 1
    `Bitmap.GetPixel()` returns a `Color` so i see no reason to call `ToArgb()` and passing it into `Color.FromArgb()` – Raphael Smit Nov 18 '17 at 19:04
0

This line uses About 10 ms.

int retval = BitBlt(hDC, 0, 0, 1, 1, hSrcDC, location.X, location.Y, (int)CopyPixelOperation.SourceCopy);