Creating a GraphicsPath from a semi-transparent bitmap

Posted by Moozhe on Stack Overflow See other posts from Stack Overflow or by Moozhe
Published on 2012-03-17T18:00:03Z Indexed on 2012/03/19 18:03 UTC
Read the original article Hit count: 597

Filed under:
|
|
|

I want to create a GraphicsPath and a list of Points to form the outline of the non-transparent area of a bitmap. If needed, I can guarantee that each image has only one solid collection of nontransparent pixels. So for example, I should be able to record the points either clockwise or counter-clockwise along the edge of the pixels and perform a full closed loop.

The speed of this algorithm is not important. However, the efficiency of the resulting points is semi-important if I can skip some points to reduce in a smaller and less complex GraphicsPath.

I will list my current code below which works perfectly with most images. However, some images which are more complex end up with paths which seem to connect in the wrong order. I think I know why this occurs, but I can't come up with a solution.

public static Point[] GetOutlinePoints(Bitmap image)
{
    List<Point> outlinePoints = new List<Point>();

    BitmapData bitmapData = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

    byte[] originalBytes = new byte[image.Width * image.Height * 4];
    Marshal.Copy(bitmapData.Scan0, originalBytes, 0, originalBytes.Length);

    for (int x = 0; x < bitmapData.Width; x++)
    {
        for (int y = 0; y < bitmapData.Height; y++)
        {
            byte alpha = originalBytes[y * bitmapData.Stride + 4 * x + 3];

            if (alpha != 0)
            {
                Point p = new Point(x, y);

                if (!ContainsPoint(outlinePoints, p))
                    outlinePoints.Add(p);

                break;
            }
        }
    }

    for (int y = 0; y < bitmapData.Height; y++)
    {
        for (int x = bitmapData.Width - 1; x >= 0; x--)
        {
            byte alpha = originalBytes[y * bitmapData.Stride + 4 * x + 3];

            if (alpha != 0)
            {
                Point p = new Point(x, y);

                if (!ContainsPoint(outlinePoints, p))
                    outlinePoints.Add(p);

                break;
            }
        }
    }

    for (int x = bitmapData.Width - 1; x >= 0; x--)
    {
        for (int y = bitmapData.Height - 1; y >= 0; y--)
        {
            byte alpha = originalBytes[y * bitmapData.Stride + 4 * x + 3];

            if (alpha != 0)
            {
                Point p = new Point(x, y);

                if (!ContainsPoint(outlinePoints, p))
                    outlinePoints.Add(p);

                break;
            }
        }
    }

    for (int y = bitmapData.Height - 1; y >= 0; y--)
    {
        for (int x = 0; x < bitmapData.Width; x++)
        {
            byte alpha = originalBytes[y * bitmapData.Stride + 4 * x + 3];

            if (alpha != 0)
            {
                Point p = new Point(x, y);

                if (!ContainsPoint(outlinePoints, p))
                    outlinePoints.Add(p);

                break;
            }
        }
    }

    // Added to close the loop
    outlinePoints.Add(outlinePoints[0]);

    image.UnlockBits(bitmapData);

    return outlinePoints.ToArray();
}

public static bool ContainsPoint(IEnumerable<Point> points, Point value)
{
    foreach (Point p in points)
    {
        if (p == value)
            return true;
    }

    return false;
}

And when I turn the points into a path:

GraphicsPath outlinePath = new GraphicsPath();
outlinePath.AddLines(_outlinePoints);

Here's an example showing what I want. The red outline should be an array of points which can be made into a GraphicsPath in order to perform hit detection, draw an outline pen, and fill it with a brush.

Example of an Outline

© Stack Overflow or respective owner

Related posts about c#

Related posts about bitmap