|
|
Comments and Discussions
|
|
 |

|
Darrellp,
You did an awesome job.
I have one question. I would like the adorner to resize uniformly. In other words, like a square.
I would appreciate your input on this.
Thanks in advance.
|
|
|
|

|
I think I'd probably subclass PuncturedRect to make Punctured Square and ensure that the CoerceRectInterior forced the width and height to be the same - probably the min of the width/height in the passed in rectangle. I think you're still going to have to fiddle with the placing of the upper left corner properly in the main code. Shouldn't be too awful hard.
|
|
|
|

|
Thank you.
While trying the code, I noticed something. I'm loading my images onto a square image container. If I load an image, which is not square, in other words, it's hight is bigger, the adorned does not crop the image properly. It cuts one of the sides. Any ideas.
Thank you for your reply.
|
|
|
|

|
Hi, its a great article, I have implemented this in one of my projects. But I have a question when I crop the image the image quality reduces. Is there anything I can do to maintain the image quality?
|
|
|
|

|
Any time you increase the size of an image you inevitably lose image quality and there's no way around it. I can only imagine that this is the degradation you're referring to. If the cropped area and the area you're displaying it in are exactly the same size there should be no degradation. This can't be eliminated, but there are ways of mitigating it using antialiasing. Look at this site: http://msdn.microsoft.com/en-us/library/k0fsyd4e.aspx[^]. Hope this helps.
|
|
|
|

|
Simple to understand and a great piace of work done!
|
|
|
|

|
Thank you for the nice article!
modified 26 Jan '12 - 10:56.
|
|
|
|

|
Is there a way I could use this on a winform project? Amazing work BTW!
|
|
|
|

|
No - not really. It uses a lot of WPF specific stuff - decorators, pens which copy from arbitrary bitmaps on a contrl, etc.. I don't think there's any good way of transferring all of that stuff over.
|
|
|
|

|
Exactly what I wanted, thanks very muchly.
|
|
|
|

|
I have an issue when I set my Image container to Stretch="Uniform". The cropped portion is not the same as what is in the selection. any ideas?
|
|
|
|

|
I had the same problem. On a whim and being slightly peeved I wrapped my Image element in a Canvas element, and set the MaxHeight and MaxWidth attributes on the Image. This fixed the problem. Might be worth a try.
|
|
|
|

|
Brilliant Work.
I have just added the functionality for Maintaining Aspect Ratio if anyone is interested...
modified on Monday, March 14, 2011 2:31 AM
|
|
|
|

|
Yes please, that would be very useful
|
|
|
|

|
Here I define a target size: Only the ratio of the two sides will be used.
private Size m_TargetSize = new Size(640, 480);
public Size TargetSize
{
get { return m_TargetSize; }
set { m_TargetSize = value; }
}
Here I define which edge is used.
public enum Edge
{
None = 0,
Horizontal = 0x10,
Vertical = 0x20,
NotCorner = 0x100,
Top = 0x01 | Vertical,
Bottom = 0x02 | Vertical,
Left = 0x04 | Horizontal,
Right = 0x08 | Horizontal,
TopLeft = Top | Left | NotCorner,
TopRight = Top | Right | NotCorner,
BottomLeft = Bottom | Left | NotCorner,
BottomRight = Bottom | Right | NotCorner,
};
Calculate new size based on the input size and target size.
The dominiant edge is used as master.
public static Size CalculateSize(Size targetSize, Size input, Edge edge)
{
double outputWidth = 0;
double outputHeight = 0;
if ((edge & Edge.Horizontal) == Edge.Horizontal)
{
double ratio = targetSize.Height / targetSize.Width;
outputWidth = input.Width;
outputHeight = input.Width * ratio;
}
else if ((edge & Edge.Vertical) == Edge.Vertical)
{
double ratio = targetSize.Height / targetSize.Width;
outputWidth = input.Height / ratio;
outputHeight = input.Height;
}
return new Size(outputWidth, outputHeight);
}
And here I have modified the HandleThumb function to maintain aspect ratio.
private void HandleThumb(
double drcL,
double drcT,
double drcW,
double drcH,
double dx,
double dy, Edge edge)
{
Rect rcInterior = _prCropMask.RectInterior;
if (rcInterior.Width + drcW * dx < 0)
{
dx = -rcInterior.Width / drcW;
}
if (rcInterior.Height + drcH * dy < 0)
{
dy = -rcInterior.Height / drcH;
}
double differenceLeft = drcL * dx;
double differenceTop = drcT * dy;
double differenceWidth = drcW * dx;
double differenceHeight = drcH * dy;
rcInterior = new Rect(
rcInterior.Left + differenceLeft,
rcInterior.Top + differenceTop,
rcInterior.Width + differenceWidth,
rcInterior.Height + differenceHeight);
Console.WriteLine(rcInterior);
if (MaintainAspectRatio)
{
Edge dominantEdge = Edge.None;
if ((edge & Edge.NotCorner) == Edge.NotCorner)
dominantEdge = Edge.Horizontal;
else
dominantEdge = edge;
Size newSize = CalculateSize(TargetSize, rcInterior.Size, dominantEdge);
if ((edge & Edge.Top) == Edge.Top)
{
double newTop = rcInterior.Top + rcInterior.Height - newSize.Height;
rcInterior.Location = new System.Windows.Point(rcInterior.Left, newTop);
}
if ((edge & Edge.Left) == Edge.Left)
{
double newLeft = rcInterior.Left + rcInterior.Width - newSize.Width;
rcInterior.Location = new System.Windows.Point(newLeft, rcInterior.Top);
}
rcInterior.Height = newSize.Height;
rcInterior.Width = newSize.Width;
}
_prCropMask.RectInterior = rcInterior;
SetThumbs(_prCropMask.RectInterior);
RaiseEvent(new RoutedEventArgs(CropChangedEvent, this));
}
That's all.
I hope it helps to make this great control even better.
Have a nice life!!
|
|
|
|

|
Hey Joe! Thanks for the addition and good job!
|
|
|
|

|
THIS PROJECT IS AWESOME. THANK YOU SO MUCHHHHHIEEEEE btw, There's one bug in the project done by me that I couldn't solve. How to fix a border around the rectangle9for cropping) so that it doesn't exceed the size of the image box during zoomming?
Thanks!
|
|
|
|

|
Hi,
Very nice work, got it working in a snap.
I have one problem though, when the image is resized the adoner stays at its own location. I've looked for a way to change the adoner size pragmatically but there aren't much public methods. As the adoner layer is part of the image I can only guess that there is an integrated way to handle the resize issue without too much hassle, any idea?
Gilad.
|
|
|
|

|
There is the AdornedElement_SizeChanged method which gets called whenever the element underneath changes size. Right now it's used to make sure that the adorner stays within the bounds of it's parent element, but I'm sure you could add some code to change the crop region however you'd like. I'm not sure what you want it to do. I think when it's over an image, if the image changes proportionately, it may make sense to change the adorner size proportionately also, but images don't necessarily change size proportionally (though, admittedly, mine does). Also, when it's over a random Framework element, it's impossible to tell how the underlying element may react to size changes (since it can override them and do whatever it likes) so in general, it's impossible to know what to do here. Perhaps the generic solution would be to make an overridable procedure for size changes and allow the user to subclass the adorner, overriding the size change routine. If I were to rewrite it to solve that problem, that's probably what I'd do. I'd rather make my code more general and then override that generality in special cases than to make it more specific for a particular special case. If you've got only one thing you'll ever use it for, though, I suppose just modifying AdornedElement_SizeChanged would work too.
|
|
|
|

|
Thanks for your help.. I did the change as you suggested and attached it so maybe someone else is interested.
private void AdornedElement_SizeChanged(object sender, SizeChangedEventArgs e) {
FrameworkElement fel = sender as FrameworkElement;
Rect rcInterior = _prCropMask.RectInterior;
bool fFixupRequired = false;
double
intLeft = rcInterior.Left,
intTop = rcInterior.Top,
intWidth = rcInterior.Width,
intHeight = rcInterior.Height;
double HeightRatio = e.NewSize.Height / e.PreviousSize.Height;
double WidthRatio = e.NewSize.Width / e.PreviousSize.Width;
if (HeightRatio != 1.0) {
intTop *= HeightRatio;
intHeight *= HeightRatio;
fFixupRequired = true;
}
if (WidthRatio != 1.0) {
intLeft *= WidthRatio;
intWidth *= WidthRatio;
fFixupRequired = true;
}
if (rcInterior.Left > fel.RenderSize.Width) {
intLeft = fel.RenderSize.Width;
intWidth = 0;
fFixupRequired = true;
}
if (rcInterior.Top > fel.RenderSize.Height) {
intTop = fel.RenderSize.Height;
intHeight = 0;
fFixupRequired = true;
}
if (rcInterior.Right > fel.RenderSize.Width) {
intWidth = Math.Max(0, fel.RenderSize.Width - intLeft);
fFixupRequired = true;
}
if (rcInterior.Bottom > fel.RenderSize.Height) {
intHeight = Math.Max(0, fel.RenderSize.Height - intTop);
fFixupRequired = true;
}
if (fFixupRequired) {
_prCropMask.RectInterior = new Rect(intLeft, intTop, intWidth, intHeight);
}
}
|
|
|
|

|
man, I've been studying your codes for almost a week now but I can't still figure out how I can make it draggable. Can you teach me how?
|
|
|
|

|
I'm not sure what you mean by "draggable". You can drag out the corners and sides. Do you want to click in the middle and drag the cropping area around? It shouldn't be that hard - just simulate what I'm doing to translate two opposite corners and it should drag. I haven't looked at the code in a million years (despite the fact that I wrote it only a few years ago - imagine that!) but that's certainly where I'd start.
|
|
|
|

|
I also need to make the selection "Draggable"... I have tried to implement it, but it is proving very difficult to understand. Can you help with this task? How can I RectInterior draggable?
|
|
|
|

|
eureka!
"drag and drop" in selector is working...
public CroppingAdorner(UIElement adornedElement, Rect rcInit)
: base(adornedElement)
{
_vc = new VisualCollection(this);
_prCropMask = new PuncturedRect();
_prCropMask.IsHitTestVisible = false;
_prCropMask.RectInterior = rcInit;
_prCropMask.Fill = Fill;
_vc.Add(_prCropMask);
_cnvThumbs = new Canvas();
_cnvThumbs.HorizontalAlignment = HorizontalAlignment.Stretch;
_cnvThumbs.VerticalAlignment = VerticalAlignment.Stretch;
_vc.Add(_cnvThumbs);
BuildCorner(ref _crtTop, Cursors.SizeNS);
BuildCorner(ref _crtBottom, Cursors.SizeNS);
BuildCorner(ref _crtLeft, Cursors.SizeWE);
BuildCorner(ref _crtRight, Cursors.SizeWE);
BuildCorner(ref _crtTopLeft, Cursors.SizeNWSE);
BuildCorner(ref _crtTopRight, Cursors.SizeNESW);
BuildCorner(ref _crtBottomLeft, Cursors.SizeNESW);
BuildCorner(ref _crtBottomRight, Cursors.SizeNWSE);
// Add handlers for Cropping.
_crtBottomLeft.DragDelta += new DragDeltaEventHandler(HandleBottomLeft);
_crtBottomRight.DragDelta += new DragDeltaEventHandler(HandleBottomRight);
_crtTopLeft.DragDelta += new DragDeltaEventHandler(HandleTopLeft);
_crtTopRight.DragDelta += new DragDeltaEventHandler(HandleTopRight);
_crtTop.DragDelta += new DragDeltaEventHandler(HandleTop);
_crtBottom.DragDelta += new DragDeltaEventHandler(HandleBottom);
_crtRight.DragDelta += new DragDeltaEventHandler(HandleRight);
_crtLeft.DragDelta += new DragDeltaEventHandler(HandleLeft);
//add eventhandler to drag and drop
adornedElement.MouseLeftButtonDown += new MouseButtonEventHandler(Handle_MouseLeftButtonDown);
adornedElement.MouseLeftButtonUp += new MouseButtonEventHandler(Handle_MouseLeftButtonUp);
adornedElement.MouseMove += new MouseEventHandler(Handle_MouseMove);
// We have to keep the clipping interior withing the bounds of the adorned element
// so we have to track it FrameworkElement fel = adornedElement as FrameworkElement;
if (fel != null)
{
fel.SizeChanged += new SizeChangedEventHandler(AdornedElement_SizeChanged);
}
}
#region Drag and drop handlers
Double OrigenX;
Double OrigenY;
// generic handler move selection with Drag private void HandleDrag(double dx, double dy)
{
Rect rcInterior = _prCropMask.RectInterior;
rcInterior = new Rect(
dx ,
dy ,
rcInterior.Width ,
rcInterior.Height);
_prCropMask.RectInterior = rcInterior;
SetThumbs(_prCropMask.RectInterior);
RaiseEvent(new RoutedEventArgs(CropChangedEvent, this));
}
private void Handle_MouseMove(object sender, MouseEventArgs args)
{
Image Marco = sender as Image;
if (Marco != null && Marco.IsMouseCaptured)
{
Double x = args.GetPosition(Marco).X; //posición actual cursor
Double y = args.GetPosition(Marco).Y;
Double _x = _prCropMask.RectInterior.X; // posición actual esquina superior izq del marco interior
Double _y = _prCropMask.RectInterior.Y;
Double _width = _prCropMask.RectInterior.Width; //dimensiones del marco interior
Double _height = _prCropMask.RectInterior.Height;
//si el click es dentro del marco interior
if (((x > _x) && (x < (_x + _width))) && ((y > _y) && (y < (_y + _height))))
{
//calculamos la diferencia de la posición actual del cursor con respecto al punto de origen del arrastre
//y se la añadimos a la esquina sup. izq. del marco interior.
_x =_x + ( x - OrigenX);
_y = _y + (y - OrigenY );
//comprobamos si es posible mover sin salirse del marco exterior por ninguna de sus dimensiones
//no supera el borde izquierdo de la imagen: !(_x < 0)
if (_x < 0)
{
_x = 0;
}
//no supera el borde derecho de la imagen: !((_x + _width) > Marco.Width)
if ((_x + _width) > Marco.ActualWidth)
{
_x = Marco.ActualWidth - _width;
}
//no supera el borde superior de la imagen: !(_y<0)
if (_y < 0)
{
_y = 0;
}
//no supera el borde inferior de la imagen: !((_y + _height) > Marco.Height)
if ((_y + _height) > Marco.ActualHeight)
{
_y = Marco.ActualHeight - _height;
}
//asignamos nuevo punto origen del arrastre y movemos el marco interior
OrigenX = x;
OrigenY = y;
HandleDrag(_x, _y );
}
}
}
private void Handle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Image Marco = sender as Image;
if (Marco != null)
{
Marco.CaptureMouse();
OrigenX = e.GetPosition(Marco).X; //iniciamos las variables en el punto de origen del arrastre
OrigenY = e.GetPosition(Marco).Y;
}
}
private void Handle_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Image Marco = sender as Image;
if (Marco != null)
{
Marco.ReleaseMouseCapture();
}
}
#endregion
|
|
|
|

|
I'm trying to add a border around the crop zone but I don't see exactly where this would fit into the code. My first idea was to create a border and add it to _vc inside the CroppingAdorner constructor. That didn't quite work.
Has anyone done this? Where did you add the border?
|
|
|
|

|
See my second response to SHartmann's question below. It tells precisely how to add a border.
|
|
|
|

|
This is a terrific solution and one I'd actually been looking to write since I've seen this style selection in other graphics applications.
Do you know how you would enhance yuor code so that the selection window could be moved?
Since a selection is almost never perfectly aligned its great to be able to nudge it around to get it perfectly positioned.
Any ideas how to mod your code for this enhancement?
Thanks for this great sample!
Rick
|
|
|
|

|
Hello,
when I use this component in 2D mode everything is fine.
When I want to use it in 3D using Viewport2DVisual3D it crashes.
Doesn't Viewport2DVisual3D support adorner layer?
In the following code (proc AddCropToElement)
AdornerLayer aly = AdornerLayer.GetAdornerLayer(fel);
the adorner layer aly is allways null ...
best regards
Stephan
|
|
|
|

|
Man - you're giving this thing a workout! That's a good thing, though, so thanks!
Don't really know on this one. If I was guessing I'd guess RenderTargetBitmap() which is supposed to render anything in a control. Perhaps it doesn't like the straight Direct3D output that comes from the 3D stuff in WPF. I haven't messed around enough with 3D yet to know. That's really the place where we try to deal with the pixels on the underlying control and in fact, about the only place we deal with the underlying control at all. Other than that, it's a matter of drawing the cropping shape right, dealing with the thumbs and manipulating the bitmap that gets returned from RenderTargetBitmap().
Figuring out all the details on 3D is definitely something I want to do. Right now, though, it's a task I have in my horizon but I'm doing some other stuff which keeps me away from it for the moment. If you figure anything out, please let me know!
|
|
|
|

|
By the way, are you using this for a "real" project? Of course, that's fine - I was just curious. If it's not a problem with revealing trade secrets, it'd be kinda cool to find out how it's being used. Of course, if there are trade secrets that need to be kept, I don't want anyone to spill the beans.
|
|
|
|
|

|
I also linked to the Marching Ants solution here
http://www.codeproject.com/KB/WPF/wpfmarchingants.aspx[^]
in my last message.
I think it would be great to merge the functionality of this project with your solution.
the other project does the first part:
a user wants to select a region. he starts in the one corner and moves with pressed mouse button to the opposite corner.
Your solution starts here, when an area is selected you can easily resize it, that is something the other solution is missing.
I also like the way you put it together in the adorner
Is it possible to enhance your solution with the missing parts?
|
|
|
|

|
I still belive enhancing component is a good thing,
but I found a workaround where I can live with (at least for the moment)
If you set the size of the rectangle in the creator to 0, all thumbs are placed on a single point. When you click again you get the lower right thumb and can begin with sizing.
This is not exactly the typical behaviour of marking something, but not bad at all
|
|
|
|

|
Hi,
this is a really great article, but I have a question.
I tried to enhance your control with marchings ants
(taken from this article http://www.codeproject.com/KB/WPF/wpfmarchingants.aspx[^] )
In the constructor of CroppingAdorner I added this code:
_prCropMask.StrokeThickness = 1;
_prCropMask.StrokeDashOffset = 0;
_prCropMask.Stroke = new SolidColorBrush(Colors.Black);
_prCropMask.StrokeDashArray = new DoubleCollection();
_prCropMask.StrokeDashArray.Add(5);
DoubleAnimationUsingKeyFrames ani = new DoubleAnimationUsingKeyFrames();
ani.RepeatBehavior = RepeatBehavior.Forever;
ani.KeyFrames.Add(new LinearDoubleKeyFrame(0, KeyTime.FromTimeSpan (TimeSpan.FromSeconds(0))));
ani.KeyFrames.Add(new LinearDoubleKeyFrame(10, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(500))));
_prCropMask.BeginAnimation(Shape.StrokeDashOffsetProperty, ani);
It works fine with a little bug.
the marchings ants are not only around the selected area, but also around the whole (greyed out) area.
Is there any chance to fix this?
|
|
|
|

|
I'll take a look. For my tastes, I'd rather have the Photoshop like version without the ants - I think they'd be a bit distracting in this situation and as it is, it highlights pretty much what you want to see so it's not like the ants would really mark anything out that's not already plainly visible. That's only my opinion, though. I'll take a quick look but it's been a long time since I did this so I can't guarantee anything.
Darrell
|
|
|
|

|
I think it will work if you replace the rcExterior definition in ArrangeOverride with the following:
Rect rcExterior = new Rect(-1, -1, AdornedElement.RenderSize.Width + 3, AdornedElement.RenderSize.Height + 3);
This makes the punctured rect that contains the mask a little larger than the adorned item so that the border on the outside is clipped off. I'm not sure why I had to add 3 to the width - seems like it ought to have been 2. Perhaps because of antialiassing within WPF or something.
I've done zero testing on this - just threw it in to my test project and it seemed to work.
|
|
|
|
|

|
Good. Not sure that there really is another better solution - at least not if you plan to implement the marching ants as a "border" on the punctured rect. You could try adding a transparent rect with the marching ants border as another element to the adorner. That would probably work also, but I think this solution is simpler and very possibly, more efficient since it doesn't increase the size of the visual elements in the adorner. Glad it worked for you.
|
|
|
|

|
hi
i want 2 ask that how this croped image convert into matrix form.
|
|
|
|

|
I think you're asking how I converted the cropped image into individual pixels. The answer is...I didn't. Look at the code for BpsCrop() listed above. I use RenderTargetBitmap to render the adorned item into a WPF bitmap (no conversion to individual pixels here) and then use CroppedBitmap (in the return statement) to actually crop out the part of the bitmap I want. This returns a BitmapSource and again - no direct conversion to a matrix of individual pixels.
So I never actually convert to a matrix of pixels, however I'm only one step away. You can call the CopyPixels() member function on BitmapSource and it will give you the matrix you're looking for. So essentially, call CopyPixels on the return value from the code in BpsCrop() and you'll get the matrix of the individual pixels which is (I believe) what you're looking for.
Darrell
|
|
|
|

|
Hello
this is great work. I noticed in the
private Point UnitsToPx(double x, double y)
{
return new Point((int)(x * s_dpiX / 96), (int)(y * s_dpiY / 96));
}
Should it not be
private Point UnitsToPx(double x, double y)
{
return new Point((int)(x * (image.Source as WriteableBitmap).DpiX / 96), (int)(y * (image.Source as WriteableBitmap).DpiY / 96));
}
since in wpf the size of the image drawn is determined by the image dpi
V
|
|
|
|

|
Thanks for the kind words.
I've gotta say, if any part of this stuff will drive you batty it's the conversions to and from pixels and determining which types of which pixels are being used. Sadly, WPF is so concentrated on eliminating pixels from their worldview that they give them very short shrift any time they inadvertently allowed them to turn up in the docs. I had to write a separate test app just to infer how CroppedBitmap and RenderTargetBitmap really work. What's that got to do with the question, you ask?
Well, it gives me some small excuse to say that it's been long enough that I'm not 100% sure regarding the answer to your question, but I believe that it's correct as is since I think we're really working at all times with screen bitmaps as returned by "CroppedBitmap". That's why we work fine with vector controls as well as bitmaps if we so desire. Those screen bitmaps, I think (and "think" is the operative word here) will all use the s_dpiX/s_dpiY dots per inch so in the adorner we're not really working directly with the original bitmaps but with their representation on the screen.
I believe if I recall correctly, I had bitmaps with different DPIs and while there was something funny about them in my test app, it all works out in the adorner. Like I say, though, you kind of hit me in my soft spot there, especially after a few months of happily avoiding the subject, so I wouldn't stake my life on it. If it comes up again, I daresay that I'll probably have to rewrite the same test app to recall all the maddening little details about how all that stuff works together.
Thanks again for the response!
Darrell
|
|
|
|
 |
|
|
General News Suggestion Question Bug Answer Joke Rant Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.
|
A cropping adorner which darkens everything except the selected portion
| Type | Article |
| Licence | CPOL |
| First Posted | 24 Jan 2008 |
| Views | 74,202 |
| Downloads | 2,921 |
| Bookmarked | 79 times |
|
|