15 Şubat 2016 Pazartesi

Image Compression in C# for ASP.NET MVC

Previously, I spent some time optimizing the amount of image data being posted from the remote computer back to the server. The optimization only sends up the pixel area that has changed or partial images. The server then merges these changed pixels into the current full image to get an updated image. The optimization method is very efficient.
The monitoring station is HTML based and does not have the ability to combine images. So downloading a partial image will not work. We could implement the monitoring station using Silverlight, but that adds complexity that at this time is not necessary. Previously I had developed an image handler to serve images from the server.
This post will introduce image compression to minimize the amount of image data flowing from the server to the monitoring station. This image compression code can be added to the image handler. You can download the compression code and resulting images from the following link.
The Test Image
For a baseline image I wanted to have something containing elements that you would find on a desktop. The original image is 1680×1050. Here is the baseline image:
original
In this image there are three main areas of interest:
  • Text regions – If text becomes difficult to read, then the ability to remotely monitor desktops becomes useless.
  • Image regions – It is always interesting to see how the image compression does on images.
  • Smooth regions – Areas where there is little change are easy to compress. But as the compression gets too high, these areas will degrade.
Results
Let’s cut right to the chase…
image
By using compression we can reduce the number of bytes that are served up between the server and the web application. As you can see the original bitmap (sitting on the server) is very large (1680×1050 x 3 bytes per pixel (red,green,blue) = 5,292,000 = 5.04MB …. there is a mysterious 54 bytes in the above image).
Original Code
The original code used code like the following line to transform an uncompressed bitmap (BMP) to a PNG that could be sent over the wire.
1
2
3
4
5
6
7
8
9
10
static void DefaultCompressionPng(Image original)
{
    MemoryStream ms = new MemoryStream();
    original.Save(ms, ImageFormat.Png);
    Bitmap compressed = new Bitmap(ms);
    ms.Close();
    string fileOutPng = Path.Combine(ImagePath, "default.png");
    compressed.Save(fileOutPng, ImageFormat.Png);
}
This code encodes the BMP into a PNG file using a default compression / quality factor. The images that result are from this process were then 1.8MB (2.8 times smaller). The quality of the resulting image is very high. In fact I could not visually detect any difference.
This is a good amount of compression if you want to keep the quality as high as possible. However, if your application does not require extremely high quality images then you can do achieve better compression by sacrificing quality.
Controlling The Compression
The following code generated the JPEG files in the downloads and the results shown above:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static void VariousQuality(Image original)
{
    ImageCodecInfo jpgEncoder = null;
    ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
    foreach (ImageCodecInfo codec in codecs)
    {
        if (codec.FormatID == ImageFormat.Jpeg.Guid)
        {
            jpgEncoder = codec;
            break;
        }
    }
    if (jpgEncoder != null)
    {
        Encoder encoder = Encoder.Quality;
        EncoderParameters encoderParameters = new EncoderParameters(1);
        for (long quality = 10; quality <= 100; quality+=10)
        {
            EncoderParameter encoderParameter = new EncoderParameter(encoder, quality);
            encoderParameters.Param[0] = encoderParameter;
            string fileOut = Path.Combine(ImagePath, "quality_" + quality + ".jpeg");
            FileStream ms = new FileStream(fileOut, FileMode.Create, FileAccess.Write);
            original.Save(ms, jpgEncoder, encoderParameters);
            ms.Flush();
            ms.Close();
        }
    }
}
The above code varies the quality from 10 (lowest) to 100 (highest). Selecting the appropriate amount of compression depends upon your application. For the remote desktop viewer a quality factor of 50 is acceptable. Quality factors less than 50 tended to have distortion and noise in the flat areas. Text could still be read at quality factors down to 10.
By using a quality factor of 50 the server is now serving only 0.21MB a savings of 23.66 over the original BMP and 8.45 over the PNG generated in the original code. Put another way, the compressed images will download 8.45 (or 23.66) times faster than the original PNG (BMP). This is a huge increase in performance.
Summary
As with most design choices, there tends to be tradeoffs. When serving up images from your ASP.NET server you can trade image quality for performance. How much quality you can trade will depend upon your application requirements.