There are already some good articles on clipping out there, so why another? One of the really cool things I like about cropping in Photoshop is that the important part of the image (the part left after the crop) is the unobscured portion and the unimportant part (the part to be discarded) is the obscured portion. This makes it easier to see what the final result will be. The croppers I've seen so far do the precise opposite of this. It's easier that way and for example purposes, probably the best way to handle things, but I really wanted to make a useful cropper so I made mine work ala Photoshop. Some of the other croppers (in fact, all as far as I know) work not as general adorners, but as special purpose programs built on top of a specific bitmap. I wanted something that would clip anything, including containers and controls in WPF so I made my cropper as a general adorner and produce a bitmap of whatever is beneath the adorner.
WPF is great at vector graphics, but dealing with bitmaps can be much more tricky. Some of the other croppers have gone to the extent to write out temporary files to produce their cropped bitmaps. I've used the
CroppedBitmap to do this very quickly in memory so that the cropped portion can be shown interactively as you manipulate the cropping region.
I've included a routed event for the clipping area changing and the color is a standard WPF dependency property. The clipper could also be used without ever retrieving the bitmap underneath it to indicate a portion of a bitmap or even in a container, although it would seem like the cropping would be the normal usage. Finally, it includes as a bonus a
PuncturedRectshape which is just a rect with another rect poked out of the middle.
Shapes, Adorners and Other Trivia
There are two potentially usable products in this package -
PuncturedRect, a custom shape used for the masking portion of the clip, and
CroppingAdorner, the actual adorner.
Initially, a custom shape sounds like nothing special. After all, you can always create shapes of any sort by using paths. True enough, but if you've got a general class of shapes, it can be much nicer to package the functionality into a custom shape than to constantly reform them from a series of paths. More importantly, by creating custom shapes, you can expose dependency properties and use them much more easily in XAML and data bindings.
PuncturedRectis a fairly simple shape and perhaps serves as a good example of a custom shape and shape creation. It exposes two dependency properties,
InteriorRect. The result is a rectangle given by
ExteriorRectwith a hole "punctured" in it by the rectangle given in
InteriorRect. In the
CroppingAdorner, this shape is used for the cropping mask. While it served its purpose well in that regard, its usage outside that is probably more pedagogical than useful in actual products. Still, it could potentially be used, for instance, as a frame around underlying controls/images.
The main feature of this article, however, is the
CroppingAdorner. It's the first adorner I've written and I've learned much in its implementation. While it superficially resembles the classic resizing adorner as outlined very well in this article (whose author I would like to credit, but is listed only as "Me" in the blog entries), it actually turned out to be much different and much more difficult. Since any manipulation of the thumbs in the resizing container causes a resize on the adorned element (duh) they also result in a new layout for that element. Since the thumbs are placed during this layout phase, they can be reliably positioned. Therein lies the rub. In the cropping adorner, manipulating the thumbs does not cause a new call to layout the control, hence no hook at layout time suffices to move the thumbs interactively, but we can't normally just direct the thumbs to place themselves "just anywhere" on the adorned control. WPF decides where the controls go. The only place you can set control positions where you like outside of the arrangement phase is on a canvas. Consequently, instead of having all the thumbs be in the visual tree of the adorner, I placed a single unmoving canvas in the tree and placed all the thumbs on the canvas.
Thus, the adorner is actually a control with two children in its visual tree - the
PuncturedRectwhich forms the mask and the canvas which all the thumbs sit on. Once you realize that this is the way things need to be set up, the rest of displaying the control is pretty straightforward.
Using the Code
As discussed above, the adorner is composed of two children in its visual tree. The first is the
PuncturedRect representing the crop mask and the second is the canvas which contains the thumbs. There is a separate class for the thumbs called
CropThumbsand derived from
Thumbwhich mainly sets the appearance of the thumbs. Their behavior is unmodified from
Thumb. When a thumb is moved, it produces a message which gives the delta by which the thumb was moved. Manipulation of the cropping rectangle can be arranged by simply adding multiples of this delta to the sides of the rectangle. Thus, the behavior for a particular thumb can be characterized by the multiples it adds to each of these sides. The
ThumbMultipliersstructure is designed to hold these multiples. Each thumb stores a
ThumbMultiplerin its tag. For instance, the top right thumb has a
(0, 1, 1, -1) meaning that its
x delta should be multiplied by
0 and added to the left side, its
y delta should be multiplied by
1 and added to the top, its
x delta should be multiplied by
1 and added to the width and its
y delta should be multiplied by
-1 and added to the height. By doing this, we can handle all the thumb movements in one handler which references this tag:
private void HandleThumb(object sender, DragDeltaEventArgs args)
CropThumb crt = sender as CropThumb;
if (crt != null)
Rect rcCrop = _prCropMask.RectInterior;
ThumbMultipliers tmlt = (ThumbMultipliers)crt.Tag;
rcCrop = tmlt.Apply(rcCrop, args.HorizontalChange, args.VerticalChange);
_prCropMask.RectInterior = rcCrop;
RaiseEvent(new RoutedEventArgs(CropChangedEvent, this));
publicmethod on the
CropAdornerother than its constructor is the routine to actually extract a
BitmapSourcerepresenting the crop area. In order to do this, we retrieve a bitmap of the adorned element using a
RenderTargetBitmap. We need to know the width and height of the
RenderTargetBitmapin pixels, so we need to convert from WPF units to pixels. Once we have retrieved a
RenderTargetBitmapwith the adorned element's bitmap image, we need to extract the cropped part we're interested in. We use the
CroppedBitmapobject for this.
CroppedBitmapneeds also to have pixel coordinates for the rectangle it's going to crop.
BitmapSourceso we can return it as our final result. The method to achieve all this is given below:
public BitmapSource BpsCrop()
Thickness margin = AdornedElementMargin();
Rect rcInterior = _prCropMask.RectInterior;
Point pxFromPos = UnitsToPx(rcInterior.Left +
margin.Left, rcInterior.Top + margin.Top);
Point pxFromSize = UnitsToPx(rcInterior.Width, rcInterior.Height);
Point pxWhole = UnitsToPx(AdornedElement.RenderSize.Width +
margin.Left, AdornedElement.RenderSize.Height + margin.Left);
pxFromSize.X = Math.Max(Math.Min(pxWhole.X - pxFromPos.X, pxFromSize.X), 0);
pxFromSize.Y = Math.Max(Math.Min(pxWhole.Y - pxFromPos.Y, pxFromSize.Y), 0);
if (pxFromSize.X == 0 || pxFromSize.Y == 0)
System.Windows.Int32Rect rcFrom = new System.Windows.Int32Rect
(pxFromPos.X, pxFromPos.Y, pxFromSize.X, pxFromSize.Y);
RenderTargetBitmap rtb = new RenderTargetBitmap
(pxWhole.X, pxWhole.Y, s_dpiX, s_dpiY, PixelFormats.Default);
return new CroppedBitmap(rtb, rcFrom);
Adorners are a bit trickier to install than you might think. For one thing, adorners live where nobody else lives - in
AdornerLayerobjects are invisible things that sit above the item being adorned. In order to adorn an object, you have to find its
AdornerLayerand install the adorner there. The code looks like this:
AdornerLayer aly = AdornerLayer.GetAdornerLayer(fel);
_crp = new CroppingAdorner(fel, rcInterior);
CroppingAdorneralso supplies a routed event,
CropChangedwhich fires whenever the cropping area changes, and a readonly property
ClippingRectanglewhich gives the current clipping rectangle.
Ultimately, the best way of understanding how to use the adorner is to check out the code in the test project.
- 24th January, 2008: Initial submission