Resize and crop an image keeping its aspect ratio with C#

* Note: the complete C# source code is at the end of this article

Goals

  • Resize an image to exact new dimensions, keeping its aspect ratio
  • Avoid using native libraries (interoperability)

Resizing and croping an image keeping its aspect ratio with C#

When we have to resize an image to exact new dimensions while keeping it's aspect ratio (thus avoiding distortion of the image) with C# we can take advantage of the Graphics class. The Graphics class exposes method DrawImage which allows us to resize an existing source image and draw it onto the image referenced by the Graphics instance.

If we have a source image called source we can create a new image called target with the desired dimensions then instantiate a Graphics object from our target image:

// Note: I'm omitting 'using' statements for readability purposes
var source = Image.FromFile("source.jpg");
var target = new Bitmap(100, 50);
var g = Graphics.FromImage(target);

The Graphics class also allows us to specify the quality of the resulting image, as follows:

// Note: the higher the image quality, the slower the rendering
g.CompositingQuality = CompositingQuality.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.HighQuality;

We can then draw the resized source image onto our target with method DrawImage:

g.DrawImage(source, 0, 0, 100, 50);

This simple code will result in our target image being of the desired size, but we will probably have a distorted image because method Graphics.DrawImage doesn't maintain the aspect ratio of the source image.

We need to compute the correct aspect ratio (our scaling factor) and the correct horizontal/vertical placement (which will result in the image being cropped). We first get the scaling factor:

float scaling;
var scalingY = (float)source.Height / targetHeight;
var scalingX = (float)source.Width / targetWidth;
if (scalingX < scalingY) scaling = scalingX; else scaling = scalingY;

Then we compute the new image size:

var newWidth = (int)(source.Width / scaling);
var newHeight = (int)(source.Height / scaling);

// Correct float-to-int rounding
if (newWidth < width) newWidth = width;
if (newHeight < height) newHeight = height;

We're almost done. Since there may be differences between the source aspect ratio (source.Width / source.Height) and the target one (target.Width / target.Height) we need to ensure that cropping, if any, is equally distributed. In other words, we need to center the resized image - the result of method Graphics.DrawImage - within the new bitmap dimensions:

int shiftX = 0, shiftY = 0;
if (newWidth > width) shiftX = (newWidth - width) / 2;
if (newHeight > height) shiftY = (newHeight - height) / 2;

We can now resize the image with DrawImage:

g.DrawImage(source, -shiftX, -shiftY, newWidth, newHeight);

Putting it all together, please find the complete C# function below, followed by a usage example.

Happy Coding! ;)



Finished Method

/// <summary>
/// Resize an image keeping its aspect ratio (cropping may occur).
/// </summary>
/// <param name="source"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <returns></returns>
public Image ResizeImageKeepAspectRatio(Image source, int width, int height)
{
    Image result = null;

    try
    {
        if (source.Width != width || source.Height != height)
        {
            // Resize image
            float sourceRatio = (float)source.Width / source.Height;

            using (var target = new Bitmap(width, height))
            {
                using (var g = System.Drawing.Graphics.FromImage(target))
                {
                    g.CompositingQuality = CompositingQuality.HighQuality;
                    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    g.SmoothingMode = SmoothingMode.HighQuality;

                    // Scaling
                    float scaling;
                    float scalingY = (float)source.Height / height;
                    float scalingX = (float)source.Width / width;
                    if (scalingX < scalingY) scaling = scalingX; else scaling = scalingY;

                    int newWidth = (int)(source.Width / scaling);
                    int newHeight = (int)(source.Height / scaling);

                    // Correct float to int rounding
                    if (newWidth < width) newWidth = width;
                    if (newHeight < height) newHeight = height;

                    // See if image needs to be cropped
                    int shiftX = 0;
                    int shiftY = 0;

                    if (newWidth > width)
                    {
                        shiftX = (newWidth - width) / 2;
                    }

                    if (newHeight > height)
                    {
                        shiftY = (newHeight - height) / 2;
                    }

                    // Draw image
                    g.DrawImage(source, -shiftX, -shiftY, newWidth, newHeight);
                }

                result = (Image)target.Clone();
            }
        }
        else
        {
            // Image size matched the given size
            result = (Image)source.Clone();
        }
    }
    catch (Exception)
    {
        result = null;
    }

    return result;
} 

Usage Example

// This code will resize an existing JPGEG image and save it to a new file 
int width = 200;
int height = 400;

using (var source = Bitmap.FromFile(@"c:\temp\source.jpg"))
{
    using (var result = (Bitmap)ResizeImageKeepAspectRatio(source, width, height))
    {
        result.Save(@"d:\temp\target.jpg");
    }
}
comments powered by Disqus