How to do on the fly image compression in C#?

c#, compression, image, performance

Solution

Of course you can compress images on the fly using C# and .NET.

Here is a function to do so. First it sets up an `Encoder` object of type jpg and adds one parameter for the new quailty to it. This is then used to write the image with its new quality to a `MemoryStream`.

Then an image created from that stream is drawn onto itself with the new dimensions..:

//..
using System.Drawing.Imaging;
using System.IO;
//..

private Image compressImage(string fileName,  int newWidth, int newHeight, 
                            int newQuality)   // set quality to 1-100, eg 50
{
    using (Image image = Image.FromFile(fileName))
    using (Image memImage= new Bitmap(image, newWidth, newHeight))  
    {
        ImageCodecInfo myImageCodecInfo;
        System.Drawing.Imaging.Encoder myEncoder;
        EncoderParameter myEncoderParameter;
        EncoderParameters myEncoderParameters;
        myImageCodecInfo = GetEncoderInfo("image/jpeg"); 
        myEncoder = System.Drawing.Imaging.Encoder.Quality;
        myEncoderParameters = new EncoderParameters(1);
        myEncoderParameter = new EncoderParameter(myEncoder, newQuality);
        myEncoderParameters.Param[0] = myEncoderParameter;

        MemoryStream memStream = new MemoryStream();
        memImage.Save(memStream, myImageCodecInfo, myEncoderParameters);
        Image newImage = Image.FromStream(memStream);
        ImageAttributes imageAttributes = new ImageAttributes();
        using (Graphics g = Graphics.FromImage(newImage))
        {
            g.InterpolationMode = 
              System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;  //**
            g.DrawImage(newImage,  new Rectangle(Point.Empty, newImage.Size), 0, 0, 
              newImage.Width, newImage.Height, GraphicsUnit.Pixel, imageAttributes);
        }
        return newImage;
    }
}

private static ImageCodecInfo GetEncoderInfo(String mimeType)
{
    ImageCodecInfo[] encoders;
    encoders = ImageCodecInfo.GetImageEncoders();
    foreach (ImageCodecInfo ici in encoders)
        if (ici.MimeType == mimeType) return ici;

    return null;
}

Setting the new dimensions is up to you. If you never want to change the dimensions, take out the parameters and set the values to the old dimensions in the code block If you only sometimes want to change you could pass them in as 0 or -1 and do the check inside..

Quality should be around 30-60%, depending on the motifs. Screenshots with text don't scale down well and need around 60-80% to look good and crispy.

This function returns a jpeg version of the file. If you want, you could create a different Encoder, but for scalable quality, jpeg usually is the best choice.

Obviously you could as well pass in an image instead of a filename or save the newImage to disk instead of returning it. (You should dispose of it in that case.)

Also: you could check the `memStream.Length` to see if the results are too big and adjust the quality..

Edit: Correction //**

Problem

I have a web application that allows users to upload images of various formats (PNG, JPEG, GIF). Unfortunately my users are not necessarily technical and end up uploading images that are of way too high quality (and size) than is required. Is there a way for me to compress these images when serving them? By compress I mean lossy compression, reducing the quality and not necessarily the size. Should I be storing the file format as well if different formats are compressed differently?

Original source