Search code examples
c#thumbnailssystem.drawingsystem.drawing.imaging

Center crop image while using C# System.Drawing to generate thumbnail


I have a scenario where I am generating a thumbnail using System.Drawing by resizing the original image to a provided size (size being used ). If the source image is a rectangle, the resulting thumbnail needs to be square without ruining the image (stretch it). I have the following code so far:

//Obtain original image from input stream
using (var sourceImage = new Bitmap(Image.FromStream(inStream)))
{
    //Setting thumbnail aspect ratio based on source image
    int destWidth, destHeight;

    if (sourceImage.Width > sourceImage.Height)
    {
        destWidth = providedSize;
        destHeight = Convert.ToInt32(sourceImage.Height * providedSize/ (double)sourceImage.Width);
    }
    else
    {
        destWidth = Convert.ToInt32(sourceImage.Width * providedSize/ (double)sourceImage.Height);
        destHeight = providedSize;
    }

    //Initialize thumbnail bitmap
    var thumbnail = new Bitmap(destWidth, destHeight);

    //Create thumbnail
    using (var graphics = Graphics.FromImage(thumbnail))
    {
        graphics.CompositingQuality = CompositingQuality.HighSpeed;
        graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
        graphics.CompositingMode = CompositingMode.SourceCopy;
        graphics.DrawImage(sourceImage, 0, 0, destWidth, destHeight);

        using (MemoryStream stream = new MemoryStream())
        {
            var encoderParameters = new EncoderParameters(1);
            encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, Convert.ToInt64(Environment.GetEnvironmentVariable("ThumbnailQuality")));
            var codecInfo = ImageCodecInfo.GetImageDecoders().FirstOrDefault(c => c.FormatID == ImageFormat.Jpeg.Guid);
            thumbnail.Save(stream, codecInfo, encoderParameters);
            stream.Position = 0;

            //Upload thumbnail
        }
    }
}

Solution

  • And I've figured out the solution, the Graphics.DrawImage needs to be used as the point where calculated offsets can be provided along with the destination rectangle to create a perfect center-cropped square result. The following is the improved code:

    //Obtain original image from input stream
    using (var sourceImage = new Bitmap(Image.FromStream(inStream)))
    {
        //Obtain source dimensions and initialize scaled dimensions and crop offsets
        int sourceWidth = sourceImage.Width;
        int sourceHeight = sourceImage.Height;
        int scaledSourceWidth = 0;
        int scaledSourceHeight = 0;
        int offsetWidth = 0;
        int offsetHeight = 0;
    
        //Calculate cropping offset
        if (sourceWidth > sourceHeight)
        {
            offsetWidth = (sourceWidth - sourceHeight) / 2;
            scaledSourceWidth = sourceWidth / (sourceHeight / thumbSize);
            scaledSourceHeight = thumbSize;
        }
        else if (sourceHeight > sourceWidth)
        {
            offsetHeight = (sourceHeight - sourceWidth) / 2;
            scaledSourceHeight = sourceHeight / (sourceWidth / thumbSize);
            scaledSourceWidth = thumbSize;
        }
    
        //Create new thumbnail image of height and width defined in thumbSize
        Bitmap thumbnail = new Bitmap(thumbSize, thumbSize, PixelFormat.Format24bppRgb);
        thumbnail.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);
    
        using (var graphics = Graphics.FromImage(thumbnail))
        {
            //Draw source image scaled down with aspect ratio maintained onto the thumbnail with the offset
            graphics.CompositingQuality = CompositingQuality.HighSpeed;
            graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
            graphics.CompositingMode = CompositingMode.SourceCopy;
            graphics.DrawImage(sourceImage, new Rectangle(0, 0, scaledSourceWidth, scaledSourceHeight), offsetWidth, offsetHeight, sourceWidth, sourceHeight, GraphicsUnit.Pixel);
    
            //Push thumbnail onto stream for upload
            using (MemoryStream stream = new MemoryStream())
            {
                var encoderParameters = new EncoderParameters(1);
                encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, Convert.ToInt64(Environment.GetEnvironmentVariable("ThumbnailQuality")));
                var codecInfo = ImageCodecInfo.GetImageDecoders().FirstOrDefault(c => c.FormatID == ImageFormat.Jpeg.Guid);
                thumbnail.Save(stream, codecInfo, encoderParameters);
                stream.Position = 0;
    
                //Upload thumbnail
            }
        }
    }
    

    Any improvements or optimizations are welcomed.