Click here to Skip to main content
11,641,971 members (66,281 online)
Click here to Skip to main content

WPF Interactive Image Cropping Control

, 6 Sep 2007 264.5K 7.3K 140
Rate this:
Please Sign up or sign in to vote.
WPF Interactive Image Cropping Control

Contents

Introduction

Recently a friend of mine who has just started a company pointed me at this site which does lots of fancy image editing. Although he doesn't know XAML or Sliverlight he reckoned some of the stuff they were doing was excellent, and asked me to look into doing something similar in WPF. While this article represents only a small portion of what that website can do (namely image cropping) I feel that it outlines some useful techniques and study notes for those of you that may end up trying to do image editing applications in WPF/Silverlight. Although I cannot categorically state that 100% of this article will work with Silverlight as it has really been written in WPF, I am waiting to play with the managed version of Silverlight v1.1. JavaScript leaves me cold (nasty stuff). So after I've had a play with that I should be able to write WPF articles that I know will work with Silverlight. Till then, I'm afraid if you want a Silverlight version, you'll just have to try a code port for yourself.

So what is this article exactly? Well like I said, my friend asked me to create a posh image cropper in WPF. So that's really what it is. It's an image cropping control that may be placed within any other XAML and used to crop images. The cropping of the image is achieved by firstly drawing a shape and then moving the shape around to the desired position before completing and accepting the cropped image.

That's it, in a nutshell. It's a simple image cropper basically written in WPF.

Design Steps

I actually quite liked the image cropping here, so I wanted to create one as similar to that as I could. To this end my core function brief to myself was as follows:

  • Core Function 1 : It should support an owner drawn cropping area which was semi-transparent
  • Core Function 2 : The cropped area should be able to be moved
  • Core Function 3 : The user should be able to resize the crop area
  • Core Function 4 : The user should be able to accept or reject a particular crop operation

Those were the basic steps I wanted to cover. There were however a few more extended functions that I imposed on myself which are as follows:

  • Extended Function 1 : The cropping functionality should be wrapped up into a single re-usable WPF user control
  • Extended Function 2 : The user should be able to resize the image, in order to be able to see the entire image if dealing with a very large image

These were the tasks that I set myself for the purpose of this article. In the next section, I'll explain how I achieved or failed to achieve these tasks.

How It Works

So what I'll do now is explain each of the core/extended functions mentioned earlier.

Core Function 1 : Support a owner drawn cropping area

This was fairly easy to do, I simply subclassed the System.Windows.Controls.Canvas and overrode the mouse events such that when the mouse was moved, a new child UIElement was added to the new subclassed Canvas. Basically every time the user moved the mouse the new System.Windows.Shapes.Rectangle was either added or resized, using the mouse co-ordinates. This is a similar concept to the old tried and tested .NET 2.0 ControlPaint.DrawReversibleFrame() method. By subclassing the System.Windows.Controls.Canvas meant that this System.Windows.Controls.Canvas could be used within any code or XAML file.

A demonstration of this subclassed Canvas in action is shown below:

And the code that carries out this functionality is pretty simple and is shown below.

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace ImageCropper
{
    /// <span class="code-SummaryComment"><summary>
</span>

It can be seen that the crop area is actually a Rectangle. I initially had this to be a set color. But Josh Smith suggested that I change this to include a user allowable style dependency property. So I created a CropperStyle dependency property on the main UcImageCropper where both this canvas and the DragCanvas shown below are contained.

Core Function 2 : The cropped area should be able to be moved

Well, this was easy (really easy) as all I do is swap out the current selectionCanvas for a DragCanvas being careful to remove the current crop area (Rectangle) from the current selectionCanvas children collection, and add it to the children of the DragCanvas.

The reason this was so easy is that all the work had been done already by someone else, I simply saw an opportunity of how to use it. The original article is by Josh Smith and the particular article that I used is hosted right here at CodeProject. Its called Dragging Elements in a Canvas. So thanks for that Josh. I hope you like the use of it in this code.

Once the DragCanvas is in place, the user may then drag the crop area to wherever they like. When happy they may use the context menu (right click) to either save the image, or start over.

Core Function 3 : The crop area should be able to be resized

My immediate thought here was to use a System.Windows.Documents.Adorner. For those that have no idea what the heck I'm talking about here, to put it simply, adorners allow you to apply extra functionality to a UIElement such as rotation, sizing etc. There are a number of nice sources about this such as:

  • Adorners Samples MSDN
  • Adorners In WPF, for a nice simply intro

Unfortunately, as Josh Smith's DragCanvas uses the mouse events, and the nice MSDN ResizeAdorner sample also uses the mouse events, it was a bit of a battle to get them to work correctly. To this end I had to ditch the resizing of the crop area. But if anyone wants to give it a go, System.Windows.Documents.Adorners would be the way to go. My idea was simply to use a ResizeAdorner (MSDN) to adorn the current crop rectangle, and that way the user could not only drag (thanks to DragCanvas) but also resize. That was the idea anyway.

Core Function 4 : Accept / Reject crop

To allow the user to preview what the image would look like when it is cropped, there is a small popup which allows the user to either accept or reject the crop. If the user accepts, the cropped image will be used as the new source for the current image. If the user rejects the crop, the existing image will be used without any cropping being performed.

Extended Function 1 : Wrapped As A Control

Reuse is good. To this end I have wrapped up all this functionality into a single re-usable control called ucImageCropper which can be used in other XAML files.

The source code for the ucImageCropper is as shown below:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.IO;

#region Explanation of why this .NET3.0 app is using .NET2.0 Dlls
//For some very simple .NET niceties like being able to save a bitmap
//to a filename I have had to use the System.Drawing .NET 2.0 DLL
//
//While this looks possible using something like the following :
//
//RenderTargetBitmap rtb = new RenderTargetBitmap((int)img.width,
//(int)img, 0, 0, PixelFormats.Default);
//rtb.Render(this.inkCanv);
//BmpBitmapEncoder encoder = new BmpBitmapEncoder();
//encoder.Frames.Add(BitmapFrame.Create(rtb));
//encoder.Save(file);
//file.Close();
//
//For this to work I would have needed to used a .NET 3.0 CroppedBitmap
//within the RenderTargetBitmap.Render() method. And as CroppedBitmap
//doesn't inherit from Visual this is not possible.
//
//So if anyone knows how to do this better in .NET 3.0 I am all ears
#endregion
using System.Drawing;
using System.Drawing.Drawing2D;

//Josh Smith excellent DragCanvas
using WPF.JoshSmith.Controls;

namespace ImageCropper
{

    /// <span class="code-SummaryComment"><summary>
</span>

Extended Function 2 : Resizing source image

If you have a very large source image, you may want to revise it, so you can use the right click context menu (ONLY available while not in drag mode) which allows for 25, 50 and 100% sizing. Behind the scenes, all that is happening is that a System.Windows.Media.ScaleTransform is being applied. An example of this is as follows:

img.RenderTransform = new ScaleTransform(zoomFactor, zoomFactor, 0.5, 0.5)

How To Use It

Follow these steps:

  • Pick an image using the pick an image (top left) area
  • Scale using right click context menu (optional)
  • Draw a crop area using mouse (left button)
  • Move the crop area
  • Save or cancel using right click context menu
  • Start again

That's it

Although there is not that much code in this article, I had fun doing this one, and hope that it will be useful to someone out there.

So What Do You Think ?

I would just like to ask, if you liked the article please vote for it, and leave some comments, as it lets me know if the article was at the right level or not, and whether it contained what people need to know.

Conclusion

There's not too much to mention here as I think the rest of the article pretty much covers it. I suppose one thing to say would be that although I really love WPF, I found myself still needing to delve into .NET 2.0 to do some pixel level things. There is of course a System.Windows.Media.Imaging.CroppedBitmap within .NET 3.0, but this class did not offer the ability to save the image, so was not quite what I wanted. In terms of image filtering, WPF does actually offer some pixel level function by using the System.Windows.Media.Imaging.FormatConvertedBitmap class. But again not quite what I was after, as I wanted the cropping of my image to be persisted somewhere. So unless the .NET imaging classes were able to save to disk, I had to use the .NET Bitmap or Image classes instead.

History

  • v1.1 - 06/08/07 : Added Dependency Property for ucImageCropper, CropperStyle for the crop rectangle. Josh Smith suggested I do this.
  • v1.0 - 28/07/07 : Initial issue

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Sacha Barber
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

You may also be interested in...

Comments and Discussions

 
QuestionZoom doesn't work Pin
Milind R Chavan1-Jun-12 4:51
memberMilind R Chavan1-Jun-12 4:51 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150731.1 | Last Updated 6 Sep 2007
Article Copyright 2007 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid