Marius Schulz
Marius Schulz
Front End Engineer

Preventing Ghost Borders When Resizing Images with System.Drawing

Recently, I needed to scale down user profile images for a web application I'm working on. If a user doesn't upload an avatar, the following default avatar is used:

The Default Avatar

When I scaled down this PNG, I noticed some ugly white lines along the edges:

The Avatar with Ringing

I googled around and found out that this effect is called ringing or ghost borders. Here's how these artifacts occur and how you can get rid of them.

#Basic Image Resizing Using System.Drawing

I started out with some very simple code to resize images in .NET. The System.Drawing namespace, a wrapper around GDI+, contains pretty much everything you need for that purpose. With a little help of the Graphics class and its DrawImage method, resizing an image can be as simple as that:

public Image Resize(Image image, int targetWidth, int targetHeight)
{
    var resizedImage = new Bitmap(targetWidth, targetHeight);

    using (var graphics = Graphics.FromImage(resizedImage))
    {
        graphics.DrawImage(image, 0, 0, targetWidth, targetHeight);
    }

    return resizedImage;
}

The avatar was resized correctly; however, if you look closely, you'll notice that it's pixely because no anti-aliasing has been applied:

The Avatar with Aliasing

#Image Resizing with Anti-Aliasing

Anti-Aliasing? Well, nothing easier than that, I thought. I specified an InterpolationMode which produces the highest quality transformed images:

using (var graphics = Graphics.FromImage(resizedImage))
{
    graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
    graphics.DrawImage(image, 0, 0, targetWidth, targetHeight);
}

The resized image wasn't pixely anymore, but now showed white lines along the edges:

The Avatar with Ringing

#Stripe-Free Image Resizing with Anti-Aliasing

After reading through some articles on imaging in .NET, I found out I had to use ImageAttributes and specify a wrap mode, namely WrapMode.TileFlipXY, to get rid of the annoying artifacts. Here's the resulting code:

using (var graphics = Graphics.FromImage(resizedImage))
{
    graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;

    var attributes = new ImageAttributes();
    attributes.SetWrapMode(WrapMode.TileFlipXY);

    var destination = new Rectangle(0, 0, targetWidth, targetHeight);
    graphics.DrawImage(image, destination, 0, 0, image.Width, image.Height,
        GraphicsUnit.Pixel, attributes);
}

The DrawImage method has plenty of overloads. From the ones that accept a parameter of type ImageAttributes, I chose the highlighted one:

The Nicely Resized Avatar

Finally, here's the resized avatar, nice and free of ghost borders:

The Nicely Resized Avatar

#Explanation of the Effect

The effect of TileFlipXY comes into play when the resizing algorithm gathers detail from neighboring pixels along the edges of the image. TileFlipXY tells it to place horizontally and vertically flipped copies of the image next to itself, thereby putting similarly colored pixels next to the ones at the border. By doing that, no more ghost borders will appear.

If you want to read more about ringing, check out these two posts: