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.
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.
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.
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
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.
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:
#region Explanation of why this .NET3.0 app is using .NET2.0 Dlls
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)
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
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.
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.
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
Image classes instead.
- v1.1 - 06/08/07 : Added Dependency Property for
CropperStyle for the crop rectangle. Josh Smith suggested I do this.
- v1.0 - 28/07/07 : Initial issue