Monday, September 23, 2013

How to crop uploaded image with JQuery and ASP.NET

Hey guys,
Last week we tried to add new feature to our website, http://www.bestbars.in/, to let users upload and crop their profile pictures. Well we already had the functionality to upload pictures using uplodify plugin. But the problem we had was the images were all different size, some portrait, some landscape, some square etc. So then when we try to make our design pretty it was quite difficult. So the decision was to add cropping functionality to make users crop images into square size profile picture.
Since our website is developed using ASP.NET we started googling how to implement image cropping using JQuery and ASP.NET and came across this great tutorial “Upload and Crop Images with jQuery, JCrop and ASP.NET” from Mikesdotnetting (which is actually a cool domain name J). So thank you very much for a great tutorial Mike.
But here are a few problems we faced:
1.       We wanted to avoid postbacks when uploading and cropping images.
2.       We wanted to let people re-upload and crop image in case after cropping they did not like the results, and again, without postbacks.
With current implementation it did not work, since JCrop plugin did not recognize new loaded image and was still showing the old one. Plus we did not want to display uploaded image with 100% size, it just broke our layout, and therefore we had to touch up our math a little bit.
So avoiding any further discussions let’s dig into the code. Create new web project “AsyncImageCropping”. By default VS would create some extra stuff which is not necessary for this project, so you have a chance of either cleaning up your solution or leave it as it is. We did the cleaning, removing Account folder, About.aspx page and slightly changing Site.Master. Here is what we have so far:


Now, we need to add all required components, they are:
1.       JQuery file (jquery-1.9.0.js)
2.       Uploadify plugin (jquery.uploadify-3.1.js, uploadify.swf, uploadify.css)
3.       JCrop plugin (jquery.Jcrop.js, jquery.Jcrop.css, Jcrop.gif)
Okay, quick step aside, to make our uploadify plugin work we also need to add extra code that would take care of our files uploading and saving on the server. Don’t worry, we will get back to it later.
Now let’s prepare our form to take care of image uploading and cropping. We would need to blocks, one with profile image and a button to change it, when user clicks that button we would perform new image selection and upload and then hide this block and display a new one, where we would display new uploaded image and let our user to crop it. So second block would have an image control and a button to crop the image. Once this button is clicked we would crop the image, save it and hide this block, then display our first block with new profile image.
Here is our html code of Default.aspx:
    <div id="dvUplod">
        <div class="profileimage">
            <img id="profilePicture"alt=""src="/img/default.jpg"/></div>
        <div id="profileUpload">
            Upload</div>
    </div>
    <div id="dvCrop">
        <div class="cropimage"id="cropcontainer">
            <img src="/img/default.jpg"id="imgCrop"width="200px"/></div>
        <input type="hidden"id="X"/>
        <input type="hidden"id="Y"/>
        <input type="hidden"id="W"/>
        <input type="hidden"id="H"/>
        <div class="button"id="cropImage">
            Crop Image</div>
    </div>
And this is how it looks so far:




Add following styles to you Site.css:
.profileimage
{
    display: table;
    width: 200px;
    height: 200px;
    border: 1px solid #CCCCCC;
    padding: 8px;
    background-color: #FFFFFF;
    margin-bottom: 10px;
}
.profileimage img
{
    width: 100%;
}
.cropimage
{
    display: table;
    width: 400px;
}
.cropimageimg
{
    width: 400px;
}
.button
{
    font-size: 1em;
    padding: 8px;
    background-color: #d8188d;
    border: 1px solid #F838Ad;
    text-align: center;
    color: #FFFFFF;
    cursor: pointer;
    width: 160px;
    display: table;
    border-radius: 6px;
}

Here is the code of our Default.aspx.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.IO;
using SD = System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;

namespace AsyncImageCropping
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            Response.Cache.SetCacheability(System.Web.HttpCacheability.NoCache);
            Response.Cache.SetNoStore();
            if (Request.QueryString["action"] != null)
            {
                switch (Request.QueryString["action"])
                {
                    case "crop":
                        string profileImage = string.Empty;
                        try
                        {
                            string tempPath = "/img";
                            string path = Server.MapPath(tempPath);
                            string[] src = Request.QueryString["src"].Split('/');
                            string ImageName = src[src.Length - 1];
                            if (ImageName.IndexOf('?') >= 0)
                            {
                                ImageName = ImageName.Split('?')[0];
                            }
                            int w = Convert.ToInt32(Request.QueryString["w"]);
                            int h = Convert.ToInt32(Request.QueryString["h"]);
                            int x = Convert.ToInt32(Request.QueryString["x"]);
                            int y = Convert.ToInt32(Request.QueryString["y"]);
                            int iw = Convert.ToInt32(Request.QueryString["iw"]);
                            int ih = Convert.ToInt32(Request.QueryString["ih"]);
                            System.Drawing.Image objImage = System.Drawing.Image.FromFile(path + "/" + ImageName);
                            decimal scale = (decimal)objImage.Width / (decimal)iw;
                            x = (int)(x * scale);
                            y = (int)(y * scale);
                            w = (int)(w * scale);
                            h = (int)(h * scale);
                            byte[] CropImage = Crop(path + "/" + ImageName, w, h, x, y);
                            profileImage = ImageName.Replace("_uncropped", "");
                            using (MemoryStream ms = new MemoryStream(CropImage, 0, CropImage.Length))
                            {
                                ms.Write(CropImage, 0, CropImage.Length);
                                using (SD.Image CroppedImage = SD.Image.FromStream(ms, true))
                                {
                                    string SaveTo = path + "/" + profileImage;
                                    CroppedImage.Save(SaveTo, CroppedImage.RawFormat);
                                }
                            }
                            objImage.Dispose();
                            if (File.Exists(path + "/" + ImageName))
                            {
                                File.Delete(path + "/" + ImageName);
                            }
                        }
                        catch (Exception ex)
                        {
                        }
                        if (profileImage == string.Empty)
                            Response.Write("/img/default.jpg");
                        else
                            Response.Write("/img/" + profileImage);
                        Response.End();
                        break;
                    case "upload":
                        if (Request.Form["userToken"] != null)
                        {
                            string userId = Request.Form["userToken"];
                            string savepath = "";

                            HttpPostedFile postedFile = Request.Files["Filedata"];

                            string tempPath = "/img";
                            savepath = Server.MapPath(tempPath);
                            string filename = userId + "_uncropped.jpg";
                            if (!Directory.Exists(savepath))
                                Directory.CreateDirectory(savepath);

                            if (File.Exists(savepath + @"\" + filename))
                                File.Delete(savepath + @"\" + filename);

                            postedFile.SaveAs(savepath + @"\" + filename);
                            Response.Write(tempPath + "/" + filename);
                            Response.StatusCode = 200;
                            Response.End();
                        }
                        else
                        {
                            throw new Exception("You have to specify user ID");
                        }
                        break;
                }
            }
        }
        static byte[] Crop(string Img, int Width, int Height, int X, int Y)
        {
            try
            {
                MemoryStream ms = newMemoryStream();
                using (SD.Image OriginalImage = SD.Image.FromFile(Img))
                {
                    using (SD.Bitmap bmp = new SD.Bitmap(Width, Height))
                    {
                        bmp.SetResolution(OriginalImage.HorizontalResolution, OriginalImage.VerticalResolution);
                        using (SD.Graphics Graphic = SD.Graphics.FromImage(bmp))
                        {
                            Graphic.SmoothingMode = SmoothingMode.AntiAlias;
                            Graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
                            Graphic.PixelOffsetMode = PixelOffsetMode.HighQuality;
                            Graphic.DrawImage(OriginalImage, new SD.Rectangle(0, 0, Width, Height), X, Y, Width, Height, SD.GraphicsUnit.Pixel);
                            bmp.Save(ms, OriginalImage.RawFormat);
                        }
                    }
                }
                return ms.GetBuffer();
            }
            catch (Exception Ex)
            {
                throw (Ex);
            }
        }
    }
}



Now add following code to the header of your Default.aspx page:
<link href="/Styles/uploadify.css"rel="stylesheet"type="text/css"/>
    <link href="/Styles/jquery.Jcrop.css"rel="stylesheet"type="text/css"/>
    <script type="text/javascript"src="Scripts/jquery-1.9.0.js"></script>
    <script type="text/javascript"src="/Scripts/jquery.uploadify-3.1.js"></script>
    <script type="text/javascript"src="Scripts/jquery.Jcrop.js"></script>
    <script language="javascript"type="text/javascript">
        $(document).ready(function () {
            $('#dvCrop').hide();           
            var userId = 'ouruserid'; // replace this one with your generic user ID
            $("#profileUpload").uploadify({
                'formData': { 'userToken': userId }, // form parameters
                'fileTypeDesc': 'Image Files',
                'fileTypeExts': '*.gif; *.jpg; *.png',
                'swf': '/scripts/uploadify.swf', // path to your swf file
                'uploader': 'Default.aspx?action=upload', // path to generic handler
                'buttonClass': 'uploadify-button',
                'buttonText': 'Change Photo',
                'width': 176,
                'multi': false,
                'auto': true,
                'onUploadComplete': function (file) {
                    $('#cropcontainer').empty(); // clear the control
                    var path = "/img/" + userId + "_uncropped.jpg" + "?timestamp=" + Date(); // get the path of the image, adding tiestamp so it is not cached
                    $('#cropcontainer').append("<img src='" + path + "' width='200px' />"); // add image control to the container
                    $('#imgCrop').attr('src', path); // set source property of the image
                    $('#dvUplod').hide();
                    $('#dvCrop').show();
                    // call JCrop function
                    $('#cropcontainer img').Jcrop({
                        onSelect: storeCoords,
                        bgColor: 'black',
                        bgOpacity: 0.3,
                        aspectRatio: 1,
                        setSelect: [10, 10, 50, 50]
                    }, function () {
                        jcrop_api = this;
                    });
                }
            });
            $("#cropImage").click(function () {
                crop($('#cropcontainer img').attr('src'), $('#X').val(), $('#Y').val(), $('#H').val(), $('#W').val());
            });
            function storeCoords(c) {
                $('#X').val(c.x);
                $('#Y').val(c.y);
                $('#W').val(c.w);
                $('#H').val(c.h);
            };
            function crop(imgSrc, x, y, h, w) {
                $.ajax({ url: '/Default.aspx?action=crop&src=' + imgSrc + '&x=' + x + '&y=' + y + '&h=' + h + '&w=' + w + '&iw=' + $('#cropcontainer img').width() + '&ih=' + $('#cropcontainer img').height(),
                    async: false,
                    success: function (data) {
                        $("#profilePicture").attr('src', data + "?timestamp=" + Date());
                        $("#dvCrop").hide();
                        $("#dvUplod").show();
                    }
                });
            }
        });
    </script>
And time to give it a go:






Let’s say you are unhappy, so let’s upload another image and crop it:



Works perfect, doesn’t it?
Okay a few tricks we did here: async calls to the server side. You might want to touch up our C# code, we did not have a goal to show you the most efficient code here, just to show a working solution you can start with J
Then on JQuery side the trick with avoiding JCrop showing cached images is to actually empty the container, add another image to it and then call JCrop function, this way it will be applied to a new DOM object and you won’t have any problems.
Why so many calculations on the server side? Well in case you don’t display your image (the one you are about to crop) in its’ original size, you will end up with wrong cropped image because your coordinates (x.y.w.h) are passed based on the image size from the screen, and your cropping is done with the original image size.
So this is it guys, play with it, optimize the code, there is a huge field for improvement!
Cheers,
Your GPTeam

1 comment: