[MVC 3] MvcImage Project–How did i do Thumbnail Support?


Before reading the following, please read up on the tutorial posts listed here to get up-to speed on how i have accomplished Image handling using MVC so far, as i wont be going into detail on the code i have updated changed, only new code:

https://garfbradazweb.wordpress.com/2012/02/26/mvc-3-mvcimage-project-its-alive/

Also, before i continue, you can download and use the code on my MvcImage Codeplex Project home page.

So firstly i updated the AjaxSubmit and ImageLoad controllers. I have updated them to segregate the Image and Thumbnail byte arrays within the Session. The AjaxSubmit controller references my new Extension method Images.CreateThumbnailToByte to create the Thumbnail. I will discuss this in a moment. Remember to read up in the previous tutorials on how i use these withn a JQuery Image Preview Plugin.

So the AjaxSubmit Code is:

       [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult AjaxSubmit(int id)
        {

            Session["Image.ContentLength"] = Request.Files[0].ContentLength;
            Session["Image.ContentType"] = Request.Files[0].ContentType;
            byte[] b = new byte[Request.Files[0].ContentLength];
            Request.Files[0].InputStream.Read            (b, 0, Request.Files[0].ContentLength);
            Session["Image.ContentStream"] = b;

            if (id > 0)
            {
                byte[] thumbnail = Images.CreateThumbnailToByte(Request.Files[0].InputStream, 100, 100);

                Session["Thumbnail.ContentLength"] = thumbnail.Length;
                Session["Thumbnail.ContentType"] = Request.Files[0].ContentType;

                byte[] c = new byte[thumbnail.Length];
                Request.Files[0].InputStream.Read(c, 0,                 Request.Files[0].ContentLength);
                Session["Thumbnail.ContentStream"] = thumbnail;

            }

            return Content(Request.Files[0].ContentType + ";" + Request.Files[0].ContentLength);
        }

And the ImageLoad Controller Code – the id parameter is used to determine if to Respond with the Thumbnail binary or the Image binary.

       public ActionResult ImageLoad(int id)
        {
            if(id == 0)
            {
                byte[] b = (byte[])Session["Image.ContentStream"];
                int length = (int)Session["Image.ContentLength"];
                string type = (string)Session["Image.ContentType"];
                Response.Buffer = true;
                Response.Charset = "";
                Response.Cache.SetCacheability(HttpCacheability.NoCache);
                Response.ContentType = type;
                Response.BinaryWrite(b);
                Response.Flush();
                Response.End();
                Session["Image.ContentLength"] = null;
                Session["Image.ContentType"] = null;
                Session["Image.ContentStream"] = null;
            }

            //--The following is the Thumnbail id.

            if (id == 1)
            {
                byte[] b = (byte[])Session["Thumbnail.ContentStream"];
                int length = (int)Session["Thumbnail.ContentLength"];
                string type = (string)Session["Thumbnail.ContentType"];
                Response.Buffer = true;
                Response.Charset = "";
                Response.Cache.SetCacheability(HttpCacheability.NoCache);
                Response.ContentType = type;
                Response.BinaryWrite(b);
                Response.Flush();
                Response.End();
                Session["Thumbnail.ContentLength"] = null;
                Session["Thumbnail.ContentType"] = null;
                Session["Thumbnail.ContentStream"] = null;
            }
            return Content("");
        }

It is not the nicest code but it is there to show the logic behind what I’m trying to do. I’m following the same pattern as before but this time handling two byte arrays.

So lets resume talking about the following piece of code:

byte[] thumbnail = Images.CreateThumbnailToByte(Request.Files[0].InputStream, 100, 100);

So parameter 1 is the Stream of the Image, and the 2nd and 3rd parameters are the maximum height and width of the new resized Thumbnail Image. So using inspiration from Nathanael Jones (but not using it all as i didn’t have time), i maintain the aspect ratio of the image and create a new height and width by scaling the image properly. Then again based on advice from Nathanael’s article we create a new GDI drawing surface and create the Thumbnail Image. Then lastly we convert it back to a binary stream and return the byte array.

Here is the code:

        /// <summary>
        /// This method creates a Thumbnail Image and and scales it. /// It returns a byte array
        /// to be used.
        /// </summary>
        /// <param name="stream">Image Stream</param>
        /// <param name="maxHeight">Max Height (Used to scale the image</param>
        /// <param name="maxWidth">Max Width (Used to scale the image)</param>
        /// <returns>Scaled thumbail image byte array.</returns>
        public static byte[] CreateThumbnailToByte(Stream stream,         double maxHeight, double maxWidth)
        {
            int newHeight;
            int newWidth;
            double aspectRatio = 0;
            double boxRatio = 0;
            double scale = 0;

            Stream imageStream = new MemoryStream();
            Image originalImage;

            Streams.RewindStream(ref stream);
            using (originalImage = Image.FromStream(stream))
            {
                //--We need too maintain the aspect ratio on the image.
                aspectRatio = originalImage.Width / originalImage.Height;
                boxRatio = maxWidth / maxHeight;

                if (boxRatio > aspectRatio)
                {
                    scale = maxHeight / originalImage.Height;
                }
                else
                {
                    scale = maxWidth / originalImage.Width;
                }

                //--Scale the Original Images dimensions
                newHeight = (int)(originalImage.Height * scale);
                newWidth = (int)(originalImage.Width * scale);

                using (var bitmap = new Bitmap(newWidth, newHeight))

                //--Create a new GDI+ drawing surfance based on the original Image. 
 //--This methid allows us to alter
                //--it where necessary, based on advice from here. //--http://nathanaeljones.com/163/20-image-resizing -pitfalls/
                using (var graphics = Graphics.FromImage(bitmap))
                {
                    var rectangle = new Rectangle(0, 0, newWidth, newHeight);

                    graphics.CompositingQuality = CompositingQuality.HighQuality;
                    graphics.SmoothingMode = SmoothingMode.HighQuality;
                    graphics.InterpolationMode =                     InterpolationMode.HighQualityBicubic;
                    graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
                    graphics.DrawImage(originalImage, rectangle);

                    //--The same image to a new ImageStream so we can //--convert this to a byte array.
                    bitmap.Save(imageStream, originalImage.RawFormat);

                }

                byte[] thumbnail = new byte[imageStream.Length];
                BinaryReader reader = new BinaryReader(imageStream);
                imageStream.Seek(0, SeekOrigin.Begin);

                //--Load the image binary.
                thumbnail = reader.ReadBytes((int)imageStream.Length);
                return thumbnail;

            }        

       }

.

Now in the future i will look to extend this further to encompass all of Nathanael’s advice. So that’s it! Took a lot of work getting there, not just coding the solution but understanding it which is key.

The last thing i did was add more properties to my ImageModel to save the Thumbnail data separately from the main Image, so we don’t have to convert it each and every time we want to load the Thumbnail instead of the main Image binary.

So my new ImageModel class is now:

    /// <summary> 
    /// This class represents the table for Images and it necessary columns
    /// </summary>
    public class ImageModel : IImageModel
    {
        [Key]
        public Guid UniqueId { get; set; }
        [Required]
        public Guid TableLink { get; set; }
        public String RecordStatus { get; set; }
        [Date]
        public DateTime RecordCreatedDate { get; set; }
        [Date]
        public DateTime RecordAmendedDate { get; set; }
        public Byte[] Source { get; set; }
        public Int32 FileSize { get; set; }
        public String FileName { get; set; }
        [FileExtensions("png|jpg|jpeg|gif")]
        public String FileExtension { get; set; }
        public String ContentType { get; set; }
        public Int32 Height { get; set; }
        public Int32 Width { get; set; }

        //-- New in Alpha Release 0.0.2
        public Byte[] ThumbnailSource { get; set; }
        public Int32 ThumbnailFileSize { get; set; }
        public String ThumbnailContentType { get; set; }
        public Int32 ThumbnailHeight { get; set; }
        public Int32 ThumbnailWidth { get; set; }

    }
Enhanced by Zemanta

4 thoughts on “[MVC 3] MvcImage Project–How did i do Thumbnail Support?

  1. Gareth,
    I downloaded your MvcImage project from GitHub and installed it. The project I have only provides image operations on the Register screen. When I Create/Edit an entry there is no place to upload an image. Am I missing something or is this an older version?

    I like your approach with MVC and am using it to learn more about it.

    Thanks,
    Ken

    Like

    1. Hey Ken,

      Thanks for taking an interest very much appreciated. I have though been committing all new entries to a github repo now:

      https://github.com/garfbradaz/MvcImage

      At the moment my Dev PC PSU has blown but I should have it back and running next week and I will reply concerning the different controller actions and Image Processing. BTW my Utility Classes can be found here:

      http://bradaz.codeplex.com/

      I hope you are enjoying MVC?!

      Gareth

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s