Image Cropping Using Jcrop In ASP.NET MVC Application

11 minute read

What is Image cropping?

  • Image cropping refer to removal of some part of image
  • Uses to improve framing and size
  • Applied in photograph to resize image

Application overview
In this project, I will show image cropping of an employee phot when employee information saved and modified. Full crud operation applied in the application with image cropping.

Let’s have a look on the implementation of the project.

Tools and Technology used I have used following tools and technology to develop the project –

  • Visual Studio 2015
  • Visual C#
  • ASP.NET MVC
  • Entity Framework 5
  • Razor view engine
  • JCrop

Step 1: Create a ASP.net MVC Project

  • Select File -> New project and select the project template Visual C# -> ASP.NET Web Application (.NET Framework)

  • Select MVC Template and click OK

Step 2: Change or Add Connection String
Change or Add connection string in Web.config as follows

<connectionStrings>
  <add name="DefaultConnection" connectionString="Data Source=(LocalDb\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\JCropMVCDB.mdf;Initial Catalog=JCropMVCDB;Integrated Security=True" providerName="System.Data.SqlClient" />
</connectionStrings>

Step 3: Install JCrop Nuget Package
Go to Tools -> NuGet Package Manager -> Manages NuGet Package Manager for the solution -> Search Jcrop and install jquery.jcrop.js

Step 4: Create model class

Create model class “Employee” as follows.

Employee Class

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
 
namespace JCropMVC.Models
{
    public class Employee
    {
        [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }
 
        [Display(Name = "Full Name")]
        [Required(ErrorMessage = "Name cannot be empty")]
        public string Name { get; set; }
 
        [Display(Name = "Father Name")]
        public string FatherName { get; set; }
 
        [Display(Name = "Designation")]
        public string Designation { get; set; }
 
        [Display(Name = "Mobile No")]
        public string Mobile { get; set; }
 
        [DataType(DataType.EmailAddress)]
        [Display(Name = "Email")]
        public string Email { get; set; }
 
        [Display(Name = "Image URL")]
        public string PhotoURL { get; set; }
    }
}

Step 5: Modify Context class

  • Modify ApplicationDbContext in IdentityModel Class
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext()
        : base("DefaultConnection", throwIfV1Schema: false)
    {
    }
 
    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }
 
    public DbSet<Employee> Employees { get; set; }
}

Step 6: Create Controller and Views

  • Create Employees Controller and Views
  • Click Right button on Controller Folder->Add Controller. Now choose scaffolding template as MVC Controllers with views using Entity Framework and then Click Add.
  • Now select Model class as Employee and Data Context Class as ApplicationDbContext and click OK.

Step 7: Modify the controller

  • Modify EmployeesController as follows. Here method ProcessImage(string croppedImage) is used to process and save image. An extra parameter added for Create and Edit action.
using System;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web.Mvc;
using JCropMVC.Models;
using System.IO;
 
namespace JCropMVC.Controllers
{
    public class EmployeesController : Controller
    {
        private ApplicationDbContext db = new ApplicationDbContext();
 
        // GET: Employees
        public ActionResult Index()
        {
            return View(db.Employees.ToList());
        }
 
        // GET: Employees/Details/5
        public ActionResult Details(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Employee employee = db.Employees.Find(id);
            if (employee == null)
            {
                return HttpNotFound();
            }
            return View(employee);
        }
 
        // GET: Employees/Create
        public ActionResult Create()
        {
            return View();
        }
 
        // POST: Employees/Create
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind(Include = "Id,Name,FatherName,Designation,Mobile,Email,PhotoURL")] Employee employee, string avatarCropped)
        {
            string filePath = ProcessImage(avatarCropped);
            employee.PhotoURL = filePath;
 
            if (ModelState.IsValid)
            {
                db.Employees.Add(employee);
                db.SaveChanges();
                return RedirectToAction("Index");
            }
 
            return View(employee);
        }
 
        /// <summary>
        /// Process image and save in predefined path
        /// </summary>
        /// <param name="croppedImage"></param>
        /// <returns></returns>
        private string ProcessImage(string croppedImage)
        {
            string filePath = String.Empty;
            try
            {
                string base64 = croppedImage;
                byte[] bytes = Convert.FromBase64String(base64.Split(',')[1]);
                filePath = "/Images/Photo/Emp-" + Guid.NewGuid() + ".png";
                using (FileStream stream = new FileStream(Server.MapPath(filePath), FileMode.Create))
                {
                    stream.Write(bytes, 0, bytes.Length);
                    stream.Flush();
                }
            }
            catch (Exception ex)
            {
                string st = ex.Message;
            }
 
            return filePath;
        }
 
        // GET: Employees/Edit/5
        public ActionResult Edit(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Employee employee = db.Employees.Find(id);
            if (employee == null)
            {
                return HttpNotFound();
            }
            return View(employee);
        }
 
        // POST: Employees/Edit/5
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit([Bind(Include = "Id,Name,FatherName,Designation,Mobile,Email,PhotoURL")] Employee employee, string avatarCropped)
        {
            string filePath = ProcessImage(avatarCropped);
            employee.PhotoURL = filePath;
 
            if (ModelState.IsValid)
            {
                db.Entry(employee).State = EntityState.Modified;
                db.SaveChanges();
                return RedirectToAction("Index");
            }
            return View(employee);
        }
 
        //[Authorize(Users ="sumon")]
        // GET: Employees/Delete/5
        public ActionResult Delete(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Employee employee = db.Employees.Find(id);
            if (employee == null)
            {
                return HttpNotFound();
            }
            return View(employee);
        }
 
        // POST: Employees/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public ActionResult DeleteConfirmed(int id)
        {
            Employee employee = db.Employees.Find(id);
            db.Employees.Remove(employee);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
 
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                db.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}

Step 8: Modify the views for Employee

Create.cshtml

@model JCropMVC.Models.Employee
 
@{
    ViewBag.Title = "Create";
}
 
<h2 class="breadcrumb">
    Create Employee Information
</h2>
 
@using (Html.BeginForm("Create", "Employees", null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    @Html.AntiForgeryToken()
 
    <hr />
    @Html.ValidationSummary(true)
 
    <div class="form-group">
        @Html.LabelFor(model => model.Name, new { @class = "" })
        @Html.TextBoxFor(model => model.Name, new { @class = "form-control", placeholder = "Enter employee full name...", type = "text" })
        @Html.ValidationMessageFor(model => model.Name)
    </div>
 
    <div class="form-group">
        @Html.LabelFor(model => model.FatherName, new { @class = "" })
        @Html.TextBoxFor(model => model.FatherName, new { @class = "form-control", placeholder = "Enter father name...", type = "text" })
        @Html.ValidationMessageFor(model => model.FatherName)
    </div>
 
    <div class="form-group">
        @Html.LabelFor(model => model.Designation, new { @class = "" })
        @Html.TextBoxFor(model => model.Designation, new { @class = "form-control", placeholder = "Enter designation name...", type = "text" })
        @Html.ValidationMessageFor(model => model.Designation)
    </div>
 
    <div class="form-group">
        @Html.LabelFor(model => model.Mobile, new { @class = "" })
        @Html.TextBoxFor(model => model.Mobile, new { @class = "form-control", placeholder = "Enter mobile number...", type = "text" })
        @Html.ValidationMessageFor(model => model.Mobile)
    </div>
 
    <div class="form-group">
        @Html.LabelFor(model => model.Email, new { @class = "" })
        @Html.TextBoxFor(model => model.Email, new { @class = "form-control", placeholder = "Enter email address...", type = "text" })
        @Html.ValidationMessageFor(model => model.Email)
    </div>
 
 
    <div class="form-group">
        @Html.LabelFor(model => model.PhotoURL, new { @class = "" })
        <input type="file" id="flPhoto" name="upload" />
 
        <table>
            <tr>
                <td>
                    Width: <label id="lblWidth">200px</label> &nbsp;
                    Height: <label id="lblHeight">200px</label>
                </td>
                <td>
                    <a href="#" id="hlcropImage" style="vertical-align:top;">Crop Image</a>
 
                </td>
 
 
            </tr>
            <tr>
                <td>
                    <div style="height:400px; width:400px; overflow:auto;">
                        <img id="imgEmpPhoto" src="~/Images/Default/emp-blank-avatar.png" alt="Employee Image" />
                    </div>
                </td>
                <td>
                    <canvas id="canvas" height="5" width="5" style="vertical-align:top;"></canvas>
                </td>
            </tr>
        </table>
 
    </div>
    <p>
        <img id="imgCropped" src="#" style="display:none;" />
    </p>
 
    <input type="hidden" name="avatarCropped" id="avatarCropped" />
 
 
 
    <div class="form-group">
        <input type="submit" value="Create" class="btn btn-primary" />
    </div>
}
 
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
 
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
 
    <script type="text/javascript">
 
        var imageCropWidth = 0;
        var imageCropHeight = 0;
        var cropPointX = 0;
        var cropPointY = 0;
        var jcropApi;
 
        $(document).ready(function () {
            //initCrop();
        });
 
        $("#hlcropImage").on("click", function (e) {
 
            /*
            The event.preventDefault() method stops the default action of
            an element from happening. For example: Prevent a submit button
            from submitting a form. Prevent a link from following the URL
            */
 
            e.preventDefault();
            cropImage();
        });
 
        function initCrop() {
            $('#imgEmpPhoto').Jcrop({
                onChange: setCoordsAndImgSize,
                aspectRatio: 0, // 1 means will be same for height and weight
                onSelect: setCoordsAndImgSize
            }, function () { jcropApi = this });
        }
 
        function showCoordinate() {
            $("#lblWidth").text(imageCropWidth + "px");
            $("#lblHeight").text(imageCropHeight + "px");
        }
 
        function setCoordsAndImgSize(e) {
 
            imageCropWidth = e.w;
            imageCropHeight = e.h;
 
            cropPointX = e.x;
            cropPointY = e.y;
 
            $("#lblWidth").text(imageCropWidth + "px");
            $("#lblHeight").text(imageCropHeight + "px");
        }
 
        function cropImage() {
 
            if (imageCropWidth == 0 && imageCropHeight == 0) {
                alert("Please select crop area.");
                return;
            }
 
            var img = $("#imgEmpPhoto").attr("src");
 
            /*Show cropped image*/
            showCroppedImage();
        }
 
        function showCroppedImage() {
            var x1 = cropPointX;
            var y1 = cropPointY;
 
            var width = imageCropWidth;
            var height = imageCropHeight;
            var canvas = $("#canvas")[0];
            var context = canvas.getContext('2d');
            var img = new Image();
            img.onload = function () {
                canvas.height = height;
                canvas.width = width;
                context.drawImage(img, x1, y1, width, height, 0, 0, width, height);
                $('#avatarCropped').val(canvas.toDataURL());
            };
            img.src = $('#imgEmpPhoto').attr("src");
        }
 
        function readFile(input) {
 
            if (input.files && input.files[0]) {
                var reader = new FileReader();
 
                /*Destroy jcrop initialization other wise it will hold it previous image in img tag*/
                if (jcropApi != null) {
                    jcropApi.destroy();
                }
                reader.onload = function (e) {
                    $('#imgEmpPhoto').attr('src', "");
                    var img = $('#imgEmpPhoto').attr('src', e.target.result);
 
                    /*Current uploaded image size*/
                    var width = img[0].height;
                    var height = img[0].width;
                    $("#lblWidth").text(width + "px");
                    $("#lblHeight").text(height + "px");
 
                    //InitCrop must call here otherwise it will not work
                    initCrop();
                }
 
                reader.readAsDataURL(input.files[0]);
            }
        }
 
        $('#flPhoto').change(function () {
            readFile(this);
            //initCrop();
        });
    </script>
}

Edit.cshtml

@model JCropMVC.Models.Employee
 
@{
    ViewBag.Title = "Edit";
}
 
<h2>Edit</h2>
 
 
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
 
    @*<div class="form-horizontal">*@
    <h4>Employee</h4>
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    @Html.HiddenFor(model => model.Id)
 
    <div class="form-group">
        @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "" })
        @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } })
        @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" })
    </div>
 
        <div class="form-group">
            @Html.LabelFor(model => model.FatherName, htmlAttributes: new { @class = "" })
 
            @Html.EditorFor(model => model.FatherName, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.FatherName, "", new { @class = "text-danger" })
 
        </div>
 
        <div class="form-group">
            @Html.LabelFor(model => model.Designation, htmlAttributes: new { @class = "" })
 
            @Html.EditorFor(model => model.Designation, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Designation, "", new { @class = "text-danger" })
 
        </div>
 
        <div class="form-group">
            @Html.LabelFor(model => model.Mobile, htmlAttributes: new { @class = "" })
 
            @Html.EditorFor(model => model.Mobile, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Mobile, "", new { @class = "text-danger" })
 
        </div>
 
        <div class="form-group">
            @Html.LabelFor(model => model.Email, htmlAttributes: new { @class = "" })
 
            @Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Email, "", new { @class = "text-danger" })
 
        </div>
 
        @*<div class="form-group">
                @Html.LabelFor(model => model.PhotoURL, htmlAttributes: new { @class = "" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.PhotoURL, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.PhotoURL, "", new { @class = "text-danger" })
                </div>
            </div>*@
 
 
        <div class="form-group">
            @Html.LabelFor(model => model.PhotoURL, new { @class = "" })
            <input type="file" id="flPhoto" name="upload" />
 
            <table>
                <tr>
                    <td>
                        Width: <label id="lblWidth">200px</label> &nbsp;
                        Height: <label id="lblHeight">200px</label>
                    </td>
                    <td>
                        <a href="#" id="hlcropImage" style="vertical-align:top;">Crop Image</a>
 
                    </td>
 
 
                </tr>
                <tr>
                    <td>
                        <div style="height:400px; width:400px; overflow:auto;">
                            <img id="imgEmpPhoto" src="@Model.PhotoURL" alt="Employee Image" />
                        </div>
                    </td>
                    <td>
                        <canvas id="canvas" height="5" width="5" style="vertical-align:top;"></canvas>
                    </td>
                </tr>
            </table>
 
        </div>
        <p>
            <img id="imgCropped" src="#" style="display:none;" />
        </p>
 
        <input type="hidden" name="avatarCropped" id="avatarCropped" />
 
 
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </div>
        @*</div>*@
}
 
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
 
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
    <script type="text/javascript">
 
        var imageCropWidth = 0;
        var imageCropHeight = 0;
        var cropPointX = 0;
        var cropPointY = 0;
        var jcropApi;
 
        $(document).ready(function () {
            //initCrop();
        });
 
        $("#hlcropImage").on("click", function (e) {
 
            /*
            The event.preventDefault() method stops the default action of
            an element from happening. For example: Prevent a submit button
            from submitting a form. Prevent a link from following the URL
            */
 
            e.preventDefault();
            cropImage();
        });
 
        function initCrop() {
            $('#imgEmpPhoto').Jcrop({
                onChange: setCoordsAndImgSize,
                aspectRatio: 0, // 1 means will be same for height and weight
                onSelect: setCoordsAndImgSize
            }, function () { jcropApi = this });
        }
 
        function showCoordinate() {
            $("#lblWidth").text(imageCropWidth + "px");
            $("#lblHeight").text(imageCropHeight + "px");
        }
 
        function setCoordsAndImgSize(e) {
 
            imageCropWidth = e.w;
            imageCropHeight = e.h;
 
            cropPointX = e.x;
            cropPointY = e.y;
 
            $("#lblWidth").text(imageCropWidth + "px");
            $("#lblHeight").text(imageCropHeight + "px");
        }
 
        function cropImage() {
 
            if (imageCropWidth == 0 && imageCropHeight == 0) {
                alert("Please select crop area.");
                return;
            }
 
            var img = $("#imgEmpPhoto").attr("src");
 
            /*Show cropped image*/
            showCroppedImage();
        }
 
        function showCroppedImage() {
            var x1 = cropPointX;
            var y1 = cropPointY;
 
            var width = imageCropWidth;
            var height = imageCropHeight;
            var canvas = $("#canvas")[0];
            var context = canvas.getContext('2d');
            var img = new Image();
            img.onload = function () {
                canvas.height = height;
                canvas.width = width;
                context.drawImage(img, x1, y1, width, height, 0, 0, width, height);
                $('#avatarCropped').val(canvas.toDataURL());
            };
            img.src = $('#imgEmpPhoto').attr("src");
        }
 
        function readFile(input) {
 
            if (input.files && input.files[0]) {
                var reader = new FileReader();
 
                /*Destroy jcrop initialization other wise it will hold it previous image in img tag*/
                if (jcropApi != null) {
                    jcropApi.destroy();
                }
                reader.onload = function (e) {
                    $('#imgEmpPhoto').attr('src', "");
                    var img = $('#imgEmpPhoto').attr('src', e.target.result);
 
                    /*Current uploaded image size*/
                    var width = img[0].height;
                    var height = img[0].width;
                    $("#lblWidth").text(width + "px");
                    $("#lblHeight").text(height + "px");
 
                    //InitCrop must call here otherwise it will not work
                    initCrop();
                }
 
                reader.readAsDataURL(input.files[0]);
            }
        }
 
        $('#flPhoto').change(function () {
            readFile(this);
            //initCrop();
        });
    </script>
}

Index.cshtml

@model IEnumerable<JCropMVC.Models.Employee>
 
@{
    ViewBag.Title = "Index";
}
 
<h2>Index</h2>
 
<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table table-hover">
    <tr class="success">
        <th>
            @Html.DisplayNameFor(model => model.Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.FatherName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Designation)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Mobile)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Email)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.PhotoURL)
        </th>
        <th></th>
    </tr>
 
@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.FatherName)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Designation)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Mobile)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Email)
        </td>
        <td>
            <img src= "@item.PhotoURL" id="photo" height="50" width="50" />
            @*<img id="photo" src="~/Images/Photo/Emp-94a18d39-6de9-4330-a035-9a9dcf7c0927.png"" />*@
            @*@Html.DisplayFor(modelItem => item.PhotoURL)*@
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
            @Html.ActionLink("Details", "Details", new { id=item.Id }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.Id })
        </td>
    </tr>
}
 
</table>

Delete.cshtml

@model JCropMVC.Models.Employee
 
@{
    ViewBag.Title = "Delete";
}
 
<h2>Delete</h2>
 
<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Employee</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.Name)
        </dt>
 
        <dd>
            @Html.DisplayFor(model => model.Name)
        </dd>
 
        <dt>
            @Html.DisplayNameFor(model => model.FatherName)
        </dt>
 
        <dd>
            @Html.DisplayFor(model => model.FatherName)
        </dd>
 
        <dt>
            @Html.DisplayNameFor(model => model.Designation)
        </dt>
 
        <dd>
            @Html.DisplayFor(model => model.Designation)
        </dd>
 
        <dt>
            @Html.DisplayNameFor(model => model.Mobile)
        </dt>
 
        <dd>
            @Html.DisplayFor(model => model.Mobile)
        </dd>
 
        <dt>
            @Html.DisplayNameFor(model => model.Email)
        </dt>
 
        <dd>
            @Html.DisplayFor(model => model.Email)
        </dd>
 
        <dt>
            @Html.DisplayNameFor(model => model.PhotoURL)
        </dt>
 
        <dd>
            @Html.DisplayFor(model => model.PhotoURL)
        </dd>
 
    </dl>
 
    @using (Html.BeginForm()) {
        @Html.AntiForgeryToken()
 
        <div class="form-actions no-color">
            <input type="submit" value="Delete" class="btn btn-default" /> |
            @Html.ActionLink("Back to List", "Index")
        </div>
    }
</div>

Step 9: Add image folders
Add Image folders and sub folders (Deafult and Photo) as follows. Add blank photo in the Default folder to show blank photo for the employee.

Step 10: Add Employee link
Add Employee link in the nav bar of _Layout page as follows.

<ul class="nav navbar-nav">
    <li>@Html.ActionLink("Home", "Index", "Home")</li>
    <li>@Html.ActionLink("About", "About", "Home")</li>
    <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
    <li>@Html.ActionLink("Employee", "Index", "Employees")</li>
</ul>

Step 11: Run the application
Now run the application. Click Employee link in the nav bar. You can View, add, modify and delete employee information. If you go to the create or edit page, you have an option to upload photo and have option to crop the image and then save the image with employee information. Yes! You are done. Let’s cheers!