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

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

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

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

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
Комментарии
 
Новые блоги и статьи
Программа опроса у.з. расходомера SLS-720F
Argus19 02.09.2026
Программа опроса у. з. расходомера SLS-720F Программа опрашивает один раз в минуту три ультразвуковых расходомера SLS-720F через интерфейс RS-485 по протоколу Modbus RTU. Опрашиваются регистры. . .
Hyper-V: Компьютер должен поддерживать доверенный платформенный модуль 2.0.
Maks 31.08.2026
При установке Windows 11 на виртуальную машину Hyper-V 2-го поколения вылезла такая ошибка: Решение: в параметрах виртуальной машины, в разделе "Безопасность" (Security) активировать флаг. . .
Архитектура биовида Стива в Майнкрафте: Зачем бонобо кубический каннибализм
anaschu 30.08.2026
Кубический Вагинокапитализм в Minecraft: Математический инвариант ОДУ и рок Стивов-бонобо Главная задача разработанной «Модели Всего» — наглядно продемонстрировать наличие системной «судьбы». . .
Оттачиваю умение писать js программы.
russiannick 30.08.2026
Проектом выходного дня стало написание Книги шифров Виженера. Итогом стала версия 200, синий туман. Синий туман назван так, потому что замораживает текст под собой. Нажатие синих кнопок управляют. . .
мат медиц модель 30. презентация проекта
anaschu 27.08.2026
хоп хоп хоп хидахоп, а я кладую))
Как у меня протекала болезнь
zorxor 27.08.2026
Здравствуйте, друзья! Эта запись блога предназначена именно для вас - для моих дорогих друзей, которые знали меня лично. Чтобы ответить на вопрос - а что же со мной произошло на самом деле? Я учился. . .
Нашел вот забавное видео о измерениях. Лучшее что я видел на эту тему
kumehtar 26.08.2026
ILETXiw9bMQ Основная суть и тезисы по измерениям: 0D (Нулевое измерение): точка, не имеющая длины, ширины, высоты или объема. Объект не может перемещаться в 0D. 1D (Первое измерение):. . .
[EasyBuilder Pro] Памятка по разработке для панелей Weintek
ФедосеевПавел 26.08.2026
Памятка по разработке для панелей Weintek ВВЕДЕНИЕ Ранее, при реализации проектов основное внимание уделял разработке управляющей программы для контроллера, а панели оператора доставалось время. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru