Draw outline graphics in C#

[outline graphics]

This example shows how to outline graphics such as text or other pictures in a Windows Forms application. The idea is to consider the pixels in an image and find those that are a certain distance away from the text. For example, the picture shown here finds pixels between 5 and 10 pixels from the blue text and colors them red.

There are a few details, however, to get a good result.

The program uses the Bitmap32 class to access pixels more quickly than is possible by using a normal Bitmap. See the post Use the Bitmap32 class to manipulate image pixels very quickly in C# for information about that class.

The following MakeOutline method controls the outlining process. It takes as input a mask image that it should use to create the outline graphics. In the result, any pixels that are between min_radius and max_radius from a non-white pixel in the mask are colored, in this example red.

// Make an outline image.
private Bitmap MakeOutline(Bitmap mask,
    int min_radius, int max_radius)
{
    Bitmap32 mask_bm32 = new Bitmap32(mask);
    mask_bm32.LockBitmap();

    // Make the result bitmap.
    Bitmap new_bm = new Bitmap(mask.Width, mask.Height);
    using (Graphics gr = Graphics.FromImage(new_bm))
    {
        gr.Clear(Color.Transparent);
    }
    Bitmap32 new_bm32 = new Bitmap32(new_bm);
    new_bm32.LockBitmap();

    for (int x = 0; x < mask_bm32.Width; x++)
    {
        for (int y = 0; y < mask_bm32.Height; y++)
        {
            float dist =
                DistToNonWhite(mask_bm32, x, y, max_radius);
            if ((dist > min_radius) && (dist < max_radius))
            {
                byte alpha = 255;
                if (dist - min_radius < 1)
                    alpha = (byte)(255 * (dist - min_radius));
                else if (max_radius - dist < 1)
                    alpha = (byte)(255 * (max_radius - dist));

                new_bm32.SetPixel(x, y, 255, 0, 0, alpha);
            }
        }
    }

    mask_bm32.UnlockBitmap();
    new_bm32.UnlockBitmap();
    return new_bm;
}

The code creates a Bitmap32 to represent the input mask. It then creates the result bitmap to hold the outline graphics. It clears the result bitmap with white and makes a Bitmap32 to manipulate it.

The code then loops over the pixels in the mask. For each pixel it calls the DistToNonWhite method to find the distance from the pixel to the nearest non-white pixel in the mask.

If the distance is between min_radius and max_radius, the code colors the corresponding output pixel. If the pixel is within distance 1 of the borders of the colored area, the code calculates an alpha value to make it semi-transparent. If the pixel is right on the edge of the desired range, alpha is small so the resulting color is mostly transparent. If the pixel is well within the range min_radius to max_radius, then alpha is large so the resulting color is mostly opaque.

Using alpha values that drop off near the edges of the colored areas makes the edges of the outline graphics smoother. It basically anti-aliases the colored areas.

After it calculates alpha, the code sets the result pixel’s color. Finally, after every pixel has been processed, the method returns the result bitmap.

The following code shows the DistToNonWhite method.

// Return the distance to the nearest non-white pixel
// within the radius.
private float DistToNonWhite(Bitmap32 bm32, int x, int y,
    int radius)
{
    int minx = Math.Max(x - radius, 0);
    int maxx = Math.Min(x + radius, bm32.Width - 1);
    int miny = Math.Max(y - radius, 0);
    int maxy = Math.Min(y + radius, bm32.Height - 1);
    int dist2 = radius * radius + 1;

    for (int tx = minx; tx < maxx; tx++)
    {
        for (int ty = miny; ty <= maxy; ty++)
        {
            byte r, g, b, a;
            bm32.GetPixel(tx, ty, out r, out g, out b, out a);

            if ((r < 200) || (g < 200) || (b < 200))
            {
                int dx = tx - x;
                int dy = ty - y;
                int test_dist2 = dx * dx + dy * dy;
                if (test_dist2 < dist2) dist2 = test_dist2;
            }
        }
    }

    return (float)Math.Sqrt(dist2);
}

This method returns the distance from a given pixel to the nearest non-white pixel in a mask image. The input value max_radius tells how far the method should look.

The method first calculates the minimum and maximum X and Y values that it should check. Those values are calculated from the target pixel’s location plus and minus the maximum distance and then adjusted so they remain on the bitmap. (We can’t look at pixels that lie outside of the bitmap.)

The code then loops over the pixels in the selected area. If a pixel’s red, green, or blue color component is less than 200, then that pixel is not white. (You could use a stricter definition of non-white if you like. If you want to consider any non-white pixel, you could set those limits to 255 instead of 200.

If a pixel is non-white, the code calculates the distance squared between the target pixel and the non-white pixel.

When it has finished examining the pixels, the code returns the square root of the smallest distance it found. (Looking at distance squared is a common trick in performing numerical calculations. When you minimize distance squared, you also minimize the distance. Taking the square root only outside of the loop lets you avoid taking square roots many times inside the loop. THat’s good because the quare root operation is relatively slow.)

[outline graphics]

That’s all of the interesting code in this example. Download the program to see additional details.

The picture on the right shows the mask used by the program. You could use the text image itself for the mask but then the program would add extra red pixels between “C#” and “Helper,” and inside the “H.” This mask gives a nicer result. For your application you may need to experiment a bit to get a mask that produces an aesthetically pleasing result.


Download Example   Follow me on Twitter   RSS feed   Donate




About RodStephens

Rod Stephens is a software consultant and author who has written more than 30 books and 250 magazine articles covering C#, Visual Basic, Visual Basic for Applications, Delphi, and Java.
This entry was posted in algorithms, drawing, graphics and tagged , , , , , , , , , , , . Bookmark the permalink.

One Response to Draw outline graphics in C#

  1. Mark T. says:

    A great trick when checking distances is to just check the value of “distance squared”.
    If you want distance less than 10, then you want distance_squared less than 100.
    That eliminates the need to call Math.Sqrt at all.

Leave a Reply

Your email address will not be published. Required fields are marked *