Редактировать точки FreeShape

У меня есть некоторый графический интерфейс, позволяющий пользователю рисовать Costimized GraphicsPath. Я создал его с помощью функции GraphicsPath AddLine.

Теперь я хочу реализовать то, что вы можете видеть на прикрепленном изображении Microsoft Word - "Редактировать точки".

Я сталкиваюсь с несколькими проблемами:

  1. На моем пути есть "линии"-> каждый размером всего в один пиксель. Я хочу выбрать только "ключевые точки". Как я могу это сделать? Это своего рода реверсы "Flatten", не мог найти такую ​​функцию.

  2. Существует ли существующая функция.Net для рисования маленьких синих прямоугольников и маленького зеленого круга вокруг пути? а как насчет прямоугольников вокруг каждой выбранной точки?

Каждая помощь, даже частичная, будет оценена.

2 ответа

Решение

Для первой части вашего вопроса посмотрите этот пост, который имеет функцию сокращения для List<Point>, Обратите внимание, что GraphicsPath.PathPoints коллекция доступна только для чтения, поэтому вам нужно заново создать путь из списка сокращенных точек.

Несколько замечаний по второй части:

  • Нет встроенной процедуры для создания ручек. Ни заставлять их делать что-либо. Так что вам нужно код для них.

  • Я добавляю простой класс MoveLabel который может быть использован для этого. Он может быть помещен на элемент управления или добавлен к Controls коллекция. Тогда вы можете переместить его. Я добавил функцию обратного вызова MoveAction обработать результат, когда мышь отпущена.

Вы можете добавить

public delegate void Moved(MoveLabel sender);

.. в класс формы, или, чтобы избежать Form1 ссылка вне класса формы, но в области видимости для MoveLabel,

Его можно использовать непосредственно для перемещения точек в списке точек:

Создайте его на панели:

var lab= new MoveLabel(Color.CadetBlue, 9, Point.Round(points[i]), i);
lab.Parent = panel;
lab.MoveAction = moved;

Простая функция обработки:

void moved(MoveLabel sender)
{
    points[sender.PointIndex] = 
            new Point(sender.Left - sender.Width / 2, sender.Top - sender.Height / 2);
    panel.Invalidate();
}

Обратите внимание на GraphicsPath.PathPoints только для чтения, поэтому мы должны заново создать путь из нового списка точек! На самом деле можно изменить индивидуальный PathPoints в коде, но результат не прилипает; так что нужно скопировать PathPoints к PointF[], измените их там и заново создайте путь. Для сложных путей лучше всего использовать эту перегрузку.

Если вы хотите реализовать вращение (или другие преобразования), вы можете использовать функцию GraphicsPath.Transform. Вы можете использовать подвижные метки, чтобы определить вращение или масштабирование данных. Вот мой минимальный MoveLabel учебный класс:

public class MoveLabel : Label
{
    public Form1.Moved MoveAction { get; set; }
    public int PointIndex { get; set; }

    private Point mDown = Point.Empty;

    public MoveLabel()
    {
        MouseDown += (ss, ee) => { mDown = ee.Location; };
        MouseMove += (ss, ee) => {
            if (ee.Button.HasFlag(MouseButtons.Left))
            {
                Location = new Point(Left + ee.X - Width / 2, Top + ee.Y - Height / 2);
                mDown = Location;
            }
        };
        MouseUp += (ss, ee) => { if (MoveAction != null) MoveAction(this);  };
    }

    public MoveLabel(Color c, int size, Point location, int pointIndex) : this()
    {
        BackColor = Color.CadetBlue;
        Size = new Size(size, size);
        Location = location;
        PointIndex = pointIndex;
    }
}

Это также будет хорошо работать для перемещения точек из кривых Безье. Добавив звонок MoveAction(this); к MouseMove Линк код вы можете получать живые обновления.. Убедитесь, что Panels DoubleBuffered для этого:-)

Пример:


Кстати, я только что натолкнулся на этот пост, который показывает, как легко можно сохранить кривую или любой другой векторный рисунок GDI+ в emf, сохраняя качество вектора!

Что касается части уменьшения очков - в итоге я использовал алгоритм Дуглса-Пакера, нашел его здесь: /questions/47170866/kak-umenshit-kolichestvo-tochek-krivoj-pri-sohranenii-ee-obschej-formyi/47170887#47170887 Я не могу вспомнить, нашел ли я где-нибудь эту реализацию. Если кто-то знает, откуда это - с удовольствием свяжу его ответ и дам отзыв.

моя реализация здесь:

          public static List<Point> DouglasPeuckerReduction
        (List<Point> Points, Double Tolerance)
    {
        if (Points == null || Points.Count < 3)
            return Points;

        Int32 firstPoint = 0;
        Int32 lastPoint = Points.Count - 1;
        List<Int32> pointIndexsToKeep = new List<Int32>();

        //Add the first and last index to the keepers
        pointIndexsToKeep.Add(firstPoint);
        pointIndexsToKeep.Add(lastPoint);

        //The first and the last point cannot be the same
        while (Points[firstPoint].Equals(Points[lastPoint]))
        {
            lastPoint--;
        }

        DouglasPeuckerReduction(Points, firstPoint, lastPoint,
        Tolerance, ref pointIndexsToKeep);

        List<Point> returnPoints = new List<Point>();
        pointIndexsToKeep.Sort();
        foreach (Int32 index in pointIndexsToKeep)
        {
            returnPoints.Add(Points[index]);
        }

        return returnPoints;
    }

    /// <summary>
    /// Douglases the peucker reduction.
    /// </summary>
    /// <param name="points">The points.</param>
    /// <param name="firstPoint">The first point.</param>
    /// <param name="lastPoint">The last point.</param>
    /// <param name="tolerance">The tolerance.</param>
    /// <param name="pointIndexsToKeep">The point index to keep.</param>
    private static void DouglasPeuckerReduction(List<Point>
        points, Int32 firstPoint, Int32 lastPoint, Double tolerance,
        ref List<Int32> pointIndexsToKeep)
    {
        Double maxDistance = 0;
        Int32 indexFarthest = 0;

        for (Int32 index = firstPoint; index < lastPoint; index++)
        {
            Double distance = PerpendicularDistance
                (points[firstPoint], points[lastPoint], points[index]);
            if (distance > maxDistance)
            {
                maxDistance = distance;
                indexFarthest = index;
            }
        }

        if (maxDistance > tolerance && indexFarthest != 0)
        {
            //Add the largest point that exceeds the tolerance
            pointIndexsToKeep.Add(indexFarthest);

            DouglasPeuckerReduction(points, firstPoint,
            indexFarthest, tolerance, ref pointIndexsToKeep);
            DouglasPeuckerReduction(points, indexFarthest,
            lastPoint, tolerance, ref pointIndexsToKeep);
        }
    }

    /// <summary>
    /// The distance of a point from a line made from point1 and point2.
    /// </summary>
    /// <param name="pt1">The PT1.</param>
    /// <param name="pt2">The PT2.</param>
    /// <param name="p">The p.</param>
    /// <returns></returns>
    public static Double PerpendicularDistance
        (Point Point1, Point Point2, Point Point)
    {
        //Area = |(1/2)(x1y2 + x2y3 + x3y1 - x2y1 - x3y2 - x1y3)|   *Area of triangle
        //Base = v((x1-x2)²+(x1-x2)²)                               *Base of Triangle*
        //Area = .5*Base*H                                          *Solve for height
        //Height = Area/.5/Base

        Double area = Math.Abs(.5 * (Point1.X * Point2.Y + Point2.X *
        Point.Y + Point.X * Point1.Y - Point2.X * Point1.Y - Point.X *
        Point2.Y - Point1.X * Point.Y));
        Double bottom = Math.Sqrt(Math.Pow(Point1.X - Point2.X, 2) +
        Math.Pow(Point1.Y - Point2.Y, 2));
        Double height = area / bottom * 2;

        return height;

        //Another option
        //Double A = Point.X - Point1.X;
        //Double B = Point.Y - Point1.Y;
        //Double C = Point2.X - Point1.X;
        //Double D = Point2.Y - Point1.Y;

        //Double dot = A * C + B * D;
        //Double len_sq = C * C + D * D;
        //Double param = dot / len_sq;

        //Double xx, yy;

        //if (param < 0)
        //{
        //    xx = Point1.X;
        //    yy = Point1.Y;
        //}
        //else if (param > 1)
        //{
        //    xx = Point2.X;
        //    yy = Point2.Y;
        //}
        //else
        //{
        //    xx = Point1.X + param * C;
        //    yy = Point1.Y + param * D;
        //}

        //Double d = DistanceBetweenOn2DPlane(Point, new Point(xx, yy));
    }

}
Другие вопросы по тегам