简体   繁体   English

在图像内包含选择矩形

[英]Contain selection rectangle inside the image

I looked everywhere and haven't come across anything but I would like to know the best way to contain a selection rectangle so that it wouldn't go out of bounds. 我四处张望,什么也没碰到,但我想知道包含选择矩形的最佳方法,这样它就不会超出范围。 I have an application were user draws a selection rectangle on top of an image. 我有一个应用程序,用户在图像上方绘制选择矩形。 The rectangle can be also be moved and resized. 矩形也可以移动和调整大小。 Currently I just use an exception handler which when an out of range exception is catched it would alert the user. 当前,我只使用异常处理程序,当捕获到超出范围的异常时,它将提醒用户。 The out of range exception only occurs when moving the drawn rectangle and I would like to make it more streamlined that the actual rectangle cant be dragged or resized outside of the image. 超出范围的例外仅在移动绘制的矩形时发生,我想使其更加简化,以使实际的矩形不能在图像之外拖动或调整大小。 Below is the xaml and code behind for my crop control. 以下是用于我的作物控制的xaml和代码。

Crop Control Code Behind: 后面的作物控制代码:

     public partial class CropControl : UserControl
{
    #region Data's
    private bool isDragging = false;
    private Point anchorPoint = new Point();
    private bool MoveRect = false;          //flag which intially set to false which means a crop rectangle is not moved but created.
    private bool MoveInProgress = false;    //flag that is set to true if the crop rect is moving, otherwise false.
    private Point LastPoint;                // The drag's last point
    HitType MouseHitType = HitType.None;    //part of the rectangle under the mouse
    private enum HitType { None, Body, UL, UR, LR, LL, L, R, T, B }; //Enum for the part of the rectangle the mouse is over.
    #endregion

    #region Constructor
    public CropControl()
    {
        InitializeComponent();

    }
    #endregion

    #region Dependency Property
    //Register the Dependency Property
    public static readonly DependencyProperty SelectionProperty =
        DependencyProperty.Register("Selection", typeof(Rect), typeof(CropControl), new PropertyMetadata(default(Rect)));


    public Rect Selection
    {
        get { return (Rect)GetValue(SelectionProperty); }
        set { SetValue(SelectionProperty, value); }
    }

    // this is used, to react on changes from ViewModel. If you assign a  
    // new Rect in your ViewModel you will have to redraw your Rect here
    private static void OnSelectionChanged(System.Windows.DependencyObject d, System.Windows.DependencyPropertyChangedEventArgs e)
    {
        Rect newRect = (Rect)e.NewValue;
        Rectangle selectionRectangle = d as Rectangle;

        if (selectionRectangle != null)
            return;

        selectionRectangle.SetValue(Canvas.LeftProperty, newRect.X);
        selectionRectangle.SetValue(Canvas.TopProperty, newRect.Y);
        selectionRectangle.Width = newRect.Width;
        selectionRectangle.Height = newRect.Height;
    }
    #endregion
    private Point lastLoc;


    #region MouseLeftButtonDown Event
    private void LoadedImage_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        lastLoc = new Point(Canvas.GetLeft(selectionRectangle), Canvas.GetTop(selectionRectangle));

        //This statement will enable the creation of a new rectangle only if the mouse left
        //button press is outside of a created rectangle and that crop rectangle was initially created.
        //This is known since the HitType if outside the rectangle will always be set to None and the crop rect width > 0.
        //The previous cropping rect will be removed by setting its value to null.
        if (MouseHitType== HitType.None && selectionRectangle.Width>0)
        {
            selectionRectangle.Width = 0;         //set crop rectangle's width to 0
            selectionRectangle.Height = 0;        //set crop rectangle's height to 0
            SetMouseCursor();
            MoveRect = false;           //flag that crop rectangle is not being moved but drawn.
        }

        //This statement test if the crop rectangle is not being dragged and moved. If true it would 
        //set the x and y position of the crop rect in accordance to Canvas. If false it means that
        //crop rectangle was already created and is now being moved to different position in the canvas.
        if (!isDragging && !MoveRect)
        {
            anchorPoint.X = e.GetPosition(BackPanel).X;   //get the x position of the mouse
            anchorPoint.Y = e.GetPosition(BackPanel).Y;   //get the y position of the mouse
            isDragging = true;                      //flag that the user is dragging the mouse to create a rectangle
            BackPanel.Cursor = Cursors.Cross;       //change the cursor to a cross while left button is held down
        }
        else
        {
            MouseHitType = SetHitType(selectionRectangle, e.GetPosition(BackPanel));     //get hittype
            SetMouseCursor();        //set the mouse cursor based on the hittype
            if (MouseHitType == HitType.None) return;       
            LastPoint = e.GetPosition(BackPanel);  
            MoveInProgress = true;      //flag true since rectangle is being moved
        }
    }
    #endregion

    private double CanvasTop, CanvasLeft;

    #region MouseMove Event
    private void LoadedImage_MouseMove(object sender, MouseEventArgs e)
    {
        Point offset = new Point((anchorPoint.X-lastLoc.X),(anchorPoint.Y-lastLoc.Y));
        var newX=(anchorPoint.X+(e.GetPosition(BackPanel).X)-anchorPoint.X);
        var newY=(anchorPoint.Y+(e.GetPosition(BackPanel).Y)-anchorPoint.Y);
        CanvasTop = newX - offset.X;
        CanvasLeft = newY - offset.Y;
        //Statement that checks if crop rect is being created or moved. If moved it will set the 
        //dimension of the rectanlge and if not it would set the location of the new rectangle.
        if (isDragging  && !MoveRect)
        {
            double x = e.GetPosition(BackPanel).X;      //get x position of mouse
            double y = e.GetPosition(BackPanel).Y;      //get y position of mouse

            selectionRectangle.SetValue(Canvas.LeftProperty, Math.Min(x, anchorPoint.X));       //set the bottom
            selectionRectangle.SetValue(Canvas.TopProperty, Math.Min(y, anchorPoint.Y));       //set the top
            selectionRectangle.Width = Math.Abs(x - anchorPoint.X);         //set the width
            selectionRectangle.Height = Math.Abs(y - anchorPoint.Y);        //set the height
            if (selectionRectangle.Visibility != Visibility.Visible)       //make crop rectangle visible if its not.
                selectionRectangle.Visibility = Visibility.Visible;

        }
        else if (!isDragging && MoveRect)
        {

            if (!MoveInProgress)
            {
                MouseHitType = SetHitType(selectionRectangle, e.GetPosition(BackPanel));
                SetMouseCursor();
            }
            else
            {
                // See how much the mouse has moved.
                Point point = e.GetPosition(BackPanel);
                double offset_x = point.X - LastPoint.X;
                double offset_y = point.Y - LastPoint.Y;

                // Get the rectangle's current position.
                double new_x = Canvas.GetLeft(selectionRectangle);     
                double new_y = Canvas.GetTop(selectionRectangle);
                double new_width = selectionRectangle.Width;
                double new_height = selectionRectangle.Height;

                // Update the rectangle.
                switch (MouseHitType)
                {
                    case HitType.Body:
                        new_x += offset_x;
                        new_y += offset_y;
                        break;
                    case HitType.UL:
                        new_x += offset_x;
                        new_y += offset_y;
                        new_width -= offset_x;
                        new_height -= offset_y;
                        break;
                    case HitType.UR:
                        new_y += offset_y;
                        new_width += offset_x;
                        new_height -= offset_y;
                        break;
                    case HitType.LR:
                        new_width += offset_x;
                        new_height += offset_y;
                        break;
                    case HitType.LL:
                        new_x += offset_x;
                        new_width -= offset_x;
                        new_height += offset_y;
                        break;
                    case HitType.L:
                        new_x += offset_x;
                        new_width -= offset_x;
                        break;
                    case HitType.R:
                        new_width += offset_x;
                        break;
                    case HitType.B:
                        new_height += offset_y;
                        break;
                    case HitType.T:
                        new_y += offset_y;
                        new_height -= offset_y;
                        break;
                }

                // Don't use negative width or height.
                if ((new_width > 0) && (new_height > 0))
                {

                    // Update the rectangle.
                    Canvas.SetLeft(selectionRectangle, new_x);
                    Canvas.SetTop(selectionRectangle, new_y);
                    selectionRectangle.Width = new_width;
                    selectionRectangle.Height = new_height;

                    // Save the mouse's new location.
                    LastPoint = point;
                }
            }
        }

    }
    #endregion

    #region MouseLeftButtonUp Event
    private void LoadedImage_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {

        //statement which checks if the mouse left button action is for either creating or
        //moving the crop rectangle. If true, isDragging=false since the crop rect is created
        //and moverect = true since the created rectangle is ready to be moved.
        if (isDragging && !MoveRect)
        {
            isDragging = false;
            if (selectionRectangle.Width > 0)
            {
                MoveRect = true;
            }
        }
        else
        {
            MoveInProgress = false;  //flags Move in progress as false since rect move action is done.
        }

        // Set the Selection to the new rect, when the mouse button has been released
        Selection = new Rect(
            (double)selectionRectangle.GetValue(Canvas.LeftProperty),
            (double)selectionRectangle.GetValue(Canvas.TopProperty),
            selectionRectangle.Width,
            selectionRectangle.Height);
    }
    #endregion

    #region Mutator's
    // Return a HitType value to indicate what is at the point.
    private HitType SetHitType(Rectangle rect, Point point)
    {
        double left = Canvas.GetLeft(selectionRectangle);
        double top = Canvas.GetTop(selectionRectangle);
        double right = left + selectionRectangle.Width;
        double bottom = top + selectionRectangle.Height;

        //statement that checks if cursor is outside the area of the crop rectangle
        //and returns HitType.None.
        if (point.X < left) return HitType.None;
        if (point.X > right) return HitType.None;
        if (point.Y < top) return HitType.None;
        if (point.Y > bottom) return HitType.None;

        const double GAP = 10;  //sets the gap which when mouse over a cursor change is triggered

        //statement that checks where the mouse is located within the rectangle.
        if (point.X - left < GAP)
        {
            // Left edge.
            if (point.Y - top < GAP) return HitType.UL;
            if (bottom - point.Y < GAP) return HitType.LL;
            return HitType.L;
        }
        if (right - point.X < GAP)
        {
            // Right edge.
            if (point.Y - top < GAP) return HitType.UR;
            if (bottom - point.Y < GAP) return HitType.LR;
            return HitType.R;
        }
        if (point.Y - top < GAP) return HitType.T;
        if (bottom - point.Y < GAP) return HitType.B;
        return HitType.Body;
    }

    // Set a mouse cursor appropriate for the current hit type.
    private void SetMouseCursor()
    {
        // See what cursor we should display.
        Cursor desired_cursor = Cursors.Arrow;
        switch (MouseHitType)
        {
            case HitType.None:
                desired_cursor = Cursors.Arrow;
                break;
            case HitType.Body:
                desired_cursor = Cursors.ScrollAll;
                break;
            case HitType.UL:
            case HitType.LR:
                desired_cursor = Cursors.SizeNWSE;
                break;
            case HitType.LL:
            case HitType.UR:
                desired_cursor = Cursors.SizeNESW;
                break;
            case HitType.T:
            case HitType.B:
                desired_cursor = Cursors.SizeNS;
                break;
            case HitType.L:
            case HitType.R:
                desired_cursor = Cursors.SizeWE;
                break;
        }

        // Display the desired cursor.
        if (BackPanel.Cursor != desired_cursor) 
            BackPanel.Cursor = desired_cursor;
    }
    #endregion
}

Crop Control XAML: 作物控制XAML:

     <UserControl.Resources>
    <Storyboard x:Key="MarchingAnts">
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
                           Storyboard.TargetName="selectionRectangle" 
                           Storyboard.TargetProperty="(Shape.StrokeDashOffset)" 
                           RepeatBehavior="Forever">
            <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
            <SplineDoubleKeyFrame KeyTime="00:00:00.3000000"
                           Value="10"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</UserControl.Resources>
<UserControl.Triggers>
    <EventTrigger RoutedEvent="FrameworkElement.Loaded">
        <BeginStoryboard Storyboard="{StaticResource MarchingAnts}"/>
    </EventTrigger>
</UserControl.Triggers>
<Canvas Name="BackPanel" Background="Transparent" MouseLeftButtonDown="LoadedImage_MouseLeftButtonDown" MouseMove="LoadedImage_MouseMove" MouseLeftButtonUp="LoadedImage_MouseLeftButtonUp">
    <Rectangle Name="selectionRectangle" Stroke="#FFFFFFFF"
               StrokeThickness="1" StrokeDashOffset="0" 
               Fill="#220000FF" Visibility="Collapsed"
               StrokeDashArray="5"/>
</Canvas>

Sorry for the confusion but i changed my explanation. 对不起,我很困惑,但我改变了解释。 The rectangle don't go out of bounds when drawn but only happens if the drawn rectangle is moved. 绘制矩形时,矩形不会超出范围,仅在移动绘制矩形时才会发生。 Also the exception is caught in my view model's crop method which is shown below: 我的视图模型的裁剪方法中也捕获了异常,如下所示:

     public void Crop()
    {
        ////Get a copy of the selection in case it changes during execution
        Rect cropSelection = Selection;
        //// use it to crop your image
        Int32Rect rcFrom = new Int32Rect();
        rcFrom.X = (int)((cropSelection.X) * (ImagePath.Width) / (ImagePath.Width));
        rcFrom.Y = (int)((cropSelection.Y) * (ImagePath.Height) / (ImagePath.Height));
        rcFrom.Width = (int)cropSelection.Width;
        rcFrom.Height = (int)cropSelection.Height;

        try
        {
            BitmapSource bs = new CroppedBitmap(ImagePath as BitmapSource, rcFrom);
            CroppedImage = bs;

        }
        catch (Exception e)
        {
            MessageBox.Show("Selection Rectangle is outside the image." + "\n" + "Adjust the cropping rectangle so it's within the boundaries of the Image ", " Error Message", MessageBoxButton.OK, MessageBoxImage.Error);
        }
    }

Update: 更新:

I was able to get it working by calculating the size and position of the selection rectangle against its parent (Canvas) which takes takes the same size as the image. 通过选择矩形的父对象(画布)的大小和位置,我能够使其工作,该矩形的大小与图像的大小相同。 Below is what I added to my code. 以下是我添加到代码中的内容。

       double bottom = new_y + selectionRectangle.Height;
                double right = new_x+selectionRectangle.Width;
                if (new_y< 0)
                    new_y = 0;
                if (new_x < 0)
                    new_x = 0;
                if (bottom > BackPanel.ActualHeight)
                    new_y = BackPanel.ActualHeight-selectionRectangle.Height;
                if (right > BackPanel.ActualWidth)
                    new_x = BackPanel.ActualWidth - selectionRectangle.Width;
                if (new_height > BackPanel.ActualHeight)
                    new_height = BackPanel.ActualHeight;
                if (new_width > BackPanel.ActualWidth)
                    new_width = BackPanel.ActualWidth;

The new_height and new_width was added because an exception is still thrown if the rectangle occupies the entire image. 添加new_height和new_width的原因是,如果矩形占据了整个图像,则仍然会引发异常。

If i correctly understood question - you shows message to the user when selection rect goes outside of image rect? 如果我正确理解了问题-当选择矩形超出图像矩形时,您会向用户显示消息? If so, why not check that: if new selection state will out of image area - then just not move selection and keep it in old state? 如果是这样,为什么不检查以下内容:如果新的选择状态将不在图像区域内,则只是不移动选择并将其保持在旧状态? I mean - compare borders positions of new selection rect with image rect and make decision: move or not your selection rect in new position (or change or not it's size). 我的意思是-将新选择矩形的边界位置与图像矩形进行比较,并做出决定:将选择矩形移动或不移动到新位置(或更改其大小)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM