How to resize an image an image in C# to a certain hard-disk size, like 2MiB? Is there a better way than trial and error (even if it's approximate, of course).
Any particular keywords to search for when trying to find the solution on the web?
Convert, Reduce (Iterative, In Memory) & Download (MVC)
public ActionResult ReduceFileSize(string ImageURL, long MAX_PHOTO_SIZE) //KB
{
var photo = Server.MapPath("~/" + ImageURL); //Files/somefiles/2018/DOC_82401583cb534b95a10252d29a1eb4ee_1.jpg
var photoName = Path.GetFileNameWithoutExtension(photo);
var fi = new FileInfo(photo);
//const long MAX_PHOTO_SIZE = 100; //KB //109600;
var MAX_PHOTO_SIZE_BYTES = (MAX_PHOTO_SIZE * 1000);
if (fi.Length > MAX_PHOTO_SIZE_BYTES)
{
using (var image = Image.FromFile(photo))
{
using (var mstream = DownscaleImage(image, MAX_PHOTO_SIZE_BYTES))
{
//Convert the memorystream to an array of bytes.
byte[] byteArray = mstream.ToArray();
//Clean up the memory stream
mstream.Flush();
mstream.Close();
// Clear all content output from the buffer stream
Response.Clear();
// Add a HTTP header to the output stream that specifies the default filename
// for the browser's download dialog
Response.AddHeader("Content-Disposition", "attachment; filename=" + fi.Name);
// Add a HTTP header to the output stream that contains the
// content length(File Size). This lets the browser know how much data is being transfered
Response.AddHeader("Content-Length", byteArray.Length.ToString());
// Set the HTTP MIME type of the output stream
Response.ContentType = "application/octet-stream";
// Write the data out to the client.
Response.BinaryWrite(byteArray);
}
}
}
else
{
return null;
}
return null;
}
private static MemoryStream DownscaleImage(Image photo, long MAX_PHOTO_SIZE_BYTES)
{
MemoryStream resizedPhotoStream = new MemoryStream();
long resizedSize = 0;
var quality = 93;
//long lastSizeDifference = 0;
do
{
resizedPhotoStream.SetLength(0);
EncoderParameters eps = new EncoderParameters(1);
eps.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, (long)quality);
ImageCodecInfo ici = GetEncoderInfo("image/jpeg");
photo.Save(resizedPhotoStream, ici, eps);
resizedSize = resizedPhotoStream.Length;
//long sizeDifference = resizedSize - MAX_PHOTO_SIZE;
//Console.WriteLine(resizedSize + "(" + sizeDifference + " " + (lastSizeDifference - sizeDifference) + ")");
//lastSizeDifference = sizeDifference;
quality--;
} while (resizedSize > MAX_PHOTO_SIZE_BYTES);
resizedPhotoStream.Seek(0, SeekOrigin.Begin);
return resizedPhotoStream;
}
private static ImageCodecInfo GetEncoderInfo(String mimeType)
{
int j;
ImageCodecInfo[] encoders;
encoders = ImageCodecInfo.GetImageEncoders();
for (j = 0; j < encoders.Length; ++j)
{
if (encoders[j].MimeType == mimeType)
return encoders[j];
}
return null;
}
I achieved this by reducing the quality until I reached my desired size.
NB: Requires you to add the System.Drawing reference.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
namespace PhotoShrinker
{
class Program
{
/// <summary>
/// Max photo size in bytes
/// </summary>
const long MAX_PHOTO_SIZE = 409600;
static void Main(string[] args)
{
var photos = Directory.EnumerateFiles(Directory.GetCurrentDirectory(), "*.jpg");
foreach (var photo in photos)
{
var photoName = Path.GetFileNameWithoutExtension(photo);
var fi = new FileInfo(photo);
Console.WriteLine("Photo: " + photo);
Console.WriteLine(fi.Length);
if (fi.Length > MAX_PHOTO_SIZE)
{
using (var image = Image.FromFile(photo))
{
using (var stream = DownscaleImage(image))
{
using (var file = File.Create(photoName + "-smaller.jpg"))
{
stream.CopyTo(file);
}
}
}
Console.WriteLine("File resized.");
}
Console.WriteLine("Done.")
Console.ReadLine();
}
}
private static MemoryStream DownscaleImage(Image photo)
{
MemoryStream resizedPhotoStream = new MemoryStream();
long resizedSize = 0;
var quality = 93;
//long lastSizeDifference = 0;
do
{
resizedPhotoStream.SetLength(0);
EncoderParameters eps = new EncoderParameters(1);
eps.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, (long)quality);
ImageCodecInfo ici = GetEncoderInfo("image/jpeg");
photo.Save(resizedPhotoStream, ici, eps);
resizedSize = resizedPhotoStream.Length;
//long sizeDifference = resizedSize - MAX_PHOTO_SIZE;
//Console.WriteLine(resizedSize + "(" + sizeDifference + " " + (lastSizeDifference - sizeDifference) + ")");
//lastSizeDifference = sizeDifference;
quality--;
} while (resizedSize > MAX_PHOTO_SIZE);
resizedPhotoStream.Seek(0, SeekOrigin.Begin);
return resizedPhotoStream;
}
private static ImageCodecInfo GetEncoderInfo(String mimeType)
{
int j;
ImageCodecInfo[] encoders;
encoders = ImageCodecInfo.GetImageEncoders();
for (j = 0; j < encoders.Length; ++j)
{
if (encoders[j].MimeType == mimeType)
return encoders[j];
}
return null;
}
}
}
You can calculate an approximate information level for the image by taking the original image size divided by the number of pixels:
info = fileSize / (width * height);
I have an image that is 369636 bytes and 1200x800 pixels, so it uses ~0.385 bytes per pixel.
I have a smaller version that is 101111 bytes and 600x400 pixels, so it uses ~0.4213 bytes per pixel.
When you shrink an image you will see that it generally will contain slightly more information per pixel, in this case about 9% more. Depending on your type of images and how much you shrink them, you should be able to calculate an average for how much the information/pixel ration increases (c), so that you can calculate an approximate file size:
newFileSize = (fileSize / (width * height)) * (newWidth * newHeight) * c
From this you can extract a formula for how large you have to make an image to reach a specific file size:
newWidth * newHeight = (newFileSize / fileSize) * (width * height) / c
This will get you pretty close to the desired file size. If you want to get closer you can resize the image to the calculated size, compress it and calculate a new bytes per pixel value from the file size that you got.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With