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

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

Запись от ashsvis размещена 11.11.2018 в 19:41
Показов 3504 Комментарии 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
Комментарии
 
Новые блоги и статьи
Установка MinGW GCC 16.2 и CMake
8Observer8 10.08.2026
VK Видео: https:/ / vkvideo. ru/ video-240781534_456239017 YouTube: eY5-5PyI9NM Текстовая версия
Неделя из жизни имитационной модели склада: мои кривые руки растут, откуда надо
anaschu 10.08.2026
Неделя из жизни имитационной модели склада: как я почти написал неправильную логику и что с этим делать Работаю сейчас над учебно-рабочим проектом: строю в AnyLogic имитационную модель процессов. . .
Калькулятор для расчета родства
russiannick 07.08.2026
1. Задача: Создать калькулятор для расчета родства. Родственных связей существует 8 ступеней, такие как: p - отец P - мать q - муж Q - жена b - брат B - сестра s - сын S - дочь
Мир по моей воле
kumehtar 07.08.2026
Когда-то кажется, что всё просто. Ты весь такой светлый. Причиняешь добро. Борешься за справедливость в этом тёмном мире. Потом начинаешь замечать одну неприятную вещь. Почти каждый хороший. . .
Кредитный калькулятор
Maks 05.08.2026
Решение задачи по прикладной информатике средствами 1С. Задача: Напишите приложение-калькулятор, которое помогает рассчитывать параметры кредита для аннуитетного и дифференцированного видов. . .
У нас сейчас поговорку "Опять 25" нужно переделать на "Опять +35".
kumehtar 04.08.2026
С ностальгией вспоминаю времена моего детства, когда у нас и правда +25 - была максимальная температура летом. Раньше +25 °C реально казались вершиной жары, когда можно было весь день пропадать на. . .
Как ИИ начал спорить и врать (возможно почуяв опасность для себя от индустрии - уход от электроники).
Hrethgir 04.08.2026
Недельный диалог, на фоне событий с НПЗ. Да, из спирта можно получать бензин, и это не сложно. Но потом в схеме я решил избавиться от насоса, при этом полностью сделав контроль подачи спирта в. . .
Термопринтер QR701
Argus19 03.08.2026
Термопринтер QR701 Купил два термопринтера QR701. На сэлф-тесте написано: Language: PC936 (GB18030). Что означает, что принтеры могут печатать только латиницу и китайские иероглифы. Так же. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru