ASP.NET MVC: jQuery image upload and crop

ASP.NET MVC: jQuery image upload and cropThis is, probably, most used case when you have to use ajax upload of the image and after that you need to use croppind. Please, notice, in this article I described how to crop static image by using Jcrop library, in current post describes almost the same, except image will not be a static and will be uploaded by jQuery. Before reading this, I strongly recommend to read ASP.NET MVC: Crop image by using Jcrop, because in this one I will make accent only on how to upload image and how to save cropped one and will not describe cropping process. But anyway, it will be completed and working example that you can download at the end.

So, let’s get started. First off, want to say, that current example was tested in the next browsers and devices:

IE 8, 9, 10, 11, Google Chrome, Mozilla FireFox, Safari (Mac OS), iPad, iPhone and Android.

1) Create new ASP.NET MVC web application and remove all default html

2) Attach next js and css files to the ~/Views/Shared/_Layout.cshtml:

jQuery 1.9.1

jquery-1.9.1.min.js
jquery-ui-1.9.2.min.js

Jcrop-v0.9.12

jquery.Jcrop.min.css
jquery.Jcrop.min.js

jQuery File Upload Plugin 5.9

jquery.fileupload.js – jQuery File Upload Plugin 5.9
jquery.fileupload-ui.js – jQuery File Upload User Interface Plugin 6.6.2
jquery.iframe-transport.js – jQuery Iframe Transport Plugin 1.3

3) Code of ~/Views/Shared/_Layout.cshtml

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Testing cropping and uploading</title>
</head>
<body>
    @RenderBody()

    <script src="~/Scripts/jquery-1.9.1.min.js"></script>
    <script src="~/Scripts/jquery-ui-1.9.2.min.js"></script>

    <link href="~/JsLibraries/Jcrop-v0.9.12/css/jquery.Jcrop.min.css" rel="stylesheet" />
    <script src="~/JsLibraries/Jcrop-v0.9.12/js/jquery.Jcrop.min.js"></script>

    <script src="~/JsLibraries/file-upload/jquery.fileupload.js"></script>
    <script src="~/JsLibraries/file-upload/jquery.fileupload-ui.js"></script>
    <script src="~/JsLibraries/file-upload/jquery.iframe-transport.js"></script>

    @RenderSection("scripts", required: false)
</body>
</html>

4) Create /Models/MyModel.cs

 public class MyModel
 {
     public HttpPostedFileBase MyFile { get; set; }

     public string CroppedImagePath { get; set; }
 }

5) FileController.cs – UploadFile() method saves image in the temp folder for time being.

[HttpPost]
public ActionResult UploadFile()
{
    HttpPostedFileBase myFile = Request.Files["MyFile"];
    bool isUploaded = false;

    string tempFolderName = ConfigurationManager.AppSettings["Image.TempFolderName"];

    if (myFile != null && myFile.ContentLength != 0)
    {
        string tempFolderPath = Server.MapPath("~/" + tempFolderName);

        if (FileHelper.CreateFolderIfNeeded(tempFolderPath))
        {
            try
            {
                myFile.SaveAs(Path.Combine(tempFolderPath, myFile.FileName));
                isUploaded = true;
            }
            catch (Exception) {  /*TODO: You must process this exception.*/}
        }
    }

    string filePath = string.Concat("/", tempFolderName, "/", myFile.FileName);
    return Json(new { isUploaded, filePath }, "text/html");
}

6) ImageController.cs – CropImage() method works ONLY with file from temp folder, each new version of the cropped image saves ONLY to the Temp folder.

[HttpPost]
public ActionResult CropImage(
    string imagePath,
    int? cropPointX,
    int? cropPointY,
    int? imageCropWidth,
    int? imageCropHeight)
{
    if (string.IsNullOrEmpty(imagePath)
        || !cropPointX.HasValue
        || !cropPointY.HasValue
        || !imageCropWidth.HasValue
        || !imageCropHeight.HasValue)
    {
        return new HttpStatusCodeResult((int)HttpStatusCode.BadRequest);
    }

    byte[] imageBytes = System.IO.File.ReadAllBytes(Server.MapPath(imagePath));
    byte[] croppedImage = ImageHelper.CropImage(imageBytes, cropPointX.Value, cropPointY.Value, imageCropWidth.Value, imageCropHeight.Value);

    string tempFolderName = Server.MapPath("~/" + ConfigurationManager.AppSettings["Image.TempFolderName"]);

    string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(imagePath);
    string fileName = Path.GetFileName(imagePath).Replace(fileNameWithoutExtension, fileNameWithoutExtension + "_cropped");

    try
    {
        FileHelper.SaveFile(croppedImage, Path.Combine(tempFolderName, fileName));
    }
    catch (Exception)
    {
        //Log an error     
        return new HttpStatusCodeResult((int)HttpStatusCode.InternalServerError);
    }

    string photoPath = string.Concat("/", ConfigurationManager.AppSettings["Image.TempFolderName"], "/", fileName);
    return Json(new { photoPath = photoPath }, JsonRequestBehavior.AllowGet);
}

7) FileHelper.cs – Uses in the ImageController and FileController and helps to work with image.

public static class FileHelper
{
    public static void SaveFile(byte[] content, string path)
    {
        string filePath = GetFileFullPath(path);
        if (!Directory.Exists(Path.GetDirectoryName(filePath)))
        {
            Directory.CreateDirectory(Path.GetDirectoryName(filePath));
        }

        //Save file
        using (FileStream str = File.Create(filePath))
        {
            str.Write(content, 0, content.Length);
        }
    }

    public static string GetFileFullPath(string path)
    {
        string relName = path.StartsWith("~") ? path : path.StartsWith("/") ? string.Concat("~", path) : path;

        string filePath = relName.StartsWith("~") ? HostingEnvironment.MapPath(relName) : relName;

        return filePath;
    }

    public static bool CreateFolderIfNeeded(string path)
    {
        bool result = true;
        if (!Directory.Exists(path))
        {
            try
            {
                Directory.CreateDirectory(path);
            }
            catch (Exception)
            {
                /*TODO: You must process this exception.*/
                result = false;
            }
        }
        return result;
    }
}

8) ImageHelper.cs – Uses only in the ImageController and helps crop and resize image.

public static class ImageHelper
{
    public static byte[] CropImage(byte[] content, int x, int y, int width, int height)
    {
        using (MemoryStream stream = new MemoryStream(content))
        {
            return CropImage(stream, x, y, width, height);
        }
    }

    public static byte[] CropImage(Stream content, int x, int y, int width, int height)
    {
        //Parsing stream to bitmap
        using (Bitmap sourceBitmap = new Bitmap(content))
        {
            //Get new dimensions
            double sourceWidth = Convert.ToDouble(sourceBitmap.Size.Width);
            double sourceHeight = Convert.ToDouble(sourceBitmap.Size.Height);
            Rectangle cropRect = new Rectangle(x, y, width, height);

            //Creating new bitmap with valid dimensions
            using (Bitmap newBitMap = new Bitmap(cropRect.Width, cropRect.Height))
            {
                using (Graphics g = Graphics.FromImage(newBitMap))
                {
                    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    g.SmoothingMode = SmoothingMode.HighQuality;
                    g.PixelOffsetMode = PixelOffsetMode.HighQuality;
                    g.CompositingQuality = CompositingQuality.HighQuality;

                    g.DrawImage(sourceBitmap, new Rectangle(0, 0, newBitMap.Width, newBitMap.Height), cropRect, GraphicsUnit.Pixel);

                    return GetBitmapBytes(newBitMap);
                }
            }
        }
    }

    public static byte[] GetBitmapBytes(Bitmap source)
    {
        //Settings to increase quality of the image
        ImageCodecInfo codec = ImageCodecInfo.GetImageEncoders()[4];
        EncoderParameters parameters = new EncoderParameters(1);
        parameters.Param[0] = new EncoderParameter(Encoder.Quality, 100L);

        //Temporary stream to save the bitmap
        using (MemoryStream tmpStream = new MemoryStream())
        {
            source.Save(tmpStream, codec, parameters);

            //Get image bytes from temporary stream
            byte[] result = new byte[tmpStream.Length];
            tmpStream.Seek(0, SeekOrigin.Begin);
            tmpStream.Read(result, 0, (int)tmpStream.Length);

            return result;
        }
    }
}

9) /Views/Home/Index.cshtml

@model TestImageUploadAndCrop.Models.MyModel
@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}

@using (Html.BeginForm("Index", "Home"))
{
    @Html.Hidden("UploadedImagePath", string.Empty, new { id = "hf-uploaded-image-path" })
    @Html.HiddenFor(m => m.CroppedImagePath, new { id = "hf-cropped-image-path" })

    <p>@Html.TextBoxFor(m => m.MyFile, new { id = "fu-my-simple-upload", type = "file" })</p>
    <p><a href="#" id="hl-start-upload">Start Upload</a></p>

    <div id="crop-image-area" style="display:none;">
        <p><a href="#" id="hl-crop-image">Crop Image</a></p>
        <p><img id="uploaded-image" src="#" /></p>
        <p><img id="my-cropped-image" src="#" style="display:none;" /></p>
        <p><input type="submit" value="Submit" id="btn-my-submit" style="display: none;" /></p>
    </div>
}

@section scripts{

    <script type="text/javascript">

        //************************************** JavaScript for ajax file upload **************************************
        var jqXHRData;

        $(document).ready(function () {

            'use strict';

            $('#fu-my-simple-upload').fileupload({
                url: '/File/UploadFile',
                dataType: 'json',
                add: function (e, data) {
                    jqXHRData = data;
                },
                done: function (event, data) {
                    if (data.result.isUploaded) {

                        $("#hf-uploaded-image-path").val(data.result.filePath);

                        destroyCrop();

                        $("#uploaded-image").attr("src", data.result.filePath + "?t=" + new Date().getTime());

                        initCrop();

                        $("#crop-image-area").fadeIn("slow");
                    } else {

                    }
                },
                fail: function (event, data) {
                    if (data.files[0].error) {
                        alert(data.files[0].error);
                    }
                }
            });
        });

        $("#hl-start-upload").on('click', function () {
            if (jqXHRData) {
                jqXHRData.submit();
            }
            return false;
        });
        //************************************** JavaScript for ajax file upload END **************************************




        //************************************** JavaScript for cropping of image *****************************************
        var imageCropWidth = 0;
        var imageCropHeight = 0;
        var cropPointX = 0;
        var cropPointY = 0;

        $("#hl-crop-image").on("click", function (e) {
            e.preventDefault();
            cropImage();
        });

        function initCrop() {
            $('#uploaded-image').Jcrop({
                onChange: setCoordsAndImgSize,
                aspectRatio: 1
            });
        }

        function destroyCrop() {
            var jcropApi = $('#uploaded-image').data('Jcrop');

            if (jcropApi !== undefined) {
                jcropApi.destroy();
                $('#uploaded-image').attr('style', "").attr("src", "");
            }
        }

        function setCoordsAndImgSize(e) {

            imageCropWidth = e.w;
            imageCropHeight = e.h;

            cropPointX = e.x;
            cropPointY = e.y;
        }

        function cropImage() {

            if (imageCropWidth == 0 && imageCropHeight == 0) {
                alert("Please select crop area.");
                return;
            }

            $.ajax({
                url: '/Image/CropImage',
                type: 'POST',
                data: {
                    imagePath: $("#hf-uploaded-image-path").val(),
                    cropPointX: cropPointX,
                    cropPointY: cropPointY,
                    imageCropWidth: imageCropWidth,
                    imageCropHeight: imageCropHeight
                },
                success: function (data) {

                    $("#hf-cropped-image-path").val(data.photoPath);

                    $("#my-cropped-image")
                        .attr("src", data.photoPath + "?t=" + new Date().getTime())
                        .show();

                    $("#btn-my-submit").fadeIn("slow");
                },
                error: function (data) { }
            });
        }

        //************************************** JavaScript for cropping of image END **************************************

    </script>
}

10) /Views/Home/WellDoneView.cshtml

@{
    ViewBag.Title = "WellDoneView";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Well done!</h2>

11) HomeController.cs

[HttpGet]
public ActionResult Index()
{
    return View();
}

[HttpPost]
public ActionResult Index(MyModel model)
{
    string imagePath = Server.MapPath("~/" + model.CroppedImagePath);

    byte[] imageBytes = System.IO.File.ReadAllBytes(imagePath);

    //Save file whatever you want

    return View("WellDoneView");
}

 

12) Add new key to the Web.config

  <appSettings>
     .....
    <add key="Image.TempFolderName" value="Temp" />
  </appSettings>

Download DEMO (Visual Studio 2013)

Have a good coding :)

This work, “ASP.NET MVC: jQuery image upload and crop”, uses image from flickr that was added by Laura Bittner, used under CC BY 4.0. “ASP.NET MVC: jQuery image upload and crop” is licensed under CC BY 4.0 by Sergey Boiko.

 

  • Win

    Wow!! awesome post.. I was looking for this!! thanks!!