Asynchronous resize of images (async http handler) with default sitecore image

Images can usually take up most of your page load time. In our project we were loading product images from the servers, and resizing them before serving them up on the site. To improve performance, we decided to try and do the resizing and image loading there after asynchoronously.
So we created an asynchronous http handler to perform this action.

We were resizing external images (not stored in sitecore), but we did want to load up a default image stored in sitecore if the image didn’t exist on the filesystem.
To achieve this, we passed in the url of the default image (language specific) as a querystring parameter to the http handler itself. In this case, it would check if the intended image existed on the filesystem, if not, it would return the resized default image in the response stream.

using Sitecore.Diagnostics;
using System;
using System.Drawing;
using System.IO;
using System.Net;
using System.Threading;
using System.Web;

namespace MySite.Customizations
{
    class ImageResizer : IHttpAsyncHandler
    {
        public bool IsReusable { get { return false; } }

        public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData)
        {
            ImageProcessor asyncImgProcessor = new ImageProcessor(cb, context, extraData);
            asyncImgProcessor.StartAsyncWork();
            return asyncImgProcessor;
        }

        public void EndProcessRequest(IAsyncResult result)
        {
        }

        public void ProcessRequest(HttpContext context)
        {
        }
    }

    class ImageProcessor : IAsyncResult
    {
        private bool _completed;
        private Object _state;
        private AsyncCallback _callback;
        private HttpContext _context;

        bool IAsyncResult.IsCompleted { get { return _completed; } }
        WaitHandle IAsyncResult.AsyncWaitHandle { get { return null; } }
        Object IAsyncResult.AsyncState { get { return _state; } }
        bool IAsyncResult.CompletedSynchronously { get { return false; } }

        public ImageProcessor(AsyncCallback callback, HttpContext context, Object state)
        {
            _callback = callback;
            _context = context;
            _state = state;
            _completed = false;
        }

        public void StartAsyncWork()
        {
            ThreadPool.QueueUserWorkItem(StartAsyncTask, null);
        }

        private void StartAsyncTask(Object workItemState)
        {
            if (_context.Request.QueryString["path"] != null)
            {
                try
                {
                    string imagePath = _context.Request.QueryString["path"];

                    if (string.IsNullOrWhiteSpace(imagePath))
                    {
                        EndAsyncTask();
                        return;
                    }

                    imagePath = HttpUtility.Decode(imagePath);
                    Image image = null;
                    bool isFileSystem = false;

                    string fileSystemImagePath;

                    if (!ImageHelper.IsValidProductFileSystemImage(imagePath, out fileSystemImagePath))
                    {
                        imagePath = _context.Request.Url.Scheme + "://" + _context.Request.Url.Host
                            + HttpUtility.Decode(_context.Request.QueryString["defaultimage"]);
                    }
                    else
                    {
                        image = Image.FromFile(fileSystemImagePath);
                        isFileSystem = true;
                    }

                    int h = 0, w = 0;
                    if (_context.Request.QueryString["h"] != null)
                    {
                        h = Convert.ToInt32(string.IsNullOrWhiteSpace(_context.Request.QueryString["h"]) ? "0" : _context.Request.QueryString["h"]);
                    }
                    if (_context.Request.QueryString["w"] != null)
                    {
                        w = Convert.ToInt32(string.IsNullOrWhiteSpace(_context.Request.QueryString["w"]) ? "0" : _context.Request.QueryString["w"]);
                    }

                    try
                    {
                        if (!isFileSystem)
                        {
                            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(imagePath);
                            using (WebResponse response = request.GetResponse())
                            using (HttpWebResponse httpResponse = (HttpWebResponse)response)
                            {
                                if (httpResponse.StatusCode != HttpStatusCode.OK)
                                {
                                    EndAsyncTask();
                                    return;
                                }
                                using (Stream imageStream = response.GetResponseStream())
                                {
                                    if (imageStream == null)
                                    {
                                        EndAsyncTask();
                                        return;
                                    }

                                    image = Image.FromStream(imageStream);
                                }
                            }
                        }

                        byte[] imageBytes = ImageHelper.ResizeAndCropImage(image, w, h,
                            (_context.Request.QueryString["crop"] == null || _context.Request.QueryString["crop"] == "true"));

                        image.Dispose();

                        _context.Response.ContentType = "image/jpeg";
                        if (imageBytes != null)
                        {
                            _context.Response.BinaryWrite(imageBytes);
                        }
                        _context.Response.End();
                    }
                    catch (Exception ex)
                    {
                        Log.Error(ex.Message, ex);
                        EndAsyncTask();
                        return;
                    }
                }
                catch (Exception ex)
                {
                    Log.Error("Exception in fetching product image for resize: " + ex.StackTrace, ex, this);
                }
            }
            EndAsyncTask();
        }

        public void EndAsyncTask()
        {
            _completed = true;
            _callback(this);
        }
    }
}

In the above code, IsValidProductFileSystemImage – basically executes a System.IO.File.Exists call to check whether the image in the ‘path’ parameter exists.
Additionally, the code for ResizeAndCropImage can be found at: Resize Images with Crop and Quality Control.

Advertisements

Resize Images with Crop and Quality Control

There are plenty of snippets out there to resize images, and I went through a lot of them, while trying to implement this.
But a bunch of them gave me quality issues and some others came with other issues of their own.

So here’s the code we used to resize images, with the provision to adjust for quality (depending on how much you are willing to sacrifice for bandwidth!)

        internal static byte[] ResizeAndCropImage(System.Drawing.Image image, int requestedWidth, int requestedHeight, bool crop = true)
        {
            if (image == null) return null;

            int sourceWidth = image.Width;
            int sourceHeight = image.Height;

            if (sourceWidth == 0 || sourceHeight == 0) return null;

            // *************** Figure out resize % ***************

            double percentageResize = 1;

            if (requestedWidth == 0 && requestedHeight == 0)
            {
                percentageResize = 1;
            }
            else if (requestedWidth == 0 && requestedHeight > 0)
            {
                if (sourceHeight <= requestedHeight)
                {
                    percentageResize = 1;
                }
                else
                {
                    percentageResize = (double)requestedHeight / sourceHeight;
                }
            }
            else if (requestedWidth > 0 && requestedHeight == 0)
            {
                if (sourceWidth <= requestedWidth)
                {
                    percentageResize = 1;
                }
                else
                {
                    percentageResize = (double)requestedWidth / sourceWidth;
                }
            }
            else if (requestedWidth <= sourceWidth && requestedHeight <= sourceHeight)
            {
                percentageResize = crop ? Math.Max((double)requestedWidth / sourceWidth, (double)requestedHeight / sourceHeight)
                    : Math.Min((double)requestedWidth / sourceWidth, (double)requestedHeight / sourceHeight);
            }
            else if ((requestedWidth <= sourceWidth && requestedHeight > sourceHeight)
                || (requestedWidth > sourceWidth && requestedHeight <= sourceHeight)
                || (requestedWidth > sourceWidth && requestedHeight > sourceHeight))
            {
                percentageResize = 1;
            }

            int destHeight = (int)Math.Round(sourceHeight * percentageResize);
            int destWidth = (int)Math.Round(sourceWidth * percentageResize);

            using (Bitmap resizedBmp = new Bitmap(destWidth, destHeight))
            {
                // *************** Resize ***************

                using (Graphics graphics = Graphics.FromImage(resizedBmp))
                {
                    graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    graphics.DrawImage(image, 0, 0, destWidth, destHeight);
                }

                // *************** Figure out crop px ***************

                int cropX = crop && (destWidth > requestedWidth && requestedWidth > 0) ? (destWidth - requestedWidth) / 2 : 0;
                int cropY = crop && (destHeight > requestedHeight && requestedHeight > 0) ? (destHeight - requestedHeight) / 2 : 0;

                // *************** Crop ***************

                Rectangle rectangle = new Rectangle(cropX, cropY, destWidth - (cropX * 2), destHeight - (cropY * 2));

                using (Bitmap croppedBmp = resizedBmp.Clone(rectangle, resizedBmp.PixelFormat))
                using (MemoryStream ms = new MemoryStream())
                {
                    ImageCodecInfo imgCodec = ImageCodecInfo.GetImageEncoders().First(c => c.FormatID == image.RawFormat.Guid);
                    EncoderParameters codecParams = new EncoderParameters(1);

                    codecParams.Param[0] = new EncoderParameter(Encoder.Quality, 95L);
                    croppedBmp.Save(ms, imgCodec, codecParams);
                    return ms.ToArray();
                }
            }
        }

The quality can be controlled by passing in the % in the codec params above. You can see, we have it set to 95% here. You could ofcourse make this configurable and choose to pass in the % with the width and height.

To get the System.Drawing.Image object from a local file:

System.Drawing.Image image = System.Drawing.Image.FromFile("C:\...\myimage.jpg");

If you wanted to resize an image using a url (say for instance in your application you stored user uploaded images in Salesforce (Storing user profile image using Salesforce Connector with Sitecore) and wanted to resize this image to display on your site), then you could use the following to get the System.Drawing.Image object:

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(imagePath);
                            using (WebResponse response = request.GetResponse())
                            using (HttpWebResponse httpResponse = (HttpWebResponse)response)
                            {
                                if (httpResponse.StatusCode != HttpStatusCode.OK)
                                {
                                    EndAsyncTask();
                                    return;
                                }
                                using (Stream imageStream = response.GetResponseStream())
                                {
                                    if (imageStream == null)
                                    {
                                        EndAsyncTask();
                                        return;
                                    }

                                    image = Image.FromStream(imageStream);
                                }
                            }

Here are some sample outputs!

Original Image (768X768):
P1020131

300X?
Imageresizer
?X100
Imageresizer (1)
300X200
Imageresizer (3)
100X200
Imageresizer (2)

Sanitizing text with regular expressions

Strip out HTML Tags

        public static string StripHtml(string str)
        {
            return Regex.Replace(str, "<.*?>", string.Empty);
        }

Convert string to Alphanumeric text

        public static string ToAlphanumeric(this string param)
        {
            if (string.IsNullOrWhiteSpace(param)) return string.Empty;
            return (new Regex("[^a-zA-Z0-9]")).Replace(param, "");
        }

If you want to allow spaces, modify the regex to “[^a-zA-Z0-9 ]”