Click here to Skip to main content
15,881,967 members
Articles / Web Development / ASP.NET

Create an image cropping control

Rate me:
Please Sign up or sign in to vote.
4.93/5 (38 votes)
8 Jul 2008CPOL6 min read 198.1K   11.2K   119  
Create a custom ASP.NET 3.5 control with WebResources, client-side JS, and HTTPhandlers.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI.WebControls;
using System.Web.UI;
using System.Globalization;
using System.Web.UI.HtmlControls;
using System.Web.Caching;
using Anders.Web.Controls.Model;
using Anders.Web.Controls.Application;

namespace Anders.Web.Controls
{
    public class ImageCropper : CompositeControl
    {
        private Image image = new Image();
        private HiddenField cropCords = new HiddenField();
        private CropAreaCordinates cropArea = new CropAreaCordinates();

        protected override void CreateChildControls()
        {
            EnsureChildControls();

            image.ID = "cropImage";
            cropCords.ID = "cords";

            string v = Page.Request.Form[this.UniqueID + "$cords"];
            if (!string.IsNullOrEmpty(v))
            {
                string[] values = v.Split(';');
                cropArea.X = int.Parse(values[0]);
                cropArea.Y = int.Parse(values[1]);
                cropArea.Width = int.Parse(values[2]);
                cropArea.Height = int.Parse(values[3]);

                //This values are not saved in client hiddenfield, we retrive them from viewstate instead
                cropArea.MinWidth = MinWidth;
                cropArea.MinHeight = MinHeight;
            }

            Controls.Add(cropCords);
            Controls.Add(image);

            base.CreateChildControls();
        }

        protected override void OnPreRender(EventArgs e)
        {
            if (CropEnabled)
            {
                InitClientCrop();
                InitImages();

                float h = (float)CroppedImageHeight / (float)CroppedImageWidth;
                IFormatProvider culture = new CultureInfo("en-US", true);
                string height = h.ToString(culture);

                image.Attributes["onload"] = string.Format("InitCrop(this.id, {0},{1},{2},{3},{4},{5}, '{6}', {7}, {8}, {9});",
                    AllowQualityLoss ? 0 : cropArea.MinWidth,
                    AllowQualityLoss ? 0 : cropArea.MinHeight,
                    cropArea.X,
                    cropArea.Y,
                    cropArea.Width,
                    cropArea.Height,
                    cropCords.ClientID,
                    CaptureKeys.ToString().ToLower(),
                    MaintainAspectRatio ? 1 : 0,
                    MaintainAspectRatio ? height : "0"
                );                
                image.ImageUrl = string.Format("~/CropImage.axd?cropCacheId={0}", CacheKey);
                cropCords.Value = string.Format("{0};{1};{2};{3}", cropArea.X, cropArea.Y, cropArea.Width, cropArea.Height);
            }
            else
                image.Attributes.Remove("onload");
            base.OnPreRender(e);
        }

        protected override void Render(HtmlTextWriter writer)
        {
            if (CropEnabled)
            {
                PostBackOptions options = new PostBackOptions(cropCords);
                options.PerformValidation = false;
                Page.ClientScript.RegisterForEventValidation(options);
            }
            base.Render(writer);
        }

        /// <summary>
        /// Crops the current image with the selected crop area         
        /// </summary>
        public void Crop()
        {
            if (SourceImage != null && CropEnabled)
            {
                ImageManager imageManager = new ImageManager();
                CropData cropData = new CropData();
                cropData.Buffer = SourceImage;
                cropData.CropAreaCordinates = cropArea;
                imageManager.CropImageCropperImage(cropData, CroppedImageWidth, CroppedImageHeight, MaintainAspectRatio, JpegQuality);

                SetBuffer(string.Empty, cropData.Buffer);
                CropEnabled = false;
            }
        }

        /// <summary>
        /// Undo the crop and reset image.
        /// </summary>
        public void Undo()
        {
            CreateImage();
            CropEnabled = true;
        }
        
        /// <summary>
        /// Quality of the resulting jpeg
        /// </summary>
        public long JpegQuality
        {
            get { return (long)ViewState["jpegQuality"]; }
            set { ViewState["jpegQuality"] = value; }
        }

        /// <summary>
        /// Gets or sets if its ok for the user to crop the image resulting in a zoom
        /// </summary>
        public bool AllowQualityLoss
        {
            get { return ViewState["allowQualityLoss"] != null ? (bool)ViewState["allowQualityLoss"] : false; }
            set { ViewState["allowQualityLoss"] = value; }
        }

        /// <summary>
        /// Gets or sets if the control should force the user to crop with a ceratin aspect ratio (CroppedImageWidth and CroppedImageHeight)
        /// </summary>
        public bool MaintainAspectRatio
        {
            get { return ViewState["maintainAspectRatio"] != null ? (bool)ViewState["maintainAspectRatio"] : false; }
            set { ViewState["maintainAspectRatio"] = value; }
        }

        /// <summary>
        /// Getes or Sets the width of the desired result
        /// </summary>
        public int CroppedImageWidth
        {
            get { return ViewState["croppedImageWidth"] != null ? (int)ViewState["croppedImageWidth"] : 0; }
            set { ViewState["croppedImageWidth"] = value; }
        }

        /// <summary>
        /// Gets or Sets the height of the desired result
        /// </summary>
        public int CroppedImageHeight
        {
            get { return ViewState["croppedImageHeight"] != null ? (int)ViewState["croppedImageHeight"] : 0; }
            set
            {
                ViewState["croppedImageHeight"] = value;
            }
        }

        /// <summary>
        /// Gets or sets if the crop function should be enabled, if disabled the control will act as a normal image
        /// </summary>
        public bool CropEnabled
        {
            get { return ViewState["cropEnabled"] != null ? (bool)ViewState["cropEnabled"] : false; }
            set { ViewState["cropEnabled"] = value; }
        }

        /// <summary>
        /// Gets ors sets if the control should capture keys, if set to true arrow keys can be used to move the crop area.
        /// If you use multiple crop controls on a page disable this feature
        /// </summary>
        public bool CaptureKeys
        {
            get { return ViewState["captureKeys"] != null ? (bool)ViewState["captureKeys"] : false; }
            set { ViewState["captureKeys"] = value; }
        }

        /// <summary>
        /// Gets or sets the AlternateText of the displaying image
        /// </summary>
        public string AlternateText
        {
            get { return image.AlternateText; }
            set { image.AlternateText = value; }
        }

        /// <summary>
        /// Gets or sets the source image
        /// </summary>
        public byte[] SourceImage
        {
            get { return Page.Cache["source" + CacheKey.ToString()] as byte[]; }
            set { SetBuffer("source", value); }
        }

        /// <summary>
        /// Gets the image buffer if no crop has been made this will be the display image that is resized to the ImageWidth / ImageHeight
        /// If the image has been cropped this will be the cropped image
        /// </summary>
        public byte[] Image
        {
            get { return Page.Cache[CacheKey.ToString()] as byte[]; }
            private set { SetBuffer(string.Empty, value); }
        }

        private int MinWidth
        {
            get { return ViewState["minWidth"] != null ? (int)ViewState["minWidth"] : 0; }
            set { ViewState["minWidth"] = value; }
        }

        private int MinHeight
        {
            get { return ViewState["minHeight"] != null ? (int)ViewState["minHeight"] : 0; }
            set { ViewState["minHeight"] = value; }
        }

        private void InitImages()
        {
            if (SourceImage != null && Image == null)
            {
                CreateImage();
            }
        }

        /// <summary>
        /// Creates the image that is displayed for the client during cropping
        /// </summary>
        private void CreateImage()
        {
            ImageManager imageManager = new ImageManager();
            CropData cropData = imageManager.GetImageCropperDisplayImage(CroppedImageWidth, CroppedImageHeight, SourceImage, JpegQuality);
            cropArea = cropData.CropAreaCordinates;
            //Min width and min height is not sent to client's hiddenfield so we need to save this in viewstate.
            MinWidth = cropArea.MinWidth;
            MinHeight = cropArea.MinHeight;

            //Saves buffer to cache
            SetBuffer(string.Empty, cropData.Buffer);
        }

        /// <summary>
        /// Register all client side elements
        /// </summary>
        private void InitClientCrop()
        {
            string protoTypePath = this.Page.ClientScript.GetWebResourceUrl(typeof(ImageCropper),
                "Anders.Web.Controls.js.lib.prototype.js");
            string builderPath = this.Page.ClientScript.GetWebResourceUrl(typeof(ImageCropper),
                "Anders.Web.Controls.js.lib.builder.js");
            string dragdropPath = this.Page.ClientScript.GetWebResourceUrl(typeof(ImageCropper),
                "Anders.Web.Controls.js.lib.dragdrop.js");
            string cropperPath = this.Page.ClientScript.GetWebResourceUrl(typeof(ImageCropper),
                "Anders.Web.Controls.js.cropper.js");

            //Register client scripts
            this.Page.ClientScript.RegisterClientScriptInclude("prototype.js", protoTypePath);
            this.Page.ClientScript.RegisterClientScriptInclude("builder.js", builderPath);
            this.Page.ClientScript.RegisterClientScriptInclude("dragdrop.js", dragdropPath);
            this.Page.ClientScript.RegisterClientScriptInclude("cropper.js", cropperPath);

            //Adds css include to html head
            if (Page.Header.FindControl("cropCss") == null)
            {
                HtmlGenericControl cssMetaData = new HtmlGenericControl("link");
                cssMetaData.ID = "cropCss";
                cssMetaData.Attributes.Add("rel", "stylesheet");
                cssMetaData.Attributes.Add("href",
                    Page.ClientScript.GetWebResourceUrl(typeof(ImageCropper),
                    "Anders.Web.Controls.css.cropper.css"));
                cssMetaData.Attributes.Add("type", "text/css");
                cssMetaData.Attributes.Add("media", "screen");
                Page.Header.Controls.Add(cssMetaData);
            }
        }

        private void SetBuffer(string prefix, byte[] buffer)
        {
            if (CacheKey != Guid.Empty)
                Page.Cache.Remove(prefix + CacheKey.ToString());
            else
                SetCacheKey();

            AddBufferToCache(prefix + CacheKey.ToString(), buffer);
        }

        private void AddBufferToCache(string cacheId, byte[] buffer)
        {
            Page.Cache.Add(cacheId, buffer, null, Cache.NoAbsoluteExpiration, new TimeSpan(0, Page.Session.Timeout, 0), CacheItemPriority.Normal, null);
        }

        private Guid CacheKey
        {
            get { return ViewState["cacheId"] != null ? (Guid)ViewState["cacheId"] : Guid.Empty; }
            set { ViewState["cacheId"] = value; }
        }

        private void SetCacheKey()
        {
            CacheKey = Guid.NewGuid();
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Agero
Sweden Sweden
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions