Search code examples
c#graphicsimage-manipulationgdi

Resizing Image Quality Reduction


I have written a simple PhotoEditor helper class to downscale and crop images uploaded to my website. Everything is working but I am seeing unsatisfactory quality when the image is saved to file. I have read up on the different settings you can tweak below is my setup for resizing, cropping is identical.

    public Image ResizeImage(Image imgToResize, Size size)
    {
        int sourceWidth = imgToResize.Width;
        int sourceHeight = imgToResize.Height;

        float nPercentW = (size.Width/(float) sourceWidth);
        float nPercentH = (size.Height/(float) sourceHeight);

        float nPercent = nPercentH < nPercentW ? nPercentH : nPercentW;

        var destWidth = (int) (sourceWidth*nPercent);
        var destHeight = (int) (sourceHeight*nPercent);

        var src = imgToResize;

        using (var dst = new Bitmap(destWidth, destHeight, imgToResize.PixelFormat))
        {
            dst.SetResolution(imgToResize.HorizontalResolution, imgToResize.VerticalResolution);

            using (var g = Graphics.FromImage(dst))
            {
                var mime = GetMimeType(imgToResize);
                ImageFormat format;
                if (mime == "image/gif" || mime == "image/png")
                {
                    //convert all gif to png, better resize quality
                    g.SmoothingMode = SmoothingMode.AntiAlias;
                    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    g.DrawImage(src, 0, 0, dst.Width, dst.Height);
                    format = ImageFormat.Png;
                }
                else
                {
                    //jpeg
                    g.CompositingQuality = CompositingQuality.HighQuality;
                    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    g.SmoothingMode = SmoothingMode.HighQuality;
                    g.PixelOffsetMode = PixelOffsetMode.HighQuality;
                    format = ImageFormat.Jpeg;
                }
                g.DrawImage(src, 0, 0, dst.Width, dst.Height);

                // At this point the new bitmap has no MimeType
                // Need to output to memory stream
                var m = new MemoryStream();
                dst.Save(m, format);

                var img = Image.FromStream(m);

                return img;
            }
        }
    }

As you can see I am using the suggested settings for Interpolation, Smoothing etc. I am also saving the jpeg with quality 100.

The resultant image has noticeable blurring and artefacts even when resizing down to a modest 75% of its original size. I looked around and this is the recommended way. I found a simplistic way to resize and decided to give that a go.

Bitmap NewImg = new Bitmap(original, new Size(387,257));
editor.SaveImage(@"C:\simpleResize.jpg", NewImg, ImageFormat.Jpeg);

Surprisingly this produces a much nicer image although it is quite a bit bigger ~30% larger in memory footprint.

My question is, what's the difference and what setting am I missing on my resize routine that could account for the uglier result. Id like to get my resize routine to produce the exact same result as the simple resize.

Your help is much appreciated. This is my first foray into image processing.

EDIT

Simple Resize (82KB)

simple http://img189.imageshack.us/img189/2137/simpleresize.jpg

My Resize (55KB)

complex http://img12.imageshack.us/img12/4023/complexresize.jpg


Solution

  • My first thought was that .NET's jpeg encoder uses chroma subsampling even at the highest quality setting, so color information is stored at half resolution. The setting is hard-coded as far as I can tell. But that wouldn't explain why you would get better quality in the second example. Unless maybe in the 2nd it didn't use antialiasing, giving a sharper (but lower quality) image and the artifacts went unnoticed.

    Edit: The dst.Save(m, format); looks like your problem. You're encoding it as jpeg there, with default quality (not 100%), then immediately decoding it back to an image. dst is already an Image (Bitmap class inherits from Image), so you can just return it as-is