This is functional code to reduce an image down to a specified smaller size. But it has several things that are not good:
- it's slow
- it can do several iterations before getting the scaled image
- each time it has to determine the size it has to load the entire image into a memoryStream
I would like to improve it. Can there be some way to get a better initial estimate to preclude so many iterations? Am I going about this all wrong? My reasons for creating it is to accept any image of unknown size and scale it to a certain size. This will allow better planning for storage needs. When you scale to a certain height/width, the image size can vary far too much for our needs.
You will need a ref to System.Drawing.
//Scale down the image till it fits the given file size.
public static Image ScaleDownToKb(Image img, long targetKilobytes, long quality)
{
//DateTime start = DateTime.Now;
//DateTime end;
float h, w;
float halfFactor = 100; // halves itself each iteration
float testPerc = 100;
var direction = -1;
long lastSize = 0;
var iteration = 0;
var origH = img.Height;
var origW = img.Width;
// if already below target, just return the image
var size = GetImageFileSizeBytes(img, 250000, quality);
if (size < targetKilobytes * 1024)
{
//end = DateTime.Now;
//Console.WriteLine("================ DONE. ITERATIONS: " + iteration + " " + end.Subtract(start));
return img;
}
while (true)
{
iteration++;
halfFactor /= 2;
testPerc += halfFactor * direction;
h = origH * testPerc / 100;
w = origW * testPerc / 100;
var test = ScaleImage(img, (int)w, (int)h);
size = GetImageFileSizeBytes(test, 50000, quality);
var byteTarg = targetKilobytes * 1024;
//Console.WriteLine(iteration + ": " + halfFactor + "% (" + testPerc + ") " + size + " " + byteTarg);
if ((Math.Abs(byteTarg - size) / (double)byteTarg) < .1 || size == lastSize || iteration > 15 /* safety measure */)
{
//end = DateTime.Now;
//Console.WriteLine("================ DONE. ITERATIONS: " + iteration + " " + end.Subtract(start));
return test;
}
if (size > targetKilobytes * 1024)
{
direction = -1;
}
else
{
direction = 1;
}
lastSize = size;
}
}
public static long GetImageFileSizeBytes(Image image, int estimatedSize, long quality)
{
long jpegByteSize;
using (var ms = new MemoryStream(estimatedSize))
{
SaveJpeg(image, ms, quality);
jpegByteSize = ms.Length;
}
return jpegByteSize;
}
public static void SaveJpeg(Image image, MemoryStream ms, long quality)
{
((Bitmap)image).Save(ms, FindEncoder(ImageFormat.Jpeg), GetEncoderParams(quality));
}
public static void SaveJpeg(Image image, string filename, long quality)
{
((Bitmap)image).Save(filename, FindEncoder(ImageFormat.Jpeg), GetEncoderParams(quality));
}
public static ImageCodecInfo FindEncoder(ImageFormat format)
{
if (format == null)
throw new ArgumentNullException("format");
foreach (ImageCodecInfo codec in ImageCodecInfo.GetImageEncoders())
{
if (codec.FormatID.Equals(format.Guid))
{
return codec;
}
}
return null;
}
public static EncoderParameters GetEncoderParams(long quality)
{
System.Drawing.Imaging.Encoder encoder = System.Drawing.Imaging.Encoder.Quality;
//Encoder encoder = new Encoder(ImageFormat.Jpeg.Guid);
EncoderParameters eparams = new EncoderParameters(1);
EncoderParameter eparam = new EncoderParameter(encoder, quality);
eparams.Param[0] = eparam;
return eparams;
}
//Scale an image to a given width and height.
public static Image ScaleImage(Image img, int outW, int outH)
{
Bitmap outImg = new Bitmap(outW, outH, img.PixelFormat);
outImg.SetResolution(img.HorizontalResolution, img.VerticalResolution);
Graphics graphics = Graphics.FromImage(outImg);
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.DrawImage(img, new Rectangle(0, 0, outW, outH), new Rectangle(0, 0, img.Width, img.Height), GraphicsUnit.Pixel);
graphics.Dispose();
return outImg;
}
Calling this will create a 2nd image that is close in size to the requested value:
var image = Image.FromFile(@"C:\Temp\test.jpg");
var scaled = ScaleDownToKb(image, 250, 80);
SaveJpeg(scaled, @"C:\Temp\test_REDUCED.jpg", 80);
For this specific example:
- original file size: 628 kB
- requested file size: 250 kB
- scaled file size: 238 kB