Форум программистов, компьютерный форум, киберфорум
Наши страницы
ashsvis
Войти
Регистрация
Восстановить пароль
"Орешек знаний тверд, Но все же мы не привыкли отступать..." (с)
Оценить эту запись

Шаг 5 (графический векторный редактор). Изменение размеров фигур

Запись от ashsvis размещена 11.11.2018 в 19:41

Продолжаем, мои верные читатели, строительство простого векторного графического редактора.
Сегодня у нас на очереди рассмотрение темы:

3. Изменение размеров фигур.

Размеры фигур будем изменять при помощи маркеров размеров, которые появляются вокруг фигуры, когда мы выбираем её
мышкой. Всего размерных маркеров восемь. Четыре по углам охватывающего прямоугольника, и ещё четыре - на серединах
сторон этого прямоугольника. Маркеры размеров имеют квадратную форму и нумеруются числами от 1 по 8, начиная с левого
верхнего угла и далее по ходу часовой стрелки.

Также потом мы будем рассматривать маркеры узлов, которые в обычном режиме не рисуются и не обслуживаются. Маркеры
узлов имеют круглую форму и нумеруются отрицательным значениями, начиная с -1 для первой узловой точки и так далее.
Точек узлов может быть много и максимальная граница не рассматривается. Когда приходит время обработки узловых точек,
берётся модуль значения индекса и вычитается единица. Таким образом получаем индекс в массиве точек, и, соответственно,
доступ к изменению координат узлов.


Для расчёта положения размерных маркеров создан метод GetBoundMarkers():
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/// <summary>
/// Метод строит восемь маркеров на углах и серединах сторон
/// </summary>
/// <param name="rect">размерный прямоугольник</param>
/// <returns>Возвращает перечисление из маркеров для изменения размера</returns>
private static IEnumerable<RectangleF> GetBoundMarkers(RectangleF rect)
{
    if (rect.Width <= 10) rect.Inflate(5, 0);
    if (rect.Height <= 10) rect.Inflate(0, 5);
    var pts = new PointF[8];
    pts[0].X = rect.Left; pts[0].Y = rect.Top;
    pts[1].X = rect.Left + rect.Width * 0.5f; pts[1].Y = rect.Top;
    pts[2].X = rect.Right; pts[2].Y = rect.Top;
    pts[3].X = rect.Right; pts[3].Y = rect.Top + rect.Height * 0.5f;
    pts[4].X = rect.Right; pts[4].Y = rect.Bottom;
    pts[5].X = rect.Left + rect.Width * 0.5f; pts[5].Y = rect.Bottom;
    pts[6].X = rect.Left; pts[6].Y = rect.Bottom;
    pts[7].X = rect.Left; pts[7].Y = rect.Top + rect.Height * 0.5F;
    rect.Inflate(-5, -5);
    var rects = new RectangleF[pts.Length];
    for (var i = 0; i < pts.Length; i++)
    {
        float k;
        if ((rect.Width <= 5) && ((i == 1) || (i == 5)))
            k = 1;
        else if ((rect.Height <= 5) && ((i == 3) || (i == 7)))
            k = 1;
        else
            k = 3;
        rects[i] = RectangleF.FromLTRB(pts[i].X - k, pts[i].Y - k,
                                        pts[i].X + k, pts[i].Y + k);
    }
    return rects;
}
На входе передаётся охватывающий фигуру прямоугольник, потом создаётся массив из восьми ячеек, который заполняется
центрами будущих маркеров размера. Затем создаётся массив прямоугольников с растяжкой от центров в три точки для
обычных размеров и в оду точку, если фигура так мала, что маркер размера её будет закрывать. Для маленьких размеров
сторон маркеры на середине сторон вообще не рисуются. Метод RectangleF.FromLTRB() создаёт прямоугольник по двум
точкам главной диагонали.

Рассмотренный выше метод GetBoundMarkers() используется в методе рисования ContainerPaint() выбранных фигур и в
методе MarkerSelected(), который проверяет попадание "мышиного" указателя на маркер размера:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/// <summary>
/// Поиск номера маркера в разных режимах редактора
/// </summary>
/// <param name="pt">точка "нажатия" мышки</param>
/// <param name="figure">проверяемая фигура</param>
/// <returns>индекс маркера</returns>
public int? MarkerSelected(PointF pt, Figure figure)
{
    if (NodeChanging)
    {
        // в режиме изменения узлов
        var rects = GetNodeMarkers(figure);
        for (var i = 0; i < rects.Length; i++)
        {
            if (rects[i].Contains(pt))
                return -(i + 1);
        }
    }
    else
    {
        // в режиме изменения размеров или перемещения
        var bounds = figure.GetBounds;
        var rects = GetBoundMarkers(bounds).ToArray();
        for (var i = 0; i < rects.Length; i++)
        {
            if (rects[i].Contains(pt))
                return i + 1;
        }
    }
    return null;
}
Как видно из кода - применяется метод RectangleF.Contains() для поиска попадания координат точки в координаты
прямоугольника маркера. Если совпадение случилось, то возвращаем индекс маркера согласно правилу нумерации маркеров размера и узлов.

Метод MarkerSelected(), через метод PointInMarker(), используется в обработчиках нажатия и перемещения мыши над
поверхностью контейнера и при вызове контекстного меню:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/// <summary>
/// Проверка нажатия на маркер в фигуре
/// положительные индексы - это маркеры размеров,
/// отрицательные - маркеры узлов,
/// ноль - тело фигуры
/// </summary>
/// <param name="location">точка выбора</param>
/// <param name="figure">указатель на фигуру</param>
/// <returns>индекс маркера</returns>
private int PointInMarker(PointF location, out Figure figure)
{
    figure = null;
    // проверка нажатия на маркерах
    for (var i = _selected.Count - 1; i >= 0; i--)
    {
        // смотрим на выбранные фигуры, начиная с хвоста списка
        var fig = _selected[i];
        var found = MarkerSelected(location, fig);
        if (found == null) continue;
        figure = fig;
        return (int)found;
    }
    return 0;
}
Нужно подчеркнуть, что проверка попадания курсора в маркер начинается с конца списка хранения фигур _selected,
так как по способу рисования наиболее "верхние" фигуры рисуются последними. Метод PointInMarker() возвращает
индекс маркера или ноль, а также ссылку на фигуру в параметре out Figure figure, если мышка попала на маркер.
Основное назначение метода PointInMarker() - поиск маркера. Если маркера не найдено, то фигура далее ищется методом
PointInFigure(e.Location).

Если маркер размера обнаружен, то его значение запоминается в контекстном свойстве MarkerIndex, также, как и величина
смещения мышки _mouseOffset. И оба эти значения служат для расчета будущего размера фигуры в методе DrawFocusFigure():
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private static void DrawFocusFigure(Figure figure, Graphics graphics, PointF offset,
                                    int markerIndex, bool nodeChanging)
{
    ....
    else if (!nodeChanging && (markerIndex > 0))
    {
        // тянут за размерный маркер
        var ps = figure.GetPoints();
        var oldrect = CalcFocusRect(new PointF(), figure, markerIndex);
        var newrect = CalcFocusRect(offset, figure, markerIndex);
        for (var i = 0; i < ps.Length; i++)
        {
            ps[i].X = newrect.Left + (ps[i].X - oldrect.Left) / oldrect.Width * newrect.Width;
            ps[i].Y = newrect.Top + (ps[i].Y - oldrect.Top) / oldrect.Height * newrect.Height;
        }
        DrawCustomFigure(graphics, ps, figure.Kind);
    }
}
Фиксация новых размеров фигуры происходит в методе обработки события ContainerMouseUp():
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private void ContainerMouseUp(object sender, MouseEventArgs e)
{
    // если мышь была захвачена
    if (_container.Capture)
    {
        // освобождаем захват мышки
        _container.Capture = false;
        // если нажата левая кнопка
        if (e.Button == MouseButtons.Left)
        {
            switch (EditorMode)
            {
                // перетаскивание выбранных фигур "мышкой"
                case EditorMode.Dragging:
                     ....
                    else if (!NodeChanging && (MarkerIndex > 0)) // тянут за размерный маркер
                    {
                        // перебираем все выделенные фигуры и меняем размер
                        foreach (var fig in _selected)
                            UpdateSize(fig, _mouseOffset, MarkerIndex);
                    }
                     ....
                    break;
            }
        }
        ....
    }
}
В методе UpdateSize(), выполняемом для каждой выбранной фигуры, происходит следующее:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/// <summary>
/// Изменение внутреннего массива точек фигуры при работе с маркерами
/// </summary>
/// <param name="fig">объект фигуры</param>
/// <param name="offset">смещение</param>
/// <param name="marker">индекс маркера</param>
public void UpdateSize(Figure fig, PointF offset, int marker)
{
    PointF[] pts;
    if (!NodeChanging && (marker > 0))
    {
        // перемещение границ
        pts = fig.GetPoints();
        var oldrect = CalcFocusRect(PointF.Empty, fig, marker);
        var newrect = CalcFocusRect(offset, fig, marker);
        for (var i = 0; i < pts.Length; i++)
        {
            pts[i].X = newrect.Left + (pts[i].X - oldrect.Left) / oldrect.Width * newrect.Width;
            pts[i].Y = newrect.Top + (pts[i].Y - oldrect.Top) / oldrect.Height * newrect.Height;
        }
        fig.SetPoints(pts);
    }
    else
        if (NodeChanging && (marker < 0))
    {
        // перемещение узлов
        pts = fig.GetPoints();
        var index = Math.Abs(marker) - 1;
        if ((index >= 0) && (index < pts.Length))
        {
            pts[index].X += offset.X;
            pts[index].Y += offset.Y;
            fig.SetPoints(pts);
        }
    }
}
Для перерасчёта координат точек, из которых состоит фигура, в строках 14 и 15 мы получаем прямоугольники без смещения и
с учётом смещения, которые вычисляются при помощи метода CalcFocusRect(). Далее в цикле, в строках 18 и 19, каждая
координата точки пропорционально смещается относительно нового размера стороны нового прямоугольника. То есть,
если фигура расширяется, то все точки, которые распределены по фигуре, также равномерно растянутся. Тоже касается и
высоты фигуры. Если высота уменьшается, то и внутренние точки будут ближе друг к другу.

Вот вся информация про изменение размеров фигур. В следующей статье немного остановимся на маркерах изменения узлов
и об алгоритме выбора узлов, добавления новых узлов и удалении существующих.

До скорого!
Всего комментариев 0
Комментарии
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2019, vBulletin Solutions, Inc.