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.
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:
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:
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:
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: