If you need to resize an image or crop an image or compress an image or even do all three you will find you can do all of those things using the .NET framework's System.Drawing classes. In this tutorial I will talk you through the steps required to do all three to transform an large image to an image fit for your website.
This is the example scenario. You have been asked to make a page that enables a user to upload any image they want so that it can be manipulated to ensure it is 200px x 200px and compressed to a smaller file size. I won't talk you though how to make the uploading part as there are a plethora of blogs discussing that already. What I will discuss is how to resize crop and compress that image.
Say you have an image that is 1000px x 800px. There are 3 main steps to converting it to a compressed 200px x 200px image:
Resizing an image
To resize it the key is that it needs to keep the same aspect ratio whilst being a lot smaller.
So the expected outcome of this stage will be that the shortest of the two dimensions will be 200px, the other will be larger and the aspect ratio will remain the same. Begin code:
private byte[] GetCroppedImage(byte[] originalBytes, Size size, ImageFormat format)
{
using (var streamOriginal = new MemoryStream(originalBytes))
using (var imgOriginal = Image.FromStream(streamOriginal))
{
//get original width and height of the incoming image
var originalWidth = imgOriginal.Width; // 1000
var originalHeight = imgOriginal.Height; // 800
//get the percentage difference in size of the dimension that will change the least
var percWidth = ((float)size.Width / (float)originalWidth); // 0.2
var percHeight = ((float)size.Height / (float)originalHeight); // 0.25
var percentage = Math.Max(percHeight, percWidth); // 0.25
//get the ideal width and height for the resize (to the next whole number)
var width = (int)Math.Max(originalWidth * percentage, size.Width); // 250
var height = (int)Math.Max(originalHeight * percentage, size.Height); // 200
//actually resize it
using (var resizedBmp = new Bitmap(width, height))
{
using (var graphics = Graphics.FromImage((Image)resizedBmp))
{
graphics.InterpolationMode = InterpolationMode.Default;
graphics.DrawImage(imgOriginal, 0, 0, width, height);
}
}
}
}
Cropping an image
After the last step you are left with an image that is 250px x 200px. As you will notice that is still an aspect ratio of 5:4 so it does not look squashed. However this still isn't the right size so you will now need to crop it.
You are now intending to cut off the excess from both sides to reduce the width whilst leaving the height the same. This will leave you with a 200px x 200px image. Code on:
private byte[] GetCroppedImage(byte[] originalBytes, Size size, ImageFormat format)
{
using (var streamOriginal = new MemoryStream(originalBytes))
using (var imgOriginal = Image.FromStream(streamOriginal))
{
//get original width and height of the incoming image
var originalWidth = imgOriginal.Width; // 1000
var originalHeight = imgOriginal.Height; // 800
//get the percentage difference in size of the dimension that will change the least
var percWidth = ((float)size.Width / (float)originalWidth); // 0.2
var percHeight = ((float)size.Height / (float)originalHeight); // 0.25
var percentage = Math.Max(percHeight, percWidth); // 0.25
//get the ideal width and height for the resize (to the next whole number)
var width = (int)Math.Max(originalWidth * percentage, size.Width); // 250
var height = (int)Math.Max(originalHeight * percentage, size.Height); // 200
//actually resize it
using (var resizedBmp = new Bitmap(width, height))
{
using (var graphics = Graphics.FromImage((Image)resizedBmp))
{
graphics.InterpolationMode = InterpolationMode.Default;
graphics.DrawImage(imgOriginal, 0, 0, width, height);
}
//work out the coordinates of the top left pixel for cropping
var x = (width - size.Width) / 2; // 25
var y = (height - size.Height) / 2; // 0
//create the cropping rectangle
var rectangle = new Rectangle(x, y, size.Width, size.Height); // 25, 0, 200, 200
//crop
using (var croppedBmp = resizedBmp.Clone(rectangle, resizedBmp.PixelFormat))
{
}
}
}
}
Compressing the image
You now have the image to the correct size but for the web it is not really optimised.
You now want to compress the image down from the current 4 bytes per pixel (32bit) image, which would be ~156KB (for a 200x200 image!). Time to compress:
private byte[] GetCroppedImage(byte[] originalBytes, Size size, ImageFormat format)
{
using (var streamOriginal = new MemoryStream(originalBytes))
using (var imgOriginal = Image.FromStream(streamOriginal))
{
//get original width and height of the incoming image
var originalWidth = imgOriginal.Width; // 1000
var originalHeight = imgOriginal.Height; // 800
//get the percentage difference in size of the dimension that will change the least
var percWidth = ((float)size.Width / (float)originalWidth); // 0.2
var percHeight = ((float)size.Height / (float)originalHeight); // 0.25
var percentage = Math.Max(percHeight, percWidth); // 0.25
//get the ideal width and height for the resize (to the next whole number)
var width = (int)Math.Max(originalWidth * percentage, size.Width); // 250
var height = (int)Math.Max(originalHeight * percentage, size.Height); // 200
//actually resize it
using (var resizedBmp = new Bitmap(width, height))
{
using (var graphics = Graphics.FromImage((Image)resizedBmp))
{
graphics.InterpolationMode = InterpolationMode.Default;
graphics.DrawImage(imgOriginal, 0, 0, width, height);
}
//work out the coordinates of the top left pixel for cropping
var x = (width - size.Width) / 2; // 25
var y = (height - size.Height) / 2; // 0
//create the cropping rectangle
var rectangle = new Rectangle(x, y, size.Width, size.Height); // 25, 0, 200, 200
//crop
using (var croppedBmp = resizedBmp.Clone(rectangle, resizedBmp.PixelFormat))
using (var ms = new MemoryStream())
{
//get the codec needed
var imgCodec = ImageCodecInfo.GetImageEncoders().First(c => c.FormatID == format.Guid);
//make a paramater to adjust quality
var codecParams = new EncoderParameters(1);
//reduce to quality of 80 (from range of 0 (max compression) to 100 (no compression))
codecParams.Param[0] = new EncoderParameter(Encoder.Quality, 80L);
//save to the memorystream - convert it to an array and send it back as a byte[]
croppedBmp.Save(ms, imgCodec, codecParams);
return ms.ToArray();
}
}
}
}
There you go. Your massive image is looking good to the correct size whilst being a much leaner version of former self. Congratulations!
Update
In line 24, you can see I have set the InterpoltionMode of the graphics object to InterpolationMode.HighQualityBicubic before doing the initial resize. I previously had this set to Default but I noticed that straight diagonal lines in resized images were coming out jagged and they didn't look pretty.
HighQualityBicubic is meant to be "the best" form of
interpolation according to Microsoft so I sided with that. I can see a big improvement in the quality so I would recommend it too.