Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

Create an image cropping control

, 8 Jul 2008 CPOL
Create a custom ASP.NET 3.5 control with WebResources, client-side JS, and HTTPhandlers.
Anders.Web.Controls.v1.1.zip
Anders.Web.Controls
Anders.Web.Controls.csproj.user
Application
bin
Debug
Anders.Web.Controls.dll
css
img
marqueeHoriz.gif
marqueeVert.gif
js
lib
Model
obj
Debug
Refactor
TempPE
Properties
Anders.Web.Test
Anders.Web.Test.csproj.user
App_Data
bin
Anders.Web.Controls.dll
Anders.Web.Test.dll
obj
Debug
Refactor
TempPE
Properties
sample.jpg
Anders.Web.Controls.v1.2.zip
Anders.Web.Controls.csproj.user
Anders.Web.Controls.dll
marqueeHoriz.gif
marqueeVert.gif
Anders.Web.Test.csproj.user
Anders.Web.Controls.dll
Anders.Web.Test.dll
sample.jpg
Anders.Web.Controls.zip
Anders.Web.Controls.csproj.user
Anders.Web.Controls.dll
marqueeHoriz.gif
marqueeVert.gif
Anders.Web.Test.csproj.user
Anders.Web.Controls.dll
Anders.Web.Test.dll
sample.jpg
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;
using System.Web.Configuration;
using System.Web;

namespace Anders.Web.Controls
{
    public class ImageCropper : CompositeControl, IPostBackDataHandler
    {
        private Image image = new Image();
        private HiddenField cropCords = new HiddenField();
        private CropAreaCordinates cropArea = new CropAreaCordinates();
        private static string httpHandlerPath = null;

        protected override void CreateChildControls()
        {
            EnsureChildControls();
            CheckForHandler();
            
            image.ID = "cropImage";
            cropCords.ID = "cords";

            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("{0}?cropCacheId={1}", httpHandlerPath, CacheKey);
                cropCords.Value = string.Format("{0};{1};{2};{3}", cropArea.X, cropArea.Y, cropArea.Width, cropArea.Height);
                Page.RegisterRequiresPostBack(this);
            }
            else
                image.Attributes.Remove("onload");

            if (SourceImage == null)
            {
                this.Controls.Remove(image);
                image.Dispose();
            }

            base.OnPreRender(e);
        }

        /// <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);

                Image = 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 && ImageChanged)
            {
                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
            Image = 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);
            ImageChanged = true;
        }

        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 bool ImageChanged
        {
            get;
            set;
        }

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

        /// <summary>
        /// This method checks that the http handler is set, if it's not set the method will throw an exeption. 
        /// If it is set the httpHandlerPath will be set to the one specified in the config
        /// </summary>
        private void CheckForHandler()
        {
            if (httpHandlerPath == null)
            {
                HttpHandlersSection handlerSection = WebConfigurationManager.GetWebApplicationSection("system.web/httpHandlers") as HttpHandlersSection;
                bool foundHandler = false;
                Type type = typeof(ImageCropperHttpHandler);
                string handlerName = type.ToString();
                string fullHandlerName = type.AssemblyQualifiedName;
                foreach (HttpHandlerAction action in handlerSection.Handlers)
                    if (action.Type == handlerName || action.Type == fullHandlerName)
                    {
                        foundHandler = true;
                        httpHandlerPath = (action.Path.StartsWith("~") ? string.Empty : "~/") + action.Path;
                        break;
                    }

                if (!foundHandler)
                    throw new ApplicationException(string.Format("The HttpHandler {0} is not registered in the web.config", handlerName));
            }
        }

        public bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
        {
            string v = postCollection[postDataKey + "$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;
                return true;
            }
            else
                return false;            
        }

        public void RaisePostDataChangedEvent()
        {

        }
    }
}

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)

Share

About the Author

Anders Malmen
Software Developer Agero
Sweden Sweden
No Biography provided

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150414.1 | Last Updated 9 Jul 2008
Article Copyright 2008 by Anders Malmen
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid