C# WPF 图像标注

2 篇文章 0 订阅
2 篇文章 0 订阅

参考

【WPF图像标注】

一、图像缩放

滚轮放大缩小图像

private void Grid_MouseWheel(object sender, MouseWheelEventArgs e)
{
    var point = e.GetPosition(canvas);
    var delta = e.Delta * 0.002;

    DoImageWheelZoom(point, delta);
    RefreshCrossLine(e.GetPosition(imgControl));
}

/// <summary>
/// 图像缩放
/// </summary>
/// <param name="point"></param>
/// <param name="delta"></param>
private void DoImageWheelZoom(Point point, double delta)
{
    if (scaleTransform.ScaleX + delta < 1) delta = 1 - scaleTransform.ScaleX;
    if (delta == 0) return;
    scaleTransform.ScaleX += delta;
    scaleTransform.ScaleY += delta;
    translateTransform.X -= point.X * delta;
    translateTransform.Y -= point.Y * delta;
}

/// <summary>
/// 刷新辅助线大小位置
/// </summary>
/// <param name="mouse"></param>
private void RefreshCrossLine(Point mouse)
{
    if (!crossLineShow) { return; }
    double scale = scaleTransform.ScaleX;
    double W = imgControl.ActualWidth;
    double H = imgControl.ActualHeight;
    double w = canvas.ActualWidth;
    double h = canvas.ActualHeight;
    lineX.StartPoint = new Point(mouse.X, Math.Max(0, translateTransform.Y + (H - h) / 2));
    lineX.EndPoint = new Point(mouse.X, Math.Min(H, translateTransform.Y + h * scale + (H - h) / 2));
    lineY.StartPoint = new Point(Math.Max(0, translateTransform.X + (W - w) / 2), mouse.Y);
    lineY.EndPoint = new Point(Math.Min(W, translateTransform.X + w * scale + (W - w) / 2), mouse.Y);
}

二、图像拖动

 空格键按下拖放图像

private void MainWindow_KeyDown(object sender, KeyEventArgs e)
{
    //空格键设置可拖动图像
    if (e.Key == Key.Space)
    {
        isSpaceDown = true;
        canvas.Cursor = Cursors.Hand;
        crossLineShow = false;
        ClearCrossLine();
    }
}


private void MainWindow_KeyUp(object sender, KeyEventArgs e)
{
    if (e.Key == Key.Space)
    {
        isSpaceDown = false;
        canvas.Cursor = Cursors.Arrow;
        crossLineShow = isLabel;
    }
}


private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    #region 图像拖动
    if (isSpaceDown && canvas.IsMouseOver)
    {
        isDrag = true;
        preMovePoint = e.GetPosition(this);
        canvas.CaptureMouse();
    }
    #endregion
}

private void Grid_MouseMove(object sender, MouseEventArgs e)
{
    #region 图像拖动
    if (isDrag)
    {
        var point = e.GetPosition(this);  // 获取移动后的位置
        var dp = point - preMovePoint;  // 计算偏移量
        preMovePoint = point;

        translateTransform.X += dp.X;
        translateTransform.Y += dp.Y;

        return;
    }
    #endregion
}

三、辅助线

bool crossLineShow = false;
LineGeometry lineX = new LineGeometry(), lineY = new LineGeometry();
GeometryGroup linegroup = new GeometryGroup();

/// <summary>
/// 隐藏辅助线
/// </summary>
private void ClearCrossLine()
{
    lineX.StartPoint = new Point();
    lineX.EndPoint = new Point();
    lineY.StartPoint = new Point();
    lineY.EndPoint = new Point();
}

/// <summary>
/// 刷新辅助线大小位置
/// </summary>
/// <param name="mouse"></param>
private void RefreshCrossLine(Point mouse)
{
    if (!crossLineShow) { return; }
    double scale = scaleTransform.ScaleX;
    double W = imgControl.ActualWidth;
    double H = imgControl.ActualHeight;
    double w = canvas.ActualWidth;
    double h = canvas.ActualHeight;
    lineX.StartPoint = new Point(mouse.X, Math.Max(0, translateTransform.Y + (H - h) / 2));
    lineX.EndPoint = new Point(mouse.X, Math.Min(H, translateTransform.Y + h * scale + (H - h) / 2));
    lineY.StartPoint = new Point(Math.Max(0, translateTransform.X + (W - w) / 2), mouse.Y);
    lineY.EndPoint = new Point(Math.Min(W, translateTransform.X + w * scale + (W - w) / 2), mouse.Y);
}

四、辅助大图

标注时显示光标附近放大图像

private void Grid_MouseMove(object sender, MouseEventArgs e)
{
    #region 辅助大图
    if (isLabel && (canvas.IsMouseOver || crossline.IsMouseOver))
    {
        RefreshBigImagePosition(e.GetPosition(canvas));
    }
    #endregion
}

/// <summary>
/// 刷新辅助大图位置
/// </summary>
/// <param name="point"></param>
private void RefreshBigImagePosition(Point point)
{
    bigImg.Margin = new Thickness(-point.X * bigImaScale + 50, -point.Y * bigImaScale + 50, 0, 0);
}

五、标注框

internal class RectPanel : Grid
{
    TranslateTransform translateTransform = new TranslateTransform();
    Rectangle rect;
    Panel parentPanel;

    public ListBoxItem listBoxItem = new ListBoxItem();

    SolidColorBrush color = new SolidColorBrush(Color.FromArgb(255, 0, 255, 0));
    ObservableCollection<RectPanel> rectPanels;
    ObservableCollection<ListBoxItem> labelItems;
    public RectPanel(ObservableCollection<RectPanel> rectPanels, ObservableCollection<ListBoxItem> labelItems)
    {
        this.MouseLeftButtonDown += RectPanel_MouseLeftButtonDown;
        this.MouseLeftButtonUp += RectPanel_MouseLeftButtonUp;
        this.MouseMove += RectPanel_MouseMove;
        this.MouseEnter += RectPanel_MouseEnter;
        this.MouseLeave += RectPanel_MouseLeave;
        this.HorizontalAlignment = HorizontalAlignment.Left;
        this.VerticalAlignment = VerticalAlignment.Top;
        this.RenderTransform = translateTransform;

        this.rectPanels = rectPanels;
        this.labelItems = labelItems;
        this.rectPanels.Add(this);
        this.labelItems.Add(listBoxItem);

        listBoxItem.PreviewMouseLeftButtonUp += ListBoxItem_PreviewMouseLeftButtonUp;
        listBoxItem.Focusable = false;

        #region 边框
        rect = new Rectangle();
        rect.Stroke = color;
        rect.StrokeThickness = 1;
        this.Children.Add(rect); 
        #endregion

        #region 拖拽点
        Rectangle rectLeftTop = new Rectangle();
        rectLeftTop.Width = 3;
        rectLeftTop.Height = 3;
        rectLeftTop.Fill = color;
        rectLeftTop.HorizontalAlignment = HorizontalAlignment.Left;
        rectLeftTop.VerticalAlignment = VerticalAlignment.Top;
        rectLeftTop.Margin = new Thickness(-1, -1, 0, 0);
        rectLeftTop.Cursor = Cursors.SizeNWSE;
        rectLeftTop.MouseLeftButtonDown += Rect_MouseLeftButtonDown;
        rectLeftTop.MouseLeftButtonUp += Rect_MouseLeftButtonUp;
        rectLeftTop.MouseMove += Rect_MouseMove;
        this.Children.Add(rectLeftTop);

        Rectangle rectRigthTop = new Rectangle();
        rectRigthTop.Width = 3;
        rectRigthTop.Height = 3;
        rectRigthTop.Fill = color;
        rectRigthTop.HorizontalAlignment = HorizontalAlignment.Right;
        rectRigthTop.VerticalAlignment = VerticalAlignment.Top;
        rectRigthTop.Margin = new Thickness(0, -1, -1, 0);
        rectRigthTop.Cursor = Cursors.SizeNESW;
        rectRigthTop.MouseLeftButtonDown += Rect_MouseLeftButtonDown;
        rectRigthTop.MouseLeftButtonUp += Rect_MouseLeftButtonUp;
        rectRigthTop.MouseMove += Rect_MouseMove;
        this.Children.Add(rectRigthTop);

        Rectangle rectRightBottom = new Rectangle();
        rectRightBottom.Width = 3;
        rectRightBottom.Height = 3;
        rectRightBottom.Fill = color;
        rectRightBottom.HorizontalAlignment = HorizontalAlignment.Right;
        rectRightBottom.VerticalAlignment = VerticalAlignment.Bottom;
        rectRightBottom.Margin = new Thickness(0, 0, -1, -1);
        rectRightBottom.Cursor = Cursors.SizeNWSE;
        rectRightBottom.MouseLeftButtonDown += Rect_MouseLeftButtonDown;
        rectRightBottom.MouseLeftButtonUp += Rect_MouseLeftButtonUp;
        rectRightBottom.MouseMove += Rect_MouseMove;
        this.Children.Add(rectRightBottom);

        Rectangle rectLeftBottom = new Rectangle();
        rectLeftBottom.Width = 3;
        rectLeftBottom.Height = 3;
        rectLeftBottom.Fill = color;
        rectLeftBottom.HorizontalAlignment = HorizontalAlignment.Left;
        rectLeftBottom.VerticalAlignment = VerticalAlignment.Bottom;
        rectLeftBottom.Margin = new Thickness(-1, 0, 0, -1);
        rectLeftBottom.Cursor = Cursors.SizeNESW;
        rectLeftBottom.MouseLeftButtonDown += Rect_MouseLeftButtonDown;
        rectLeftBottom.MouseLeftButtonUp += Rect_MouseLeftButtonUp;
        rectLeftBottom.MouseMove += Rect_MouseMove;
        this.Children.Add(rectLeftBottom);
        #endregion
    }

    /// <summary>
    /// 标注位置大小信息
    /// </summary>
    public Rect GetLabelInfo
    { 
        get
        {
            return new Rect(new Point(translateTransform.X, translateTransform.Y), new Size(this.ActualWidth, this.ActualHeight));
        }
    }

    #region 事件
    #region 拖动
    bool isDrag = false;
    Point mouseDownPoint = new Point(0, 0);
    Point translateTransformPoint = new Point(0, 0);
    private void RectPanel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        isDrag = true;
        translateTransformPoint.X = translateTransform.X;
        translateTransformPoint.Y = translateTransform.Y;
        mouseDownPoint = e.GetPosition(parentPanel);
        this.CaptureMouse();
    }

    private void RectPanel_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        isDrag = false;
        this.ReleaseMouseCapture();
    }

    private void RectPanel_MouseMove(object sender, MouseEventArgs e)
    {
        if (isDrag && e.LeftButton == MouseButtonState.Pressed)
        {
            var point = e.GetPosition(parentPanel);  // 获取移动后的位置
            var dp = point - mouseDownPoint;  // 计算偏移量

            double x = translateTransformPoint.X + dp.X, y = translateTransformPoint.Y + dp.Y;

            //拖动边界
            if (x < 0) x = 0;
            if (y < 0) y = 0;
            if (x + this.ActualWidth > parentPanel.ActualWidth) x = parentPanel.ActualWidth - this.ActualWidth;
            if (y + this.ActualHeight > parentPanel.ActualHeight) y = parentPanel.ActualHeight - this.ActualHeight;

            translateTransform.X = x;
            translateTransform.Y = y;
        }
    }
    #endregion

    #region 移入移出背景光标
    private void RectPanel_MouseEnter(object sender, MouseEventArgs e)
    {
        MouseEnterStyle();
    }

    private void RectPanel_MouseLeave(object sender, MouseEventArgs e)
    {
        MouseLeaveStyle();
    } 
    #endregion

    bool isChange = false;
    Point startPoint = new Point();
    private void Rect_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        e.Handled = true;
        Rectangle rect = sender as Rectangle;
        if (rect == null) return;

        isChange = true;
        rect.CaptureMouse();
        //左上角
        if (rect.HorizontalAlignment == HorizontalAlignment.Left && rect.VerticalAlignment == VerticalAlignment.Top)
        {
            //右下角坐标
            startPoint.X = translateTransform.X + this.ActualWidth;
            startPoint.Y = translateTransform.Y + this.ActualHeight;
        }
        //右上角
        else if (rect.HorizontalAlignment == HorizontalAlignment.Right && rect.VerticalAlignment == VerticalAlignment.Top)
        {
            //左下角坐标
            startPoint.X = translateTransform.X;
            startPoint.Y = translateTransform.Y + this.ActualHeight;
        }
        //右下角
        else if (rect.HorizontalAlignment == HorizontalAlignment.Right && rect.VerticalAlignment == VerticalAlignment.Bottom)
        {
            //左上角坐标
            startPoint.X = translateTransform.X;
            startPoint.Y = translateTransform.Y;
        }
        //左下角
        else if (rect.HorizontalAlignment == HorizontalAlignment.Left && rect.VerticalAlignment == VerticalAlignment.Bottom)
        {
            //右上角坐标
            startPoint.X = translateTransform.X + this.ActualWidth;
            startPoint.Y = translateTransform.Y;
        }
    }

    private void Rect_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        Rectangle rect = sender as Rectangle;
        if (rect == null) return;
        isChange = false;

        rect.ReleaseMouseCapture();
    }

    private void Rect_MouseMove(object sender, MouseEventArgs e)
    {
        if (isChange)
        {
            ResizeChange(startPoint, e.GetPosition(parentPanel));
        }
    }

    private void ListBoxItem_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        listBoxItem.IsSelected = true;
    }
    #endregion

    /// <summary>
    /// 大小位置变更
    /// </summary>
    /// <param name="startPoint"></param>
    /// <param name="endPoint"></param>
    public void ResizeChange(Point startPoint, Point endPoint)
    {
        if (parentPanel == null) parentPanel = this.Parent as Panel;

        #region 边界
        if (endPoint.X < 0) endPoint.X = 0;
        if (endPoint.Y < 0) endPoint.Y = 0;
        if (endPoint.X > parentPanel.ActualWidth) endPoint.X = parentPanel.ActualWidth;
        if (endPoint.Y > parentPanel.ActualHeight) endPoint.Y = parentPanel.ActualHeight;
        #endregion

        double width = Math.Abs(startPoint.X - endPoint.X);
        double height = Math.Abs(startPoint.Y - endPoint.Y);
        if (width <= 0 || height <= 0) return;
        double x = Math.Min(startPoint.X, endPoint.X);
        double y = Math.Min(startPoint.Y, endPoint.Y);

        this.rect.Width = width;
        this.rect.Height = height;

        translateTransform.X = x;
        translateTransform.Y = y;
    }

    /// <summary>
    /// 标注框删除
    /// </summary>
    public void RectDel()
    {
        if (rectPanels.Contains(this)) rectPanels.Remove(this);
        if (labelItems.Contains(listBoxItem)) labelItems.Remove(listBoxItem);
        if (parentPanel.Children.Contains(this)) parentPanel.Children.Remove(this);
    }

    /// <summary>
    /// 移入样式
    /// </summary>
    public void MouseEnterStyle()
    {
        this.Cursor = Cursors.Hand;
        this.Background = new SolidColorBrush(Color.FromArgb(55, 0, 255, 0));
        Panel.SetZIndex(this, 10000);
    }

    /// <summary>
    /// 移出样式
    /// </summary>
    public void MouseLeaveStyle()
    {
        this.Background = new SolidColorBrush(Colors.Transparent);
        Panel.SetZIndex(this, 0);
    } 
}

六、标注类型弹窗

internal class LabelPopupWindow : Window
{
    ObservableCollection<ListBoxItem> listBoxItems;
    public LabelPopupWindow(ObservableCollection<ListBoxItem> listBoxItems)
    {
        this.listBoxItems = listBoxItems;
        WinInit();
    }

    public new bool? Show()
    {
        return this.ShowDialog();
    }

    TextBox textBox;
    ListBox listBox;
    #region 初始化
    private void WinInit()
    {
        this.Width = 300;
        this.Height = 400;
        this.ResizeMode = ResizeMode.NoResize;
        this.WindowStyle = WindowStyle.None;
        this.WindowStartupLocation = WindowStartupLocation.CenterScreen;

        #region 边框
        Border border = new Border();
        border.BorderBrush = new SolidColorBrush(Color.FromArgb(255, 0, 255, 0));
        border.BorderThickness = new Thickness(1);
        this.Content = border;
        #endregion

        #region 布局容器
        Grid grid = new Grid();
        grid.Margin = new Thickness(20);
        border.Child = grid;
        RowDefinition row1 = new RowDefinition();
        RowDefinition row2 = new RowDefinition();
        RowDefinition row3 = new RowDefinition();
        row1.Height = new GridLength(50);
        row3.Height = new GridLength(50);
        grid.RowDefinitions.Add(row1);
        grid.RowDefinitions.Add(row2);
        grid.RowDefinitions.Add(row3);
        #endregion

        #region 标注类型
        textBox = new TextBox();
        textBox.Height = 35;
        textBox.VerticalContentAlignment = VerticalAlignment.Center;
        textBox.VerticalAlignment = VerticalAlignment.Center;
        Grid.SetRow(textBox, 0);
        grid.Children.Add(textBox);
        #endregion

        #region 标注类型集合
        listBox = new ListBox();
        listBox.Margin = new Thickness(0, 20, 0, 20);
        Grid.SetRow(listBox, 1);
        listBox.ItemsSource = listBoxItems;
        grid.Children.Add(listBox);
        //默认上次选中项
        ListBoxItem sltItem = listBoxItems.FirstOrDefault(item => item.IsSelected);
        if (sltItem != null)
        {
            listBox.SelectedIndex = listBoxItems.IndexOf(sltItem);
        }

        #region 数据绑定
        this.DataContext = this;
        Binding binding = new Binding("SelectedItem.Content")
        {
            Source = listBox,
            Mode = BindingMode.OneWay
        };

        textBox.SetBinding(TextBox.TextProperty, binding);
        #endregion
        #endregion

        #region 确定 | 取消
        StackPanel stackPanel = new StackPanel();
        stackPanel.Orientation = Orientation.Horizontal;
        stackPanel.FlowDirection = FlowDirection.RightToLeft;
        Grid.SetRow(stackPanel, 2);
        grid.Children.Add(stackPanel);

        Button btnCancel = new Button();
        btnCancel.Width = 80;
        btnCancel.Height = 30;
        btnCancel.Content = "取消";
        btnCancel.HorizontalAlignment = HorizontalAlignment.Right;
        btnCancel.VerticalAlignment = VerticalAlignment.Center;
        btnCancel.Click += BtnCancel_Click;
        stackPanel.Children.Add(btnCancel);

        Button btnCfm = new Button();
        btnCfm.Margin = new Thickness(20, 0, 20, 0);
        btnCfm.Width = 80;
        btnCfm.Height = 30;
        btnCfm.Content = "确定";
        btnCfm.HorizontalAlignment = HorizontalAlignment.Right;
        btnCfm.VerticalAlignment = VerticalAlignment.Center;
        btnCfm.Click += BtnCfm_Click;
        stackPanel.Children.Add(btnCfm);
        #endregion
    } 
    #endregion

    /// <summary>
    /// 标注类型
    /// </summary>
    public string label;
    public string Label
    {
        get
        { 
            return label;
        }
        private set
        { 
            label = value;
        }
    }

    /// <summary>
    /// 确定
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void BtnCfm_Click(object sender, RoutedEventArgs e)
    {
        Label = textBox.Text.Trim();
        if (string.IsNullOrWhiteSpace(label))
        {
            this.DialogResult = false;
            return;
        }

        //检测是否存在相同标注类型,不存在添加
        if (!listBoxItems.Any(item => item.Content.ToString() == label))
        {
            ListBoxItem listBoxItem = new ListBoxItem();
            listBoxItem.Content = label;
            listBoxItems.Add(listBoxItem);
        }

        //设置默认选中最后标注类型
        foreach (ListBoxItem item in listBoxItems)
        {
            if (item.Content.Equals(Label))
            {
                item.IsSelected = true;
            }
            else
            {
                item.IsSelected = false;
            }
        }
        
        this.DialogResult = true;
    }

    /// <summary>
    /// 取消
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void BtnCancel_Click(object sender, RoutedEventArgs e)
    {
        this.DialogResult = false;
    }
}

七、删除

 Delete快捷键删除选中项标注

private void MainWindow_KeyDown(object sender, KeyEventArgs e)
{
    //空格键设置可拖动图像
    if (e.Key == Key.Space)
    {
        isSpaceDown = true;
        canvas.Cursor = Cursors.Hand;
        crossLineShow = false;
        ClearCrossLine();
    }
    //Delete键删除标注
    else if (e.Key == Key.Delete)
    {
        if (listBox.SelectedIndex < 0) return;
        rectPanels[listBox.SelectedIndex].RectDel();
    }
}

八、演示效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值