Basic Image Manipulation in C#


Image Manipulation is a powerful utility provided by .NET that can make powerful applications or tools depending what you are building. We have worked in both patterns building developer tools using basic image manipulation and production applications that required thumbnail and compression algorithms before we served images to the user. In this article you will learn the fundamental algorithms and useful implementations to accomplish your image manipulation needs in C#

Recently we needed to handle resizing and compressing images from high resolution versions to serve smaller files for a website project. We are going to go over techniques to solve the following problems:

  • Converting Image Type
  • Resizing
  • Compression

With the library provided these techniques turn into simple file I/O calls instead of manually manipulating the files

Converting Images

Before we can start converting an image from a png to a jpeg or any other combination we need to get the file into something our code can understand. Every file that you read can be translated into a byte array. This is the raw data that makes up the file that we need to perform our manipulations. 

To convert an image to a Jpeg you will need to complete the following workflow:

  1. Load image using Image.FromStream()
  2. Save the data to a new MemoryStream and specify the format using ImageFormat.Jpeg
  3. Return the resulting byte array
public byte[] AsJpeg(byte[] data)
{
    using (var inStream = new MemoryStream(data))
    using (var outStream = new MemoryStream())
    {
        var imageStream = Image.FromStream(inStream);
        imageStream.Save(outStream, ImageFormat.Jpeg);                
        return outStream.ToArray();
    }
}

The ImageFormat class gives you the power to convert several different types of images. Below is an excerpt of the class definition to demonstrate the power of this code

namespace System.Drawing.Imaging
{
    public sealed class ImageFormat
    {
        public static ImageFormat MemoryBmp { get; }
        public static ImageFormat Bmp { get; }
        public static ImageFormat Emf { get; }
        public static ImageFormat Wmf { get; }
        public static ImageFormat Gif { get; }
        public static ImageFormat Jpeg { get; }
        public static ImageFormat Png { get; }
        public static ImageFormat Tiff { get; }
        public static ImageFormat Exif { get; }
        public static ImageFormat Icon { get; }
    }
}

Resizing Images

Resizing image is not that difficult, you may need to do some math out to keep your aspect ratio but other than that you perform your calculation and plug in the numbers to the apprioprate methods.

If you are working with a Square Image resizing becomes very trivial as you plug in the new width & height and you are done. No distortion of your image and you have a resized image. Unfortunately most images we deal with are not a square and we need to handle preserving the aspect ratio as we resize the image. In our example we are going to use the width as the master of the ratio and the height will always be a calculated value from the width.

Given the following equation

(width / height) = (newWidth/newHeight)

we know that newHeight is always equal to -> (newWidth * height) / width. This translates very nicely into code.

var height = (width * image.Height) / image.Width;

Once we have the calculated height we can plug this into the GetThumbnailImage() which is a special method from the Image library that helps us resize an image given a new width and height in pixels. We perform this operation by using an ImageStream

var thumbnail = image.GetThumbnailImage(width, height, null, IntPtr.Zero);

Putting all of this together we need to complet the following workflow:

  1. Create Image from the raw data
  2. Calculate the height based off of the width
  3. Create a new Thumbnail Image
  4. Save the Thumbnail Image into a stream
  5. Return the raw byte array data of the thumbnail image stream
public byte[] Resize(byte[] data, int width)
{
    using (var stream = new MemoryStream(data))
    {
        var image = Image.FromStream(stream);

        var height = (width * image.Height) / image.Width;
        var thumbnail = image.GetThumbnailImage(width, height, null, IntPtr.Zero);

        using (var thumbnailStream = new MemoryStream())
        {
            thumbnail.Save(thumbnailStream, ImageFormat.Jpeg);
            return thumbnailStream.ToArray();
        }
    }
}

Jpeg Image Compression

Compressing an image is a little bit more complicated than the other 2 techniques we domonstrated in this article. In my example we are going to be compressing a Jpeg image to try and trim down the size of our file. 

We start by building our ImageCodecInfousing this helper method that we have to write:

private ImageCodecInfo GetEncoder(ImageFormat format)
{
    var codecs = ImageCodecInfo.GetImageDecoders();
    foreach (var codec in codecs)
    {
        if (codec.FormatID == format.Guid)
        {
            return codec;
        }
    }

    return null;
}

To properly retrieve a jpeg encoder just call the method and pass in the correct ImageFormat

var jpgEncoder = GetEncoder(ImageFormat.Jpeg);

Once we have the encoder we can perform the compression. We do this by creating an EncoderParametersobject and passing the compression value. This variable is then passed to the save method when we save the image to our Output Stream.

var qualityEncoder = Encoder.Quality;
var encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(qualityEncoder, 50L);
image.Save(outStream, jpgEncoder, encoderParameters);

Let's put it all together now with the complete AndCompressmethod:

public byte[] Compress(byte[] data, long value)
{
    var jpgEncoder = GetEncoder(ImageFormat.Jpeg);

    using (var inStream = new MemoryStream(data))
    using (var outStream = new MemoryStream())
    {
        var image = Image.FromStream(inStream);

        // if we aren't able to retrieve our encoder
        // we should just save the current image and
        // return to prevent any exceptions from happening
        if (jpgEncoder == null)
        {
            image.Save(outStream, ImageFormat.Jpeg);
        }
        else
        {
            var qualityEncoder = Encoder.Quality;
            var encoderParameters = new EncoderParameters(1);
            encoderParameters.Param[0] = new EncoderParameter(qualityEncoder, 50L);
            image.Save(outStream, jpgEncoder, encoderParameters);
        }

        return outStream.ToArray();
    }
}

 


Share

Tags