使用 Cyotek.ImageBox 在 C# winform 上用鼠标绘制轮廓

draw contours with mouse on C# winform with Cyotek.ImageBox

我想要的功能是让用户用鼠标在图像上画出几个物体的轮廓。在图像上显示轮廓,并能够导出轮廓数据。我在这里使用 cyotek.imagebox https://github.com/cyotek/Cyotek.Windows.Forms.ImageBox,一个用于显示图像的自定义控件。 这是我的方法。

private List<List<Point>> contours = new List<List<Point>>();    //a list to store all contours

private List<Point> contour_temp= new List<Point>();    //a list to store the contour the user is drawing

private bool mousedown // a flag that change when mouse up/mouse down event is triggered

private void imageBox_MouseDown(object sender, MouseEventArgs e)
{
     mousedown = true;
}

private void imageBox_MouseUp(object sender, MouseEventArgs e)
{
    mousedown = false;
}

private void imageBox_MouseMove(object sender, MouseEventArgs e)
{
    //drawing occurs when mouse is down, a contour is finish drawing when mouse is up
    // when a contour finish drawing. it will be added to the contours list and the contour temp will be clear

    if (draw_on) // a flag for this function to be active
    {
        if (contour_temp.Count > 0)
        {
            if (mousedown)
            {
                if (imageBox.IsPointInImage(e.Location)) //function cyotek.imagebox provides , it translate the mouse location to pixel location
                {
                    Point p = this.imageBox.PointToImage(e.Location);
                    if (p != contour_temp[contour_temp.Count - 1])
                        contour_temp.Add(p);
                }
            }
            else 
            {
                if (contour_temp.Count > 2)
                {
                    contours.Add(contour_temp);
                    contour_temp.Clear();
                }
            }
        }
        else
        {
            if (mousedown)
            {
                contour_temp = new List<Point>();
                Point p = this.imageBox.PointToImage(e.Location);
                contour_temp.Add(p);
            }
        }
        imageBox.Invalidate();
    }
}

private void imageBox_Paint(object sender, PaintEventArgs e)
{
    //at the painting function, always paint all the contours stored
    //if mouse is down, print the temporary contour the user is currently drawing
    foreach (List<Point> contour in contours)
    {
        Point p0 = contour[0];
        foreach (Point p1 in contour)
        {
            e.Graphics.DrawLine(new Pen(Color.Red), imageBox.GetOffsetPoint(p0), imageBox.GetOffsetPoint(p1));//Cyotek.ImageBox provided, get the correct point even after zooming
            p0 = p1;
        }    //draw all contours

    }

    if(draw_on && mousedown && contour_temp.Count>0)
    {

        Point p0 = contour_temp[0];
        foreach (Point p1 in contour_temp)
        {
            e.Graphics.DrawLine(new Pen(Color.Green), imageBox.GetOffsetPoint(p0), imageBox.GetOffsetPoint(p1));
            p0 = p1;
        }// draw the contour user is drawing

    }
  }

绘图 contour_temp 部分工作正常。但是当轮廓完成绘制时程序崩溃,在 'mscorlib.dll' 上抛出 System.ArgumentOutOfRangeException, 经过一些测试,我发现绘画功能中绘制所有轮廓的东西 在某种程度上是错误的。我对异常做了一些研究,说索引 列表超出范围。但是当我使用 "foreach" 时怎么会发生呢?

问题是由于这里的这些行造成的:

contours.Add(contour_temp);
contour_temp.Clear();

在第一种情况下,您要将临时点列表 contour_temp 添加到主列表 contours,但由于 List<T>reference type,您我们没有添加点的副本,而是添加了对 same 列表的引用 - 然后您将其清除,导致 contours 实际上只包含空列表。但是您的绘画例程期望这些列表始终包含至少一个点,因此在尝试绘画时 ArgumentOutOfRangeException 因为您试图在不存在任何项目的列表中获取第一项。

我通过将第一行更改为

来修复您的演示程序
contours.Add(new List<Point>(contour_temp));

这将创建列表的副本,从而保留点。我用这个修正测试了你的演示程序,它工作正常。

希望这对您有所帮助。感谢您使用 ImageBox!