Use transparency when drawing with anti-aliasing in C#

[anti-aliasing]

Anti-aliasing makes drawn lines look smoother on a raster image such as a bitmap or on the screen. Transparency allows you to place an image on top of another image so parts of the underlying image show through. Unfortunately, when you use both anti-aliasing and transparency, you can get some odd results.

To see where the problem occurs, consider the picture at the top of this post. To make the drawing on the left, the program drew a smiley face in a bitmap with a white background. It then used the bitmap’s MakeTransparent method to make the image’s white pixels transparent. It finished by copying the bitmap, which now has some transparent pixels, onto a PictureBox that has a blue background.

The problem is that the smiley face was drawn with anti-aliasing. The result is that the pixels at the border between two colors are blended. For example, where the smiley’s black outline pixels meet the white background, some pixels are converted into shades of gray to make them blend more smoothly together. Then when the program calls MakeTransparent, that method only makes the pixels that are pure white into transparent pixels. The gray ones remain gray. When the program draws the result on the blue background, you can still see the gray pixels along the edge of the smiley face. (Look closely at the edges of the left smiley in the picture above.)

Approaches

There are a couple of ways that you can fix this problem. First, you can ensure that the background in the original image matches (at least approximately) the background in the final image on which you display the bitmap. Then the anti-aliased pixels blend properly. In this example, the program could either use a blue background for the original image before calling MakeTransparent, or it could use a white background in the final image instead of a blue one.

That solution only works if you have control over how the original image or the final image is produced. If you are simply given an image with transparent pixels and you need to place it over a specific background image, then you might not have that luxury.

A second approach would be to examine all of the transparent bitmap’s pixels and find the ones that use anti-aliasing. In this example, you would look for shades of gray. You would then convert the background component into a transparent component. For example, if a gray pixel is 50% white and 50% black, you would convert it into 50% transparent and 50% black. This approach should work well, but only if you know which colors show the anti-aliasing. It also requires you to examine every pixel in the the image, which could be slow.

A third approach, and by far the best, is to draw the original image on a transparent background. Then the anti-aliasing will make the appropriate pixels semi-transparent as needed just as if you had taken the second approach. Now no matter what background you place behind the bitmap, the transparent parts of the image will blend perfectly.

The program uses this third approach to draw the right smiley in the picture at the top of this post. If you look closely, you’ll see that the smiley’s edge blend smoothly with the background.

[anti-aliasing]

The picture on the right shows a closeup of the area between the smileys. In this enlarged image, you can easily see the pixels that provide the anti-aliasing. In the left smiley, they are shades of gray that blended the original white background with the smiley’s black edge. In the right smiley, those pixels have colors that include some transparency so they blend the smiley’s black edge with any background color (in this case, blue). (You can also see in both smileys how the black and yellow pixels blend together.)

Code

The following code shows how the program draws its two smiley bitmaps, each of which contains some transparent background pixels.

private Bitmap FgOnWhite, FgOnTransparent;

// Draw the smiley bitmaps with transparent backgrounds.
private void Form1_Load(object sender, EventArgs e)
{
    int wid = pictureBox1.ClientSize.Width;
    int hgt = pictureBox1.ClientSize.Height;

    // Draw with a white background.
    FgOnWhite = new Bitmap(wid, hgt);
    using (Graphics gr = Graphics.FromImage(FgOnWhite))
    {
        gr.SmoothingMode = SmoothingMode.AntiAlias;
        gr.Clear(Color.White);
        DrawSmiley(gr, pictureBox1.ClientRectangle, 10);

        // Make the white pixels transparent.
        FgOnWhite.MakeTransparent(Color.White);
    }

    // Draw with a transparent background.
    FgOnTransparent = new Bitmap(wid, hgt);
    using (Graphics gr = Graphics.FromImage(FgOnTransparent))
    {
        gr.SmoothingMode = SmoothingMode.AntiAlias;
        gr.Clear(Color.Transparent);
        DrawSmiley(gr, pictureBox1.ClientRectangle, 10);
    }
}

The code declares the FgOnWhite and FgOnTransparent bitmaps outside of any method so all of the form’s code can use them.

The form’s Load event handler first creates the FgOnWhite bitmap. It clears the bitmap with white and then calls the DrawSmiley method described shortly to draw on it. It then calls the bitmap’s MakeTransparent method to make the white pixels transparent.

Next, the program repeats roughly the same steps to make another smiley bitmap. This time it clears the bitmap with the color Transparent before it draws. Because the background is already transparent, it doesn’t need to call MakeTransparent for this bitmap.

The following method draws a smiley face in a given rectangle.

// Draw a smiley face in the rectangle.
private void DrawSmiley(Graphics gr,
    Rectangle rect, int wid)
{
    rect.Inflate(-4, -4);
    using (Pen pen = new Pen(Color.Black, wid))
    {
        // Face.
        Rectangle r = new Rectangle(
            rect.Left + wid / 2, rect.Top + wid / 2,
            rect.Width - wid, rect.Height - wid);
        gr.FillEllipse(Brushes.Yellow, r);
        gr.DrawEllipse(pen, r);

        // Smile.
        pen.Width /= 2;
        r.Inflate(-30, -30);
        gr.DrawArc(pen, r, 10, 160);

        // Left eye.
        int eye_wid = (int)(rect.Width * 0.2);
        int eye_hgt = (int)(rect.Height * 0.25);
        Rectangle eye_r = new Rectangle(
            (int)(rect.Left + rect.Width * 0.25),
            (int)(rect.Top + rect.Height * 0.20),
            eye_wid, eye_hgt);
        gr.FillEllipse(Brushes.LightBlue, eye_r);
        gr.DrawEllipse(pen, eye_r);
        Rectangle pupil_r = new Rectangle(
            eye_r.Left + eye_wid / 2,
            eye_r.Top + eye_hgt / 4,
            eye_wid / 2, eye_hgt / 2);
        gr.FillEllipse(Brushes.Black, pupil_r);
        gr.DrawEllipse(pen, pupil_r);

        // Right eye.
        eye_r = new Rectangle(
            (int)(rect.Right - rect.Width * 0.25) - eye_wid,
            (int)(rect.Top + rect.Height * 0.20),
            eye_wid, eye_hgt);
        gr.FillEllipse(Brushes.LightBlue, eye_r);
        gr.DrawEllipse(pen, eye_r);
        pupil_r = new Rectangle(
            eye_r.Left + eye_wid / 2,
            eye_r.Top + eye_hgt / 4,
            eye_wid / 2, eye_hgt / 2);
        gr.FillEllipse(Brushes.Black, pupil_r);
        gr.DrawEllipse(pen, pupil_r);

        // Nose.
        Rectangle nose_r = new Rectangle(
            (int)(rect.Left + rect.Width / 2) - eye_wid / 2,
            (int)(rect.Top + rect.Height * 0.45),
            eye_wid, eye_wid);
        gr.FillEllipse(Brushes.LightGreen, nose_r);
        gr.DrawEllipse(pen, nose_r);
    }
}

This method simply draws the smiley’s outline, smile, eyes, and nose.

The last pieces of the program are the following event handlers, which draw the smiley bitmaps on top of blue backgrounds when each of the form’s PictureBox controls raises its Paint event.

// Draw the smiley face on the control.
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.Clear(Color.Blue);
    e.Graphics.DrawImage(FgOnWhite, 0, 0);
}

private void pictureBox2_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.Clear(Color.Blue);
    e.Graphics.DrawImage(FgOnTransparent, 0, 0);
}

These event handlers simply clear their controls with blue and then draw the smiley bitmaps on top of them.

Download the example to experiment with it. For example, instead of clearing the controls with blue, give them a BackgroundImage at design time and then just draw the smiley images over them in their Paint event handlers.


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 drawing, graphics, image processing and tagged , , , , , , , , . Bookmark the permalink.

1 Response to Use transparency when drawing with anti-aliasing in C#

  1. Pingback: Make an image containing shadowed text in WPF and C# - C# HelperC# Helper

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.