find a location for property in a new city

Thursday, 19 May 2011

Image resizing, cropping and compression using .NET

If you need to resize an image or crop an image or compress an image or even do all three you will find you can do all of those things using the .NET framework's System.Drawing classes. In this tutorial I will talk you through the steps required to do all three to transform an large image to an image fit for your website.

This is the example scenario. You have been asked to make a page that enables a user to upload any image they want so that it can be manipulated to ensure it is 200px x 200px and compressed to a smaller file size. I won't talk you though how to make the uploading part as there are a plethora of blogs discussing that already. What I will discuss is how to resize crop and compress that image.

Say you have an image that is 1000px x 800px. There are 3 main steps to converting it to a compressed 200px x 200px image:

Resizing an image

To resize it the key is that it needs to keep the same aspect ratio whilst being a lot smaller.

So the expected outcome of this stage will be that the shortest of the two dimensions will be 200px, the other will be larger and the aspect ratio will remain the same. Begin code:
private byte[] GetCroppedImage(byte[] originalBytes, Size size, ImageFormat format)
{
    using (var streamOriginal = new MemoryStream(originalBytes))
    using (var imgOriginal = Image.FromStream(streamOriginal))
    {
        //get original width and height of the incoming image
        var originalWidth = imgOriginal.Width; // 1000
        var originalHeight = imgOriginal.Height; // 800

        //get the percentage difference in size of the dimension that will change the least
        var percWidth = ((float)size.Width / (float)originalWidth); // 0.2
        var percHeight = ((float)size.Height / (float)originalHeight); // 0.25
        var percentage = Math.Max(percHeight, percWidth); // 0.25

        //get the ideal width and height for the resize (to the next whole number)
        var width = (int)Math.Max(originalWidth * percentage, size.Width); // 250
        var height = (int)Math.Max(originalHeight * percentage, size.Height); // 200

        //actually resize it
        using (var resizedBmp = new Bitmap(width, height))
        {
            using (var graphics = Graphics.FromImage((Image)resizedBmp))
            {
                graphics.InterpolationMode = InterpolationMode.Default;
                graphics.DrawImage(imgOriginal, 0, 0, width, height);
            }
        }
    }
}

Cropping an image

After the last step you are left with an image that is 250px x 200px. As you will notice that is still an aspect ratio of 5:4 so it does not look squashed. However this still isn't the right size so you will now need to crop it.

You are now intending to cut off the excess from both sides to reduce the width whilst leaving the height the same. This will leave you with a 200px x 200px image. Code on:
private byte[] GetCroppedImage(byte[] originalBytes, Size size, ImageFormat format)
{
    using (var streamOriginal = new MemoryStream(originalBytes))
    using (var imgOriginal = Image.FromStream(streamOriginal))
    {
        //get original width and height of the incoming image
        var originalWidth = imgOriginal.Width; // 1000
        var originalHeight = imgOriginal.Height; // 800

        //get the percentage difference in size of the dimension that will change the least
        var percWidth = ((float)size.Width / (float)originalWidth); // 0.2
        var percHeight = ((float)size.Height / (float)originalHeight); // 0.25
        var percentage = Math.Max(percHeight, percWidth); // 0.25

        //get the ideal width and height for the resize (to the next whole number)
        var width = (int)Math.Max(originalWidth * percentage, size.Width); // 250
        var height = (int)Math.Max(originalHeight * percentage, size.Height); // 200

        //actually resize it
        using (var resizedBmp = new Bitmap(width, height))
        {
            using (var graphics = Graphics.FromImage((Image)resizedBmp))
            {
                graphics.InterpolationMode = InterpolationMode.Default;
                graphics.DrawImage(imgOriginal, 0, 0, width, height);
            }

            //work out the coordinates of the top left pixel for cropping
            var x = (width - size.Width) / 2; // 25
            var y = (height - size.Height) / 2; // 0

            //create the cropping rectangle
            var rectangle = new Rectangle(x, y, size.Width, size.Height); // 25, 0, 200, 200

            //crop
            using (var croppedBmp = resizedBmp.Clone(rectangle, resizedBmp.PixelFormat))
            {
            }
        }
    }
}

Compressing the image

You now have the image to the correct size but for the web it is not really optimised.

You now want to compress the image down from the current 4 bytes per pixel (32bit) image, which would be ~156KB (for a 200x200 image!). Time to compress:
private byte[] GetCroppedImage(byte[] originalBytes, Size size, ImageFormat format)
{
    using (var streamOriginal = new MemoryStream(originalBytes))
    using (var imgOriginal = Image.FromStream(streamOriginal))
    {
        //get original width and height of the incoming image
        var originalWidth = imgOriginal.Width; // 1000
        var originalHeight = imgOriginal.Height; // 800

        //get the percentage difference in size of the dimension that will change the least
        var percWidth = ((float)size.Width / (float)originalWidth); // 0.2
        var percHeight = ((float)size.Height / (float)originalHeight); // 0.25
        var percentage = Math.Max(percHeight, percWidth); // 0.25

        //get the ideal width and height for the resize (to the next whole number)
        var width = (int)Math.Max(originalWidth * percentage, size.Width); // 250
        var height = (int)Math.Max(originalHeight * percentage, size.Height); // 200

        //actually resize it
        using (var resizedBmp = new Bitmap(width, height))
        {
            using (var graphics = Graphics.FromImage((Image)resizedBmp))
            {
                graphics.InterpolationMode = InterpolationMode.Default;
                graphics.DrawImage(imgOriginal, 0, 0, width, height);
            }

            //work out the coordinates of the top left pixel for cropping
            var x = (width - size.Width) / 2; // 25
            var y = (height - size.Height) / 2; // 0

            //create the cropping rectangle
            var rectangle = new Rectangle(x, y, size.Width, size.Height); // 25, 0, 200, 200

            //crop
            using (var croppedBmp = resizedBmp.Clone(rectangle, resizedBmp.PixelFormat))
            using (var ms = new MemoryStream())
            {
                //get the codec needed
                var imgCodec = ImageCodecInfo.GetImageEncoders().First(c => c.FormatID == format.Guid);

                //make a paramater to adjust quality
                var codecParams = new EncoderParameters(1);

                //reduce to quality of 80 (from range of 0 (max compression) to 100 (no compression))
                codecParams.Param[0] = new EncoderParameter(Encoder.Quality, 80L);

                //save to the memorystream - convert it to an array and send it back as a byte[]
                croppedBmp.Save(ms, imgCodec, codecParams);
                return ms.ToArray();
            }
        }
    }
}

There you go. Your massive image is looking good to the correct size whilst being a much leaner version of former self. Congratulations!

Update

In line 24, you can see I have set the InterpoltionMode of the graphics object to InterpolationMode.HighQualityBicubic before doing the initial resize. I previously had this set to Default but I noticed that straight diagonal lines in resized images were coming out jagged and they didn't look pretty. HighQualityBicubic is meant to be "the best" form of interpolation according to Microsoft so I sided with that. I can see a big improvement in the quality so I would recommend it too.

Follow britishdev on Twitter

26 comments:

  1. A quick warning to everybody thinking about using System.Drawing (or GDI+) in an ASP.NET environment. It's not supported by Microsoft and MSDN (as of recently) clearly states that you may experience memory leaks.

    Clearly GDI+ is a legacy API and one should opt for image processing using WPF in general.

    Here's a quote from http://msdn.microsoft.com/en-us/library/system.drawing.aspx:

    Classes within the System.Drawing namespace are not supported for use within a Windows or ASP.NET service. Attempting to use these classes from within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions. For a supported alternative, see Windows Imaging Components.

    ReplyDelete
  2. Cool. I've never heard of WIC so it sounds interesting and I'll have a look.

    Just to make clear the code above isn't thrown together. It evolved with time because as you suggested it is too easy to mistakenly create performance issues when using GDI+. Just see how many times I've written 'using' above!

    Anyway, please be assured the above code is highly optimised and tested and has run successfully in a high traffic production environment for a few months now.

    ReplyDelete
  3. Excellent! Thanks for sharing Colin!

    ReplyDelete
  4. @Anders Borum: Actually, despite what MSDN (incorrectly) states, WIC is NOT supported for use within an ASP.NET service. As WPF wraps WIC, it is subject to the same problems WIC faces. WIC is also only available on Server 2008 and higher, instability will result on OSes where WIC MTA threading isn't available.

    You can read it for yourself (Note #2) at the end of this MS blog: http://weblogs.asp.net/bleroy/archive/2009/12/10/resizing-images-from-the-server-using-wpf-wic-instead-of-gdi.aspx

    Also, using WPF or WIC will require you place the entire ASP.NET app in Full Trust mode, destroying any sandbox-level security you would have had. GDI works properly even on low-trust scenarios.


    --------

    To all readers.

    Take a look at http://imageresizing.net/

    It's free, it's open, and it's reliable. It's fast, well tested, and refined. It even has a NuGet package. And it is thoroughly documented. What are you waiting for?


    BUT - If you don't like using existing code, well tested libraries, or simply enjoy the Windows API so much you HAVE to write your own image resizing module, please read the list of pitfalls you need to avoid (these were compiled over many years and many versions of the aforementioned library).

    http://nathanaeljones.com/163/20-image-resizing-pitfalls/

    Hope this helps!
    Nathanael Jones

    ReplyDelete
  5. Nice one.....
    i have been looking for this thx dude

    ReplyDelete
  6. Great Work... Thanks...very very much

    ReplyDelete
  7. Thanks. It works just great!

    ReplyDelete
  8. This is what I need! It works great for me. Thank you!
    instagram search

    ReplyDelete
  9. Motifz Designer Lawn. 1, 2 & 3 Piece Unstitched Premium Embroidered Lawn 2019, Premium embroidered Lawn, Premium lawn, Premium lawn 2019, Motifz, Premium lawn in Pakistan Summer collection. Shipping worldwide. Stitching option available.

    ReplyDelete
  10. It is really nice to get the instructions of Image functionalities using the .NET.
    Do you know? Data breaches on personal computers cause harms to individuals identity, to prevent such attacks use McAfee antivirus and if you want to make sure it is worth it, then take the McAfee Free Trial

    ReplyDelete
  11. I want to protect my important data from virus attacks, so I prefer the McAfee antivirus program to keep protected my important information for various malicious attacks. I have tried my possible efforts to install it, but I failed to do it.
    mcafee.com/activate
    www.mcafee.com/activate
    mcafee activate

    ReplyDelete
  12. McAfee antivirus allows you to protect your computer from suspicious malwares and keep your files safe. Visit mcafee.com/activate
    and activate your antivirus.
    mcafee.com/activate
    www.mcafee.com
    www.mcafee.com/activate

    ReplyDelete
  13. Really very happy to say, your post is very interesting to read. You’re doing a great job. Keep it up, and also visit our site to get latest news about Marvel Studios and their actress.

    Iron Man Armour

    Avengers Endgame

    ReplyDelete
  14. We are Ring gear pinions, Rueda de corona y piñón gear manufacturers for application in agricultural tractors, Agri machines, automobiles, cars, buses and trucks and we also cater to export markets. We have state of the art manufacturing facility with more than 100 Gleason Machines imported for USA. for any query mail us on : goela.parts@gmail.com
    gearbox
    crown wheel pinion
    spiral bevel gears
    staight bevel gear
    transmission gear shafts
    differential gears

    ReplyDelete
  15. Goela Engineers is currently one of the leading Axle shafts manufacturers in India. We are manufacturing rear axle shafts, P.T.O. shafts, differential axle, propeller shafts for Commercial Vehicles and axle shafts and supply in India and overseas
    axle shafts
    rotavator gear shafts
    gearbox
    bevel gears
    face gears

    ReplyDelete
  16. Tractor Spare Parts Catalog online - Ford Tractor, Massey Ferguson Tractor, IMT Tractor, UTB Tractor, MTZ tractor, Eicher tractor, swaraj tractor, mahindra tractor
    tractor spare parts
    mtz tractor parts
    utb tractor parts
    massey ferguson tractor parts
    imt tractor parts
    ford tractor parts
    gearbox

    ReplyDelete
  17. Are you interested more to know about how to Update Garmin GPS easily? For detailed information please visit our website or call us.

    ReplyDelete
  18. it was a wonderful chance to visit this kind of site and I am happy to know. thank you so much for giving us a chance to have this opportunity.. videoproduktion

    ReplyDelete

  19. نقل عفش من الدمام الى الرياض نقل عفش من الدمام الى الرياض
    ارخص نقل عفش بمكة ارخص نقل عفش بمكة
    نقل عفش من جدة الى الاردن نقل عفش من جدة الى الاردن
    نقل عفش

    ReplyDelete
  20. For such purposes a good web connection is required with stable speed in order to buffer the video or the movie efficiently. Moreover, online movie download features are also available via proper subscription to such legit sites.
    ffmovie

    ReplyDelete
  21. Social media is an inexpensive way to get the word out about your movie and create a killer viral buzz online.123movie

    ReplyDelete
  22. Equipment from these kind of providers are inexpensive, usually is not commercial grade (but rather equipment meant for personal use) or that is under-powered for an outdoor movie setting. Their equipment is really ment for indoor movie events.

    123 movies

    ReplyDelete
  23. Every single person involved in the making of your film has a story and an angle you can pursue media.0123movies

    ReplyDelete
  24. And this ties in that a time will come when audiences will grow out of movies which rely on special effects and big star names to entertain and start turning there back on movies which have no originality or fail to deliver a different angle on an old storyline.

    0123movie

    ReplyDelete
  25. Halo, Bingun cari situs Agen judi online Taruhan terpercaya.? YUK GABUNG DI BINTANG BOLA YANG MENYEDIAKAN BERBAGAI JENIS TARUHAN
    -> SBO
    -> IBC
    -> JOKER
    -> TOGEL
    Bintang Bola juga memberikan bonus:
    -> Cash Back 10%
    BINTANG BOLA
    WhatsApp: +855-81-287-168
    Live Chat 24.JAM

    ReplyDelete