A correct answer can be pieced together from some of the other responses, but none of them is complete and some present some very bad ideas (like drawing the image twice).
The problem
There are three reasons for the artifacts you re seeing:
- The default
Graphics.PixelOffsetMode
setting causes the pixel values to be sampled incorrectly, resulting in a slight distortion of the image, particularly around the edges.
InterpolationMode.HighQualityBicubic
samples pixels from beyond the image edge, which are transparent by default. Those transparent pixels are mixed with the edge pixels by the sampler, resulting in semi-transparent edges.
- When you save a semi-transparent image in a format that doesn t support transparency (e.g. JPEG), the transparent values are replaced by black.
That all adds up to semi-black (i.e. grey) edges.
There are a few other issues with the code you posted as well:
The Bitmap
constructor you used is initializing the new Bitmap
by resizing the original image, so you re doing the resize operation twice. You should use a constructor overload with just the desired dimensions to create a blank canvas.
Remember that the Bitmap
class represents an unmanaged copy of the image in memory. It needs to be disposed so that GDI+ can be told to release that memory when you re done with it. I assume you re doing that in the code that receives the return Image
, but I point that out in case anyone else borrows this code.
The CompositingQuality.HighQuality
setting used in your code will have no visual effect if you get the other settings right and will actually hurt performance fairly significantly in combination with the default value of CompositingMode.SourceOver
. You can omit the CompositingQuality
setting and set CompositingMode.SourceCopy
to get the same results with better performance.
The SmoothingMode
setting used in your code has no impact at all on DrawImage()
, so it can be removed.
Solution
The correct way to remove those artifacts is to use PixelOffsetMode.Half
and to use an ImageAttributes
object to specify edge tiling so the HighQualityBicubic
sampler has something other than transparent pixels to sample.
You can read more about the Graphics
class settings and their impact on image quality and performance here: http://photosauce.net/blog/post/image-scaling-with-gdi-part-3-drawimage-and-the-settings-that-affect-it
The revised code should look something like this:
public static Image Scale(Image sourceImage, int destWidth, int destHeight)
{
var toReturn = new Bitmap(destWidth, destHeight);
using (var graphics = Graphics.FromImage(toReturn))
using (var attributes = new ImageAttributes())
{
toReturn.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);
attributes.SetWrapMode(WrapMode.TileFlipXY);
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.Half;
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.DrawImage(sourceImage, Rectangle.FromLTRB(0, 0, destWidth, destHeight), 0, 0, sourceImage.Width, sourceImage.Height, GraphicsUnit.Pixel, attributes);
}
return toReturn;
}