This is the second (and the last) post discussing cropping images and saving them to the SQL Server database, using C# ASP.NET and jQuery library called Jcrop. In my previous post, I have created a simple database model that can be used for this purpose, so if you haven’t checked it out, please do. Also, I won’t get into details with Jcrop, since its website is very informative and contains a good manual, as well as demos.
I’ve made a simple demo which can be downloaded here, and I’ll quickly guide you through it, so it could be easily understood. The idea is the following: user clicks on ‘Choose file’ button (which opens FileUpload control), picks an image on their computer and clicks on ‘Upload’ button. At that point, the image is uploaded to the app server and saved to a folder in the file system(in this demo, it is TempImages folder). User can then see the uploaded picture and do the cropping, after which ‘Crop’ button should be clicked. Then, a new bitmap will be created, containing only the area of the original photo selected as the crop area. The bitmap will then be saved to the database as the array of bytes.
FIrst of all, here is the aspx page:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="JcropDemo._Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Jcrop Demo</title> <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/jquery-ui.min.js"></script> <script type="text/javascript" src="js/jquery.Jcrop.min.js"></script> <link rel="Stylesheet" type="text/css" href="css/jquery.Jcrop.min.css" /> <link rel="Stylesheet" type="text/css" href="css/custom.css" /> <script type="text/javascript"> jQuery(document).ready(function() { $preview = $('#preview_pane'), $pcnt = $('#preview_pane .preview_container'), $pimg = $('#preview_pane .preview_container img'), xsize = $pcnt.width(), ysize = $pcnt.height(); jQuery(window).load(function() { jQuery($('img[bid=EditedImage]')).Jcrop({ onChange: doPreviewSave, onSelect: doPreviewSave, boxWidth: 600, boxHeight: 400, aspectRatio: xsize / ysize }, function() { // Use the API to get the real image size var bounds = this.getBounds(); boundx = bounds[0]; boundy = bounds[1]; // Store the API in the jcrop_api variable jcrop_api = $('img[bid=EditedImage]'); //this; // Move the preview into the jcrop container for css positioning $preview.appendTo(jcrop_api.ui.holder); }); }); function doPreviewSave(c) { jQuery($('[id$=HIDDENX]')).val(c.x); jQuery($('[id$=HIDDENY]')).val(c.y); jQuery($('[id$=HIDDENW]')).val(c.w); jQuery($('[id$=HIDDENH]')).val(c.h); if (parseInt(c.w) > 0) { var rx = xsize / c.w; var ry = ysize / c.h; $pimg.css({ width: Math.round(rx * boundx) + 'px', height: Math.round(ry * boundy) + 'px', marginLeft: '-' + Math.round(rx * c.x) + 'px', marginTop: '-' + Math.round(ry * c.y) + 'px' }); } }; }); </script> </head> <body> <form id="form1" runat="server"> <div> <asp:Panel ID="CurrentImagePanel" runat="server"> <fieldset> <legend id="CurrentPhotoLegend" runat="server"></legend> <p> <asp:Image ID="UserImage" runat="server" CssClass="user_photo_large" /> <asp:FileUpload ID="ImageUpload" runat="server" /> </p> <p> <asp:Button ID="ImageUploadButton" runat="server" onclick="ImageUploadButton_Click" Text="Upload"/> </p> <p> <asp:Label ID="ImageUploadErrorLabel" runat="server"></asp:Label> </p> </fieldset> </asp:Panel> <asp:Panel ID="EditedImagePanel" runat="server"> <fieldset> <legend><asp:Label ID="EditedPhotoLabel" runat="server"></asp:Label></legend> <asp:Image ID="EditedImage" runat="server" bid="EditedImage" /> <div id="preview_pane"> <div class="preview_container"> <asp:Image ID="PreviewImage" runat="server" CssClass="jcrop-preview" /> </div> </div> <p style="clear:both"> <asp:Button ID="CropImageButton" runat="server" OnClientClick="return checkCroppedImage();" onclick="CropImageButton_Click" CssClass="custom_button3" Text="Crop"/> <asp:HiddenField ID="HIDDENX" runat="server" /> <asp:HiddenField ID="HIDDENY" runat="server" /> <asp:HiddenField ID="HIDDENW" runat="server" /> <asp:HiddenField ID="HIDDENH" runat="server" /> </p> </fieldset> </asp:Panel> </div> </form> </body> </html>
So, we have to add references to jQuery and jQueryui, as well as to Jcrop js and css files. There are two panels, CurrentImagePanel, shown before the image has been selected, and EditedImagePanel, where the cropping will take place. Hidden fields HIDDENX, HIDDENY, HIDDENW and HIDDENH are used to store coordinates of the rectangle representing new (cropped) image. On window load, Jcrop’s default behavior is activated. When the selection is made, hidden fields are populated with its values (check doPreviewSave function).
Code behind:
using System; using System.Linq; using System.IO; using System.Web; namespace JcropDemo { public partial class _Default : System.Web.UI.Page { private string[] _AllowedExtensions = { ".png", ".jpeg", ".jpg", ".gif" }; private string _TempImageLocation = "TempImages\\"; private string _ImagePath = HttpContext.Current.Request.PhysicalApplicationPath + "TempImages\\"; private int _UserID = 1; protected void Page_Load(object sender, EventArgs e) { if (Session["WorkingImage"] != null) { EditedImagePanel.Visible = true; CurrentImagePanel.Visible = false; PreviewImage.ImageUrl = EditedImage.ImageUrl = _TempImageLocation + (string)Session["WorkingImage"]; } else { EditedImagePanel.Visible = false; CurrentImagePanel.Visible = true; UserImage.ImageUrl = _TempImageLocation + "noimage.jpg"; } } protected void ImageUploadButton_Click(object sender, EventArgs e) { string imageName = ""; if (ImageUpload.HasFile) { string extension = System.IO.Path.GetExtension(ImageUpload.FileName).ToLower(); if (_AllowedExtensions.Contains(extension)) { try { imageName = string.Format("{0}_{1}{2}", _UserID, DateTime.Now.ToFileTimeUtc(), extension); ImageUpload.SaveAs(_ImagePath + imageName); EditedImagePanel.Visible = true; CurrentImagePanel.Visible = false; PreviewImage.ImageUrl = EditedImage.ImageUrl = _TempImageLocation + imageName; Session["WorkingImage"] = imageName; } catch (Exception ex) { ImageUploadErrorLabel.Text = "Image could not be uploaded. Details: " + ex.Message; } } else { ImageUploadErrorLabel.Text = "Wrong extension!!!"; } } } protected void CropImageButton_Click(object sender, EventArgs e) { if (Session["WorkingImage"] == null) return; string imageName = Session["WorkingImage"].ToString(); int w = Convert.ToInt32(Math.Floor(Convert.ToDouble(HIDDENW.Value))); int h = Convert.ToInt32(Math.Floor(Convert.ToDouble(HIDDENH.Value))); int x = Convert.ToInt32(Math.Floor(Convert.ToDouble(HIDDENX.Value))); int y = Convert.ToInt32(Math.Floor(Convert.ToDouble(HIDDENY.Value))); byte[] cropImage = Crop(_ImagePath + imageName, w, h, x, y); BLL.SaveUserProfilePhoto(_UserID, cropImage); File.Delete(_ImagePath + imageName); Session["WorkingImage"] = null; EditedImagePanel.Visible = false; CurrentImagePanel.Visible = true; } static byte[] Crop(string Img, int Width, int Height, int X, int Y) { try { using (System.Drawing.Image originalImage = System.Drawing.Image.FromFile(Img)) { using (System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(Width, Height)) { bmp.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution); using (System.Drawing.Graphics Graphic = System.Drawing.Graphics.FromImage(bmp)) { Graphic.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; Graphic.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; Graphic.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; Graphic.DrawImage(originalImage, new System.Drawing.Rectangle(0, 0, Width, Height), X, Y, Width, Height, System.Drawing.GraphicsUnit.Pixel); MemoryStream ms = new MemoryStream(); bmp.Save(ms, originalImage.RawFormat); return ms.GetBuffer(); } } } } catch (Exception Ex) { throw (Ex); } } } }
In order for this example to be as simple as possible, there is only one user that the image would be added to, hence the variable _UserID is set to 1. In the ImageUploadButton_Click event, the image is saved to a folder on the server. In CropImageButton_Click, the points of the cropped rectangle (represented in the hidden fields) are used to create array of bytes that is later saved to the database. On successful save, the original image is deleted from the file system.
I’ve put the business logic into separate file:
using System; using System.Configuration; using System.Linq; using System.Web; namespace JcropDemo { public class BLL { public static Model Model { get { if (!HttpContext.Current.Items.Contains("Model")) HttpContext.Current.Items.Add("Model", new Model()); return (Model)HttpContext.Current.Items["Model"]; } } public static void SaveUserProfilePhoto(int userID, byte[] photoContent) { User usr = Model.Entities.User.Where(x => x.UserId == userID).FirstOrDefault(); if (usr != null) { Image img = new Image(); img.ImageContent = photoContent; img.User.Add(usr); Model.Entities.AddToImage(img); Model.Entities.SaveChanges(true); } } } public class Model { private TestDbEntities _Entities = null; public TestDbEntities Entities { get { if (_Entities == null) { if (ConfigurationManager.ConnectionStrings["TestDbEntities"] == null) { throw new NullReferenceException("Missing connection string!"); } _Entities = new TestDbEntities(ConfigurationManager.ConnectionStrings["TestDbEntities"].ConnectionString); } return _Entities; } } } }
Static method SaveUserProfilePhoto uses EF model to save the data to the SQL Server database.
So, once again: you can download the source code here, and remember to change your connection settings in Web.config!
Hope this example is useful, and I’ll be glad to get your feedback on it.