Introduction
To enable customers to upload images which exceed the maximum upload size given by the system, it became necessary to scale down large images until the given size limit was no longer exceeded. Since I did not find any articles about this problem, I decided to write one myself to maybe help others struggling with a similar problem.
Initialization
Before we can start the resizing process, we need to obtain the source path of the uploaded image, the destination path (which is very likely the same path) and the maximum allowed size given by your system. All these values are initialized in the constructor:
private int allowedFileSizeInByte;
private string sourcePath;
private string destinationPath;
public ImageResizer(int allowedSize, string sourcePath, string destinationPath)
{
allowedFileSizeInByte = allowedSize;
this.sourcePath = sourcePath;
this.destinationPath = destinationPath;
}
Scaling Images
Let's start with the function which does the scaling. I use a Bitmap as the source image and a scale factor which is applied to change the width and height of the image. Later, we will see how to calculate this scale factor. Since the following code is pretty straight forward, I won't go into further details:
public Bitmap ScaleImage(Bitmap image, double scale)
{
int newWidth = (int)(image.Width * scale);
int newHeight = (int)(image.Height * scale);
Bitmap result = new Bitmap(newWidth, newHeight, PixelFormat.Format24bppRgb);
result.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (Graphics g = Graphics.FromImage(result))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.CompositingQuality = CompositingQuality.HighQuality;
g.SmoothingMode = SmoothingMode.HighQuality;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.DrawImage(image, 0, 0, result.Width, result.Height);
}
return result;
}
Calculating the Scale Factor
The scale factor for the file size can be approximated by dividing the allowed file size by the file size of the image. This simply describes by which amount the file size has to decrease to reach the size limit. Since we generally don't know anything about the compression of the uploaded image, we cannot exactly predict by which factor we have to scale the width and height of our image. However, we know that the file size of an image will increment quadratically if we increase its width and height, since the file size is approximately calculated by multiplying width and height. Since we want to decrease the file size of the picture, we simply have to calculate the square root of the scale factor calculated above and use this value as a parameter for our ScaleImage
function. To clarify this process, take a look at the following function which will be called by the client to perform the resizing:
public void ScaleImage()
{
using (MemoryStream ms = new MemoryStream())
{
using (FileStream fs = new FileStream(sourcePath, FileMode.Open))
{
Bitmap bmp = (Bitmap)Image.FromStream(fs);
SaveTemporary(bmp, ms, 100);
while (ms.Length > allowedFileSizeInByte)
{
double scale = Math.Sqrt
((double)allowedFileSizeInByte / (double)ms.Length);
ms.SetLength(0);
bmp = ScaleImage(bmp, scale);
SaveTemporary(bmp, ms, 100);
}
if (bmp != null)
bmp.Dispose();
SaveImageToFile(ms);
}
}
}
As you can see, we use a while MemoryStream
to save the uploaded image in memory instead of the hard disk during the resizing process. The Bitmap
variable bmp
contains the image to be resized which is obtained through a FileStream
. Inside the while
loop, the image is resized using the scale factor and the new image is saved back into the MemoryStream
using the SaveTemporary
function we will see in a minute. This while
loop is applied until the size limit is not exceeded anymore. We need to iterate here, because our scale factor is really only an approximation and depending on the image it might not provide enough scaling when calling ScaleImage
for the first time.
Other Helper Functions
Finally, let's take a look at the helper functions used by the scaling process:
private void SaveTemporary(Bitmap bmp, MemoryStream ms, int quality)
{
EncoderParameter qualityParam = new EncoderParameter
(System.Drawing.Imaging.Encoder.Quality, quality);
var codec = GetImageCodecInfo();
var encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = qualityParam;
bmp.Save(ms, codec, encoderParams);
}
private void SaveImageToFile(MemoryStream ms)
{
byte[] data = ms.ToArray();
using (FileStream fs = new FileStream(destinationPath, FileMode.Create))
{
fs.Write(data, 0, data.Length);
}
}
private ImageCodecInfo GetImageCodecInfo()
{
FileInfo fi = new FileInfo(sourcePath);
switch (fi.Extension)
{
case ".bmp": return ImageCodecInfo.GetImageEncoders()[0];
case ".jpg":
case ".jpeg": return ImageCodecInfo.GetImageEncoders()[1];
case ".gif": return ImageCodecInfo.GetImageEncoders()[2];
case ".tiff": return ImageCodecInfo.GetImageEncoders()[3];
case ".png": return ImageCodecInfo.GetImageEncoders()[4];
default: return null;
}
}
The SaveTemporary
function is used to temporarily save the image into the provided MemoryStream
during each iteration. It uses .NET EncoderParameters
and ImageCodecInfo
to get high quality compression rates which are nearly as good as the compression rates provided by Image editing software. Using the EncoderParameters
and ImageCodecInfo
in the call to bmp.Save provides better result than using the save
function which takes a stream
and an ImageFormat
instance as its only parameters, so I advise you to use this version to get the best results. The SaveImageToFile
function is finally used to write the MemoryStream
, which contains the resized image, to its final location on the hard disk.
Summary
Hopefully you find this code useful and can apply it to solve similar problems. I appreciate any feedback tips and suggestions.