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:
When I scaled down this PNG, I noticed some ugly white lines along the edges:
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:
#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:
#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:
Finally, here's the resized avatar, nice and free of ghost borders:
#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:
- Image Resizing - outperform GDI+ (CodeProject article)
- Ghost-borders ('ringing') when resizing in GDI+ (StackOverflow question)