1

I have an Bitmap with various color patterns and I need to find the bounding rectangles of one given color (For example: Red) within the Bitmap. I found some code to process images but unable to figure out how to achieve this.

Any help would be highly appreciated.

This is my code.

private void LockUnlockBitsExample(PaintEventArgs e)
{

    // Create a new bitmap.
    Bitmap bmp = new Bitmap("c:\\fakePhoto.jpg");

    // Lock the bitmap's bits.  
    Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
    System.Drawing.Imaging.BitmapData bmpData =
        bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
        bmp.PixelFormat);

    // Get the address of the first line.
    IntPtr ptr = bmpData.Scan0;

    // Declare an array to hold the bytes of the bitmap.
    int bytes  = Math.Abs(bmpData.Stride) * bmp.Height;
    byte[] rgbValues = new byte[bytes];

    // Copy the RGB values into the array.
    System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);

    // Set every third value to 255. A 24bpp bitmap will look red.  
    for (int counter = 2; counter < rgbValues.Length; counter += 3)
        rgbValues[counter] = 255;

    // Copy the RGB values back to the bitmap
    System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);

    // Unlock the bits.
    bmp.UnlockBits(bmpData);

    // Draw the modified image.
    e.Graphics.DrawImage(bmp, 0, 150);
}

Edit: The Bitmap contains solid color shapes, multiple shapes with same color can appear. I need to find the bounding rectangle of each shape.

Just like the paint fills color with bucket tool, I need the bounding rectangle of the filled area.

I can provide x, y coordinates of point on Bitmap to find the bound rectangle of color.

Ruwan Liyanage
  • 333
  • 1
  • 13
  • 1
    This snippet modifies the alpha values of a Bitmap colors and doesn't reflect what you are asking for. Also, what color rectangles? Maybe you mean their points/locations on the Bitmap? –  Jul 13 '20 at 13:44
  • 1
    JQSOFT I edited the question. I need to find bounding boxes of given colour shapes. How to find spread area of given point colour? – Ruwan Liyanage Jul 14 '20 at 03:53

3 Answers3

1

You would do this just like any other code where you want to find the min or max value in a list. With the difference that you want to find both min and max in both X and Y dimensions. Ex:

    public static Rectangle GetBounds(this Bitmap bmp, Color color)
    {
        int minX  = int.MaxValue;
        int minY = int.MaxValue;
        int maxX = int.MinValue;
        int maxY = int.MinValue;
        for (int y = 0; y < bmp.Height; y++)
        {
            for (int x = 0; x < bmp.Width; x++)
            {
                var c = bmp.GetPixel(x, y);
                if (color == c)
                {
                    if (x < minX) minX = x;
                    if (x > maxX) maxX = x;
                    if (y < minY) minY = y;
                    if (y > maxY) maxY = y;
                }
            }
        }

        var width = maxX - minX;
        var height = maxY - minY;
        if (width <= 0 || height <= 0)
        {
            // Handle case where no color was found, or if color is a single row/column 
            return default;
        }
        return new Rectangle(minX, minY, width, height);
    }

There are plenty of resources on how to use LockBits/pointers. So converting the code to use this instead of GetPixel is left as an exercise.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • @nawala The edits to the question changes it significantly, and would require a completely different answer. I would recommend posting it as a new question. – JonasH Jul 14 '20 at 06:39
1

If you are not concerned with the performance, and an exact color match is enough for you, then just scan the bitmap:

var l = bmp.Width; var t = bmp.Height; var r = 0; var b = 0;
for (var i = 0; i<rgbValues.Length, i++)
{
    if(rgbValues[i] == 255) // rgb representation of red; 
    {
        l = Math.Min(l, i % bmpData.Stride); r = Math.Max(r, i % bmpData.Stride);
        t = Math.Min(l, i / bmpData.Stride); b = Math.Max(b, i / bmpData.Stride);
    }
}
if(l>=r) // at least one point is found
    return new Rectangle(l, t, r-l+1, b-t+1);
else
    return new Rectangle(0, 0, 0, 0); // nothing found
      
0

You can search for the first point of each shape that fills a different area on the Bitmap, read a single horizontal row to get the points of the given color, then loop vertically within the horizontal range to get the adjacent points.

Once you get all the points of each, you can calculate the bounding rectangle through the first and last points.

public static IEnumerable<Rectangle> GetColorRectangles(Bitmap src, Color color)
{
    var rects = new List<Rectangle>();
    var points = new List<Point>();
    var srcRec = new Rectangle(0, 0, src.Width, src.Height);
    var srcData = src.LockBits(srcRec, ImageLockMode.ReadOnly, src.PixelFormat);
    var srcBuff = new byte[srcData.Stride * srcData.Height];
    var pixSize = Image.GetPixelFormatSize(src.PixelFormat) / 8;

    Marshal.Copy(srcData.Scan0, srcBuff, 0, srcBuff.Length);
    src.UnlockBits(srcData);

    Rectangle GetColorRectangle()
    {
        var curX = points.First().X;
        var curY = points.First().Y + 1;
        var maxX = points.Max(p => p.X);

        for(var y = curY; y < src.Height; y++)
            for(var x = curX; x <= maxX; x++)
            {
                var pos = (y * srcData.Stride) + (x * pixSize);
                var blue = srcBuff[pos];
                var green = srcBuff[pos + 1];
                var red = srcBuff[pos + 2];

                if (Color.FromArgb(red, green, blue).ToArgb().Equals(color.ToArgb()))
                    points.Add(new Point(x, y));
                else
                    break;
            }

        var p1 = points.First();
        var p2 = points.Last();

        return new Rectangle(p1.X, p1.Y, p2.X - p1.X, p2.Y - p1.Y);
    }

    for (var y = 0; y < src.Height; y++)
    {
        for (var x = 0; x < src.Width; x++)
        {
            var pos = (y * srcData.Stride) + (x * pixSize);
            var blue = srcBuff[pos];
            var green = srcBuff[pos + 1];
            var red = srcBuff[pos + 2];

            if (Color.FromArgb(red, green, blue).ToArgb().Equals(color.ToArgb()))
            {
                var p = new Point(x, y);

                if (!rects.Any(r => new Rectangle(r.X - 2, r.Y - 2,
                    r.Width + 4,  r.Height + 4).Contains(p)))
                    points.Add(p);
            }
        }

        if (points.Any())
        {
            var rect = GetColorRectangle();
            rects.Add(rect);
            points.Clear();
        }
    }

    return rects;
}

Demo

private IEnumerable<Rectangle> shapesRects = Enumerable.Empty<Rectangle>();

private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
    var sx = 1f * pictureBox1.Width / pictureBox1.ClientSize.Width;
    var sy = 1f * pictureBox1.Height / pictureBox1.ClientSize.Height;
    var p = Point.Round(new PointF(e.X * sx, e.Y * sy));
    var c = (pictureBox1.Image as Bitmap).GetPixel(p.X, p.Y);

    shapesRects = GetColorRectangles(pictureBox1.Image as Bitmap, c);
    pictureBox1.Invalidate();
}

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    if (shapesRects.Any())
        using (var pen = new Pen(Color.Black, 2))
        e.Graphics.DrawRectangles(pen, shapesRects.ToArray());
}

SOA62875861

  • JQSOFT, Your demo looks like exactly what I'm looking for. I tried your code and created some additional code to reproduce the output as your demo.But I'm getting multiple rectangles in same shape with various sizes. What I'm doing wrong here? – Ruwan Liyanage Jul 15 '20 at 13:09
  • Bitmap bmp = ((Bitmap)pictureBox2.Image); var ptclr = (bmp.GetPixel(e.X ,e.Y )); var pts = GetColorRectangles((Bitmap)pictureBox2.Image, ptclr); using (Graphics gr = Graphics.FromImage(bmp)) { gr.SmoothingMode = SmoothingMode.AntiAlias; foreach (var rr in pts) { //Rectangle rect = new Rectangle(10, 10, 260, 90); gr.DrawRectangle(Pens.Black , rr); } } p3.Image = bmp; – Ruwan Liyanage Jul 15 '20 at 13:11
  • @nawala Well. I don't know what you are doing. You need to have an image and a color, pass them to the function and it will return the bounding rectangles as you see in the demo. Don't call it while you drawing a Bitmap, once you have a valid Bitmap which you can see it in a `PictureBox` then you can use the function. –  Jul 15 '20 at 13:19
  • I used same image you posted in demo, with mouse pointer pixel colour (Code in previous comment) I draw after function call so it does not effect the process. btw I dont know how to post screenshot of output I got here.. – Ruwan Liyanage Jul 15 '20 at 14:15
  • @nawala Set `pictureBox2.SizeMode = PictureBoxSizeMode.Normal;` and try. –  Jul 15 '20 at 14:20
  • I tried but same result. I get multiple small rectangles inside of shape. not at bounds.. – Ruwan Liyanage Jul 15 '20 at 14:40
  • @nawala You have a solution with a demo here that answers your second version of your question. So, raising new requirements again is not accepted. Whether ask a new question for a specific problem or wait for a better solutions. You need to learn how to understand the code that you ask for and how to tweak/modify it to meet your needs. Good day. –  Jul 15 '20 at 15:38