Eduardo Maia
Eduardo Maia

Reputation: 1

How to find a pixel of a determined color in an area of the screen

I'm working on a simple personal project and i need it to be able to analyze every pixel of a defined area of the screen every second and print on the console the location of every matching pixels it finds. So i tried this:

private ScreenCapture screenCapture = new ScreenCapture();
private List<Color> targetColors = new List<Color>();
private CancellationTokenSource cancellationTokenSource;
private Bitmap screenshot;
private TemporaryForm tempForm;

public frm1()
{
    InitializeComponent();
    this.DoubleBuffered = true;
    Utils.ColorsFound += HandleColorsFound;
}

// FUNCTIONS

private void StartColorMonitoring()
{
    cancellationTokenSource?.Cancel();
    cancellationTokenSource?.Dispose();
    cancellationTokenSource = new CancellationTokenSource();

    Task.Factory.StartNew(() =>
    {
        while (!cancellationTokenSource.Token.IsCancellationRequested)
        {
            Bitmap screenshot = CaptureScreen(GlobalVariables.MonitoringArea);

            Utils.CheckColorsInBackground(screenshot, targetColors, 0, GlobalVariables.MonitoringArea);

            screenshot.Dispose();
            Thread.Sleep(1000); 
        }
    }, cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}

private void StopColorMonitoring()
{
    cancellationTokenSource?.Cancel();
}

private void HandleColorsFound(List<Point> colorLocations)
{
    foreach (Point location in colorLocations)
    {
        Console.WriteLine($"Color found in X: {location.X}, Y: {location.Y}");
    }
}

private Bitmap CaptureScreen(Rectangle monitoringArea)
{
    Bitmap screenBmp = new Bitmap(monitoringArea.Width, monitoringArea.Height);

    using (Graphics screenG = Graphics.FromImage(screenBmp))
    {
        screenG.CopyFromScreen(monitoringArea.Location, Point.Empty, monitoringArea.Size);
    }

    return screenBmp;
}


private void button1_Click(object sender, EventArgs e)
{
    if (targetColors.Count > 0) { targetColors.Clear(); }

    targetColors.Add(GlobalVariables.Color1);
    targetColors.Add(GlobalVariables.Color2);
    targetColors.Add(GlobalVariables.Color3);

    int minX = Math.Min(GlobalVariables.Corner1.X, Math.Min(GlobalVariables.Corner2.X, Math.Min(GlobalVariables.Corner3.X, GlobalVariables.Corner4.X)));
    int minY = Math.Min(GlobalVariables.Corner1.Y, Math.Min(GlobalVariables.Corner2.Y, Math.Min(GlobalVariables.Corner3.Y, GlobalVariables.Corner4.Y)));
    int maxX = Math.Max(GlobalVariables.Corner1.X, Math.Max(GlobalVariables.Corner2.X, Math.Max(GlobalVariables.Corner3.X, GlobalVariables.Corner4.X)));
    int maxY = Math.Max(GlobalVariables.Corner1.Y, Math.Max(GlobalVariables.Corner2.Y, Math.Max(GlobalVariables.Corner3.Y, GlobalVariables.Corner4.Y)));

    GlobalVariables.MonitoringArea = new Rectangle(minX, minY, maxX - minX, maxY - minY);

    if (tempForm == null)
    {
       tempForm = new TemporaryForm();
       tempForm.Show();
    }
    else
    {
       tempForm.Close();
       tempForm.Dispose();
       tempForm = null;
    }

    StartColorMonitoring();
}


public class TemporaryForm : Form
{
    public TemporaryForm()
    {
        this.TopMost = true;
        this.FormBorderStyle = FormBorderStyle.None;
        this.MinimizeBox = this.MaximizeBox = false;
        this.MaximizedBounds = GlobalVariables.MonitoringArea;
        this.WindowState = FormWindowState.Maximized;
        this.TransparencyKey = this.BackColor = Color.Red;

        int x = Math.Min(GlobalVariables.Corner1.X, Math.Min(GlobalVariables.Corner2.X, Math.Min(GlobalVariables.Corner3.X, GlobalVariables.Corner4.X)));
        int y = Math.Min(GlobalVariables.Corner1.Y, Math.Min(GlobalVariables.Corner2.Y, Math.Min(GlobalVariables.Corner3.Y, GlobalVariables.Corner4.Y)));
        this.Location = new Point(x, y);

        this.Paint += (sender, e) =>
        {
            using (Pen yellowPen = new Pen(Color.Yellow, 4))
            {
                Rectangle borderRect = new Rectangle(0, 0, GlobalVariables.MonitoringArea.Width - 1, GlobalVariables.MonitoringArea.Height - 1);
                e.Graphics.DrawRectangle(yellowPen, borderRect);
            }
        };
    }

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            cp.ExStyle |= 0x80000 /* WS_EX_LAYERED */ | 0x20 /* WS_EX_TRANSPARENT */;
            return cp;
        }
    }
}

// CODE ABOVE IS IN Form1.

//CODE UNDER IS IN Utils.cs:

public static event Action<List<Point>> ColorsFound;

public static void CheckColorsInBackground(Bitmap screenshot, List<Color> targetColors, int tolerance, Rectangle monitoringArea)
{
    List<Point> colorLocations = new List<Point>();
    Color pixelColor = new Color();

    for (int x = monitoringArea.Left; x <= monitoringArea.Right; x++)
    {
        for (int y = monitoringArea.Top; y <= monitoringArea.Bottom; y++)
        {
            int width = screenshot.Width;
            int height = screenshot.Height;

            if (x >= 0 && x < width && y >= 0 && y < height)
            {
                pixelColor = screenshot.GetPixel(x, y);
            }

            foreach (Color targetColor in targetColors)
            {
                if (ColorMatch(pixelColor, targetColor, tolerance))
                {
                    colorLocations.Add(new Point(x, y));
                }
            }
        }
    }

    ColorsFound?.Invoke(colorLocations);
}

So, it kinda works, but for example, i selected the color of a pixel of a random icon from my desktop. Then i move the icon inside the yellow borders of the TemporaryForm, that represents the monitoring area, but the color is not identified anywhere in the middle of the area. It only gets identified if i move the icon to the top left or bottom left corner of the monitoring area. Is there a way to make the pixel analysis better and faster?

Upvotes: -1

Views: 95

Answers (1)

Dan Bystr&#246;m
Dan Bystr&#246;m

Reputation: 9244

You have a coordinate system problem. :-)

When you copy the screen "monitoring area" into the new bitmap "screenshot" your area of interest in that bitmap will be from zero to monitoringArea.Width etc, but in CheckColorsInBackground you do:

for (int x = monitoringArea.Left; x <= monitoringArea.Right; x++)
{
    for (int y = monitoringArea.Top; y <= monitoringArea.Bottom; y++)
    {

That should instead be:

for (int x = 0; x <= monitoringArea.Width; x++)
{
    for (int y = 0; y <= monitoringArea.Height; y++)
    {

It could also be noted that your soulution is horribly slow. But get it working first, then optimize.

Upvotes: 1

Related Questions