Форум программистов, компьютерный форум, киберфорум
C# Windows Forms
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.57/177: Рейтинг темы: голосов - 177, средняя оценка - 4.57
 Аватар для ashsvis
923 / 503 / 202
Регистрация: 08.10.2018
Сообщений: 1,553
Записей в блоге: 11

Простой векторный графический редактор (разбор полётов)

16.11.2018, 09:22. Показов 36747. Ответов 127

Студворк — интернет-сервис помощи студентам
Всем привет! Меня зовут ashsvis и я программист...
Я пытался бороться с этим "недугом", но эта привычка затягивает меня всё глубже... (шутка)
Короче, для тех кто не в теме: я пытаюсь сделать простой векторный графический редактор.
Сначала я выпустил цикл статей по этому поводу (см. https://www.cyberforum.ru/blog... tegory389/)

Получилась начальная версия редактора (можно забрать по ссылке в статье https://www.cyberforum.ru/blog... g5533.html)
Первая версия, она известно какая и, благодаря критике моих более опытных коллег, была несколько переработана,
с целью улучшить её (а с какой-же ещё?) и получилась версия вторая (можно забрать по ссылке в статье https://www.cyberforum.ru/blog... g5536.html)

После второй версии мне намекнули, что я начал движение вообще не в ту сторону и предложили выложить в отдельную тему,
на стол прозектора, так сказать. Что я и делаю.

Подключайтесь к обсуждению, господа программисты. Давайте выведем меня на чистую воду...

Добавлено через 3 часа 28 минут
[для "затравки" дискуссии]
У меня тут появилась мысль, как ни странно, что если попытаться перевести логику работы редактора из WinForms в WPF,
то с текущей моделью это будет сделать сложновато. Там ведь (в WPF) всё другое, даже точки Point на основе double...
0
Лучшие ответы (1)
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
16.11.2018, 09:22
Ответы с готовыми решениями:

Написать простой графический редактор электрических цепей
Пожалуйста,не понимаю как написать простенький граф.редактор для проектирования электрических цепей. Убил все нервы, как только не пытался...

Простой графический редактор, как сохранять изображение
Здравствуйте! Мне задали на C# сделать какое-то подобие Paint. Всё уже есть, все инструменты. Всё рисуется методом Graphics.Draw...(), и...

Создать простой векторный графический редактор
Доброго времени суток! Мне необходимо создать простой векторный графический редактор, в котором должна быть возможность рисовать лини,...

127
 Аватар для ashsvis
923 / 503 / 202
Регистрация: 08.10.2018
Сообщений: 1,553
Записей в блоге: 11
17.11.2018, 14:15  [ТС]
Студворк — интернет-сервис помощи студентам
Обновил репозиторий до текущего состояния:
https://github.com/ashsvis/VectorGraphicsEditor
Сделайте его свойством. А еще лучше сделайте операторы приведения типа.
Вот здесь, пожалуйста, по-подробнее...
0
Эксперт .NETАвтор FAQ
 Аватар для Storm23
10428 / 5158 / 1825
Регистрация: 11.01.2015
Сообщений: 6,226
Записей в блоге: 34
17.11.2018, 15:11
Цитата Сообщение от ashsvis Посмотреть сообщение
Вот здесь, пожалуйста, по-подробнее...
Операторы преобразования типа:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    /// <summary>
    /// Сериализуемая обертка над GraphicsPath
    /// </summary>
    public class SerializableGraphicsPath
    {
        public GraphicsPath Path = new GraphicsPath();
 
        //....
 
        public static implicit operator GraphicsPath(SerializableGraphicsPath path)
        {
            return path.Path;
        }
 
        public static implicit operator SerializableGraphicsPath(GraphicsPath path)
        {
            return new SerializableGraphicsPath() {Path = path};
        }
    }
После этого мы можем писать код такого типа:
C#
1
2
3
4
5
6
            var sp = new SerializableGraphicsPath();
            GraphicsPath p = sp;
 
            //или
            Graphics gr = ...;
            gr.DrawPath(Pens.Black, sp);
SerializableGraphicsPath будет автоматически преобразовываться к GraphicsPath. И наоборот.

Добавлено через 7 минут
Цитата Сообщение от ashsvis Посмотреть сообщение
Обновил репозиторий до текущего состояния:
А что это:
https://github.com/ashsvis/Vec... ure.cs#L35
?

Далее https://github.com/ashsvis/Vec... der.cs#L31
Не нужно передавать size или radius в геометрию.
Понимаете, геометрия задает только форму. Размеры задаются через Transform. Вся геометрия создается единичного размера.

EllipceGeometry, RectangleGeometry - вообще не нужны. Мы же условились, что все примитивные фигуры - через PrimitiveGeometry. Зачем вы опять лепите эти все лишние классы?
0
 Аватар для ashsvis
923 / 503 / 202
Регистрация: 08.10.2018
Сообщений: 1,553
Записей в блоге: 11
17.11.2018, 15:43  [ТС]
Спасибо за операторы преобразования типа, это ускорит мою работу, сам бы рыл долго...
Цитата Сообщение от Storm23 Посмотреть сообщение
А что это
Сначала я думал, что для рисования фигур мне понадобится указывать где рисовать (Location) и какого размера (Size)
Потом я вышел из положения, передавая RectangleF для EllipceGeometry, RectangleGeometry, для примитивных тоже
сделал... Видимо я не совсем представляю, что в конце будет происходить, то есть передача мышиного прямоугольника
в размер фигуры.
Цитата Сообщение от Storm23 Посмотреть сообщение
Зачем вы опять лепите
Я не со зла, а по скудоумию... Видимо мне нужен ещё примерчик (базовый), как будем использовать Matrix.

Кстати, я по аналогии соорудил вот что:
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
    [Serializable]
    public class SerializableGraphicsMatrix : ISerializable
    {
        public Matrix Matrix = new Matrix();
 
        public SerializableGraphicsMatrix()
        {
        }
 
        public SerializableGraphicsMatrix(SerializationInfo info, StreamingContext context)
        {
            if (info.MemberCount ==6)
            {
                var el = (float[])info.GetValue("e", typeof(float[]));
                Matrix = new Matrix(el[0], el[1], el[2], el[3], el[4], el[5]);
            }
            else
                Matrix = new Matrix();
        }
 
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            if (Matrix.Elements.Length <= 0) return;
            info.AddValue("e", Matrix.Elements);
        }
    }
и тест прошёл!

Добавлено через 20 минут

Не по теме:

Цитата Сообщение от Storm23 Посмотреть сообщение
Операторы преобразования типа:
Какая классная штука! И github классная штука!

0
Эксперт .NETАвтор FAQ
 Аватар для Storm23
10428 / 5158 / 1825
Регистрация: 11.01.2015
Сообщений: 6,226
Записей в блоге: 34
17.11.2018, 15:46
Цитата Сообщение от ashsvis Посмотреть сообщение
Сначала я думал, что для рисования фигур мне понадобится указывать где рисовать (Location) и какого размера (Size)
Потом я вышел из положения, передавая RectangleF для EllipceGeometry, RectangleGeometry, для примитивных тоже
сделал... Видимо я не совсем представляю, что в конце будет происходить, то есть передача мышиного прямоугольника
в размер фигуры.
Не нужны вам никакие Location, никакие RectangleF. Уберите все это. Я вам дал полный пример метода BuildSquareGeometry, просто сделайте по аналогии построение других фигур. Размеры фигур в геометрии - фиксированные.
Наследников класса Geometry - только три: PrimitiveGeometry, TextGeometry и PolygoneGeometry.
Два из них я уже написал. Вам нужно сделать только третий.

Цитата Сообщение от ashsvis Посмотреть сообщение
по аналогии соорудил вот что
Это хорошо, но сейчас это не нужно.
Цитата Сообщение от ashsvis Посмотреть сообщение
то есть передача мышиного прямоугольника
Вы снова зацикливаетесь на интерфейсе. Не нужно этого. Прямоугольник, задаваемый мышкой будет храниться в матрице Transform. К геометрии это отношение не имеет.
Мыслите абстрактно. Геометрия - это форма фигуры. А размеры, позиция и вращение - это Transform.
0
 Аватар для ashsvis
923 / 503 / 202
Регистрация: 08.10.2018
Сообщений: 1,553
Записей в блоге: 11
17.11.2018, 16:22  [ТС]
Цитата Сообщение от Storm23 Посмотреть сообщение
Мыслите абстрактно
Видимо, мой уровень абстрагирования не достаточно высок. Буду стараться!

Но когда у меня не было радиуса, круг в тестах рисовался точкой в левом верхнем углу...
А как различить между собой квадрат и прямоугольник, если размеры не задавать?

Добавлено через 2 минуты
Мне для тестов что-то через матрицу нужно соорудить, иначе я не увижу, правильно ли методы работают...
Пошёл книжки читать...

Добавлено через 10 минут
Убрал размеры из геометрии, репозиторий обновил.
0
Эксперт .NETАвтор FAQ
 Аватар для Storm23
10428 / 5158 / 1825
Регистрация: 11.01.2015
Сообщений: 6,226
Записей в блоге: 34
17.11.2018, 16:25
Цитата Сообщение от ashsvis Посмотреть сообщение
Но когда у меня не было радиуса, круг в тестах рисовался точкой в левом верхнем углу...
Очень просто:
C#
1
fig.Transform.Scale(100, 100);
Цитата Сообщение от ashsvis Посмотреть сообщение
А как различить между собой квадрат и прямоугольник, если размеры не задавать?
А они и не различаются в геометрии. Они различаются поведением. При ресайзе - квадрат будет ресайзится с сохранением аспекта, а прямогуольник - без сохранения.
1
 Аватар для ashsvis
923 / 503 / 202
Регистрация: 08.10.2018
Сообщений: 1,553
Записей в блоге: 11
17.11.2018, 18:55  [ТС]
С выводом примитивов вроде разобрался. В тестах выводятся квадрат и круг. Но это если задаёшь одинаковые числа в
Matrix.Scale, а если разные, то получаются прямоугольник и эллипс.

С TextGeometry на получается ограничить область текста, чтобы переносил на другую строку.

Сейчас в методе применяется Point.Empty и текст выводится в одну строку и отсекается по правому краю,
а если ставишь Rectangle с настройками как у примитивов, то текст вообще не выводится. И Matrix.Scale к TextGeometry
применять нельзя - текст опять куда-то исчезает...
0
Эксперт .NETАвтор FAQ
 Аватар для Storm23
10428 / 5158 / 1825
Регистрация: 11.01.2015
Сообщений: 6,226
Записей в блоге: 34
17.11.2018, 21:02
Цитата Сообщение от ashsvis Посмотреть сообщение
С выводом примитивов вроде разобрался.
Ок. Но я не вижу изменений на github. И кстати, коммиты на git нужно делать как можно чаще. Сделали какой-то законченный функционал - отправьте на git, сделали следующий - снова отправьте. Во-первых, так видно ход работы, во-вторых вы сможете сделать откат к предыдущей версии, если вдруг что-то сломалось в коде.

Цитата Сообщение от ashsvis Посмотреть сообщение
Но это если задаёшь одинаковые числа в
Matrix.Scale, а если разные, то получаются прямоугольник и эллипс.
Ну я уже писал про это. Геометрии у них одинаковые, у них разное поведение. Вы не можете присваивать произвольные числа в Scale для квадрата. Мы сейчас как раз к этому переходим.

Цитата Сообщение от ashsvis Посмотреть сообщение
С TextGeometry на получается ограничить область текста, чтобы переносил на другую строку.
А должен? В Paint.NET например текст не ограничивается. И я сделал также. Ну в принципе мы позже сможем изменить это поведение, это не сложно.

Цитата Сообщение от ashsvis Посмотреть сообщение
И Matrix.Scale к TextGeometry
применять нельзя - текст опять куда-то исчезает...
Ну тут вы что-то нахимичили, все должно работать.

Итак, вернемся к нашей модели.
Теперь сделаем чуть более сложные вещи.
Во-первых, мы знаем, что геометрия определяет не только форму объекта, но и поведение. Квадрат отличается от прямоугольника тем, что его нельзя растягивать произвольным образом. Зафиксируем это в коде.
Поскольку поведение зависит от геометрии, то это должно быть прописано в классе Geometry. Для этого, добавим поле AllowedOperations:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    /// <summary>
    /// Класс-основа для задания геометрии фигуры
    /// </summary>
    [Serializable]
    public abstract class Geometry
    {
        /// <summary>
        /// Предоставление пути для рисования фигуры
        /// </summary>
        public abstract GraphicsPath Path { get; }
 
        /// <summary>
        /// Допустимые операции над геометрией
        /// </summary>
        public abstract AllowedOperations AllowedOperations { get; }
    }
Перечисление AllowedOperations выглядит так:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
    /// <summary>
    /// Допустимые операции над геометрией
    /// </summary>
    [Serializable]
    [Flags]
    public enum AllowedOperations : uint
    {
        None    = 0x0,
        Scale   = 0x1, //can be scaled
        Size    = 0x2, //can change size by vert and horiz
        Rotate  = 0x4, //can be rotated
        Select  = 0x8, //can be selected
        All     = 0xffffffff,
    }
Потом мы сможем расширить список допустимых операций, если будет нужно.

Далее, сделаем модификации в классе FigureBuilder. Он должен задавать не только GraphicsPath, но и присваивать AllowedOperations. Вот теперь квадрат будет отличаться от прямоугольника:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    /// <summary>
    /// Строит компоненты фигуры
    /// </summary>
    public class FigureBuilder
    {
        public void BuildSquareGeometry(Figure figure)
        {
            var path = new SerializableGraphicsPath();
            path.Path.AddRectangle(new RectangleF(-0.5f, -0.5f, 1, 1));
            figure.Geometry = new PrimitiveGeometry(path, AllowedOperations.All ^ AllowedOperations.Size);
        }
 
        public void BuildRectangleGeometry(Figure figure)
        {
            var path = new SerializableGraphicsPath();
            path.Path.AddRectangle(new RectangleF(-0.5f, -0.5f, 1, 1));
            figure.Geometry = new PrimitiveGeometry(path, AllowedOperations.All);
        }
 
        //.....
    }
Как видно, для квадрата мы выключаем возможность менять размер по вертикали и горизонтали (оставляем только масштабирование).

Идем далее.
Создадим класс Selection, который сможет хранить список выделенных фигур, а также производить операции над ними.
Тут внимательно следите за руками. Этот класс тоже является фигурой. И мы его будет отрисовывать также, как и остальные фигуры.
Таким образом, у нас не будет каких-то особых средств для отрисовки выделенных объектов. Мы просто добавим новую фигуру, которая будет отрисовывать выделение.
Кроме того, при изменении Transform это фигуры, этот Transform будет применяться к выделенным фигурам. И таким образом мы сможем реализовывать перемещение, шкалирование и вращение как одной, так и нескольких выделенных фигур.
Класс Selection предлагаю сделать так:
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
    /// <summary>
    /// Набор выделенных фигур и операции над ними
    /// </summary>
    public class Selection : Figure
    {
        private HashSet<Figure> Selected = new HashSet<Figure>();
 
        public void Clear()
        {
            Selected.Clear();
            GrabGeometry();
        }
 
        public void Add(Figure fig)
        {
            Selected.Add(fig);
            GrabGeometry();
        }
 
        public void Remove(Figure fig)
        {
            Selected.Remove(fig);
            GrabGeometry();
        }
 
        /// <summary>
        /// Копирование геометрий выделенных фигур в свою геометрию
        /// </summary>
        private void GrabGeometry()
        {
            //grab geometry of selected figures
            var path = new GraphicsPath();
            foreach (var fig in Selected)
                path.AddPath(fig.GetTransformedPath(), false);
 
            //draw frame around selected figures
            var bounds = path.GetBounds();
            path.AddRectangle(bounds);
 
            // choose allowed operations
            // if selected only one figure - just use its AllowedOperations
            // otherwise - allow all operations
            var allowedOperations = Selected.Count == 1 ? Selected.First().Geometry.AllowedOperations : AllowedOperations.All;
 
            //assign geometry
            Geometry = new PrimitiveGeometry(path, allowedOperations);
 
            //reset transform to Identical matrix
            Transform = new Matrix();
        }
 
        /// <summary>
        /// Применение своего Transform к Transform выделенных фигур
        /// </summary>
        public void PushTransformToSelectedFigures()
        {
            foreach (var fig in Selected)
                fig.Transform.Matrix.Multiply(Transform);
 
            GrabGeometry();
        }
    }
Создайте unit-тесты, создайте несколько фигур, добавьте их в выделенные фигуры в Selection, сделайте операции над Selection.Transform (перемещение, шкалирование, вращение) и убедитесь, что выделенные фигуры также перемещаются, вращаются и т.д.
2
 Аватар для ashsvis
923 / 503 / 202
Регистрация: 08.10.2018
Сообщений: 1,553
Записей в блоге: 11
18.11.2018, 12:09  [ТС]
Цитата Сообщение от Storm23 Посмотреть сообщение
добавим поле AllowedOperations
А в каком месте модели мы будем применять ограничения AllowedOperations?
По идее, это должно происходить в методе Figure.GetTransformedPath()
C#
1
2
3
4
5
6
7
8
9
10
11
12
public GraphicsPath GetTransformedPath()
{
    // создаём копию геометрии фигуры
    var path = (GraphicsPath)Geometry.Path.Clone();
    
    ... здесь нужно к (Matrix)Transform как-то применить ограничения, указанные
        в наборе флагов AllowedOperations...
 
    // трансформируем её при помощи Трансформера
    path.Transform(Transform);
    return path;
}
А как это сделать, пока не пойму, не могу перейти к применению Selected и написанию тестов. Поможете мне?
В матрице я как-то должен манипулировать коэффициентами m11 и m12, чтобы сохранять пропорции для квадрата.


Цитата Сообщение от Storm23 Посмотреть сообщение
А должен? В Paint.NET например текст не ограничивается
Мне кажется, нужно реализовать функционал поведения текста в компоненте Label, то есть всякие выравнивания и
по ширине и по высоте и justify. Для этого и есть прямоугольник, который мы не применяем в TextGeometry.
0
Эксперт .NETАвтор FAQ
 Аватар для Storm23
10428 / 5158 / 1825
Регистрация: 11.01.2015
Сообщений: 6,226
Записей в блоге: 34
18.11.2018, 12:18
Цитата Сообщение от ashsvis Посмотреть сообщение
А в каком месте модели мы будем применять ограничения AllowedOperations?
Да пока никак мы их не применяем, мы просто их декларируем. Применять их будет класс, который будет тягать маркер.
Цитата Сообщение от ashsvis Посмотреть сообщение
По идее, это должно происходить в методе Figure.GetTransformedPath()
Нет.

Цитата Сообщение от ashsvis Посмотреть сообщение
Мне кажется, нужно реализовать функционал поведения текста в компоненте Label, то есть всякие выравнивания и
по ширине и по высоте и justify.
Ну вообще-то мы не текстовый редактор делаем....
В любом случае, это позже. Не зацикливайтесь на этом. Итак медленно все идет.

Класс Selection готов? Протестирован?
Там нужно исправить:
C#
1
fig.Transform.Matrix.Multiply(Transform);
на
C#
1
fig.Transform.Matrix.Multiply(Transform, MatrixOrder.Append);
0
 Аватар для ashsvis
923 / 503 / 202
Регистрация: 08.10.2018
Сообщений: 1,553
Записей в блоге: 11
18.11.2018, 12:34  [ТС]
Цитата Сообщение от Storm23 Посмотреть сообщение
Класс Selection готов? Протестирован?
Меня держит непонимание, работает ли ограничения AllowedOperations в тестах. Сейчас я вижу, что
не работают. К тестированию Selection не могу перейти, так как трансформации правильно не работают,
что я увижу в тестах - ничего (то есть буду скапливать баги на потом и буду работать со следующим классом
модели, не понимая до конца, как работает (и работает ли) уже сделанное. {смайл обезьяна, тыкающая на клавиши}

Добавлено через 2 минуты
Цитата Сообщение от Storm23 Посмотреть сообщение
Итак медленно все идет.
Ну тут ничего не могу поделать... У нас же не скоростное программирование...
0
Эксперт .NETАвтор FAQ
 Аватар для Storm23
10428 / 5158 / 1825
Регистрация: 11.01.2015
Сообщений: 6,226
Записей в блоге: 34
18.11.2018, 12:58
Цитата Сообщение от ashsvis Посмотреть сообщение
Меня держит непонимание, работает ли ограничения AllowedOperations в тестах. Сейчас я вижу, что не работают.
Да как они могут работать, если мы их нигде не используем пока? Я ж говорю, мы их просто декларируем. Что вы ожидаете там увидеть в тестах?

Цитата Сообщение от ashsvis Посмотреть сообщение
К тестированию Selection не могу перейти, так как трансформации правильно не работают,
Как это не работают? Я сам уже скачал ваш проект с git и сам добавил Selection и уже сам проверил. Работает там все отлично.
0
 Аватар для ashsvis
923 / 503 / 202
Регистрация: 08.10.2018
Сообщений: 1,553
Записей в блоге: 11
19.11.2018, 05:20  [ТС]
Цитата Сообщение от Storm23 Посмотреть сообщение
как они могут работать
Когда я делаю тест на рисование квадрата, а задаю ему размер прямоугольника (через Matrix.Scale()),
то я бы хотел, чтобы ^AllowedOperations.Size уже не позволял рисовать прямоугольник, а рисовать квадрат,
вот это я хочу видеть в тестах, которые формируют .bmp файлы.
Работу Selection тоже надо проверять визуально (видеть эту рамку, наконец) и где? Тоже в тестах, так как
интерфейсную часть пока не написали.
Хорошо бы уже в интерфейсной части расставлять наши фигуры по полю редактирования, а следующим этапом
работать с Selection и отлаживать его. То есть делать цикл выпуска завершённым. Или я не прав?
Цитата Сообщение от Storm23 Посмотреть сообщение
сам добавил Selection и уже сам проверил. Работает там все отлично.
Ну, что же, это ваша заготовка, почему бы ей не работать. Я боюсь, что не буду успевать за ходом ваших мыслей,
если будем двигаться такими темпами. Тем более, что свободного времени может и не быть так много, как в предыдущие
дни. Так что я предлагаю немножко сделать кода для для интерфейсной части, чтобы хотя бы вытащить фигуры на
свет Божий.

Добавлено через 7 часов 44 минуты
Storm23, мы продолжим "вгрызаться" в эту интереснейшую тему? Я весь внимание...
0
 Аватар для ashsvis
923 / 503 / 202
Регистрация: 08.10.2018
Сообщений: 1,553
Записей в блоге: 11
19.11.2018, 08:33  [ТС]
Создал такой тест для Selection:
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
37
38
39
40
[TestMethod]
public void SelectionFigureTestMethod()
{
    var builder = new FigureBuilder();
    var list = new List<Figure>(); 
    var section = new Selection();
    var colors = new [] {Color.White, Color.Red, Color.Green, Color.Yellow, Color.Blue};
    for (var i = 0; i < 5; i++)
    {
        var fig = new Figure();
        list.Add(fig);
        fig.Style.FillStyle.Color = colors[i];
        fig.Transform.Matrix.Translate(100 + i*10, 50 + i*10);
        fig.Transform.Matrix.Scale(160, 80);
        builder.BuildPolygoneGeometry(fig);
    }
    section.Clear();
    foreach (var figure in list)
        section.Add(figure);
    // пробуем отрисовывать
    using (var bmp = new Bitmap(320, 240))
    {
        using (var canvas = Graphics.FromImage(bmp))
        {
            using (var pen = new Pen(Color.Gray))
            {
                pen.DashStyle = DashStyle.DashDotDot;
                canvas.DrawRectangle(pen, 0, 0, 319, 239);
            }
            foreach (var figure in list)
                figure.Renderer.Render(canvas, figure);
            bmp.Save("RenderedFiveFigures.bmp");
            section.Renderer.Render(canvas, section);
            bmp.Save("RenderedFiveFiguresWithSelection.bmp");
            section.Remove(list[2]);
            section.Renderer.Render(canvas, section);
            bmp.Save("RenderedFourFiguresWithSelection.bmp");
        }
    }
}
Его результаты работы:
Название: RenderedFiveFigures.jpg
Просмотров: 187

Размер: 9.3 Кб

Название: RenderedFiveFiguresWithSelection.jpg
Просмотров: 188

Размер: 13.1 Кб

Название: RenderedFourFiguresWithSelection.jpg
Просмотров: 188

Размер: 10.6 Кб
И результат этого теста меня не радует...
0
Эксперт .NETАвтор FAQ
 Аватар для Storm23
10428 / 5158 / 1825
Регистрация: 11.01.2015
Сообщений: 6,226
Записей в блоге: 34
19.11.2018, 09:54
Цитата Сообщение от ashsvis Посмотреть сообщение
я бы хотел, чтобы ^AllowedOperations.Size уже не позволял рисовать прямоугольник
Я бы тоже много чего хотел. Но я вам в третий раз говорю - это не реализуется на данном этапе. Мы просто это декларируем. Понимаете? Это реализуется на уровне функционала, то есть движения маркеров. А их пока нет. А мы пока разрабатываем модель данных. Декларативно.

Цитата Сообщение от ashsvis Посмотреть сообщение
Создал такой тест для Selection
Вот тест для selection:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
        [TestMethod]
        public void SelectionTest()
        {
            //создаем первую фигуру
            var builder = new FigureBuilder();
            var fig1 = new Figure();
            fig1.Transform.Matrix.Translate(50, 50);
            fig1.Transform.Matrix.Scale(100, 100);
            builder.BuildEllipseGeometry(fig1);
 
            //создаем вторую фигуру
            var fig2 = new Figure();
            fig2.Transform.Matrix.Translate(150, 150);
            fig2.Transform.Matrix.Scale(100, 100);
            builder.BuildRectangleGeometry(fig2);
 
            //рисуем до выделения
            Draw("c:\\1.png", fig1, fig2);
 
            //создаем selection
            var selection = new Selection();
            selection.Style.BorderStyle.Width = 1;
            selection.Style.BorderStyle.Color = Color.Magenta;
 
            //выделяем фигуры 
            selection.Add(fig1);
            selection.Add(fig2);
 
            //рисуем после выделения
            Draw("c:\\2.png", fig1, fig2, selection);
 
            //вращаем и выделенные фигуры
            Matrix m = selection.Transform;
            m.RotateAt(45, new PointF(100, 100));
            selection.PushTransformToSelectedFigures();
 
            //рисуем после вращения выделенных фигур
            Draw("c:\\3.png", fig1, fig2, selection);
 
            //перемещаем выделенные фигуры
            m = selection.Transform;
            m.Translate(0, 100);
            selection.PushTransformToSelectedFigures();
 
            //рисуем после перемещения выделенных фигур
            Draw("c:\\4.png", fig1, fig2, selection);
 
            //снимаем выделение, отрисовываем результат
            selection.Clear();
            Draw("c:\\5.png", fig1, fig2, selection);
        }
 
        private static void Draw(string fileName, params Figure[] figures)
        {
            using (var bmp = new Bitmap(400, 400))
            {
                using (var canvas = Graphics.FromImage(bmp))
                {
                    canvas.Clear(Color.White);
                    foreach(var fig in figures)
                        fig.Renderer.Render(canvas, fig);
                }
                bmp.Save(fileName);
            }
        }
Вот результаты:


Вникните в смысл класса Seleсtion. Не усложняйте жизнь ненужными подробностями и техническими деталями. Если я вижу, что проблема в принципе решаема, я НЕ реализую этот функционал на данном этапе. Потому что это усложнит и затянет разработку. А первым делом нужно получить MVP (minimum valuable product). А технические (и решаемые в принципе) мелочи - это оставим на доводку продукта.
Когда вникните в Seleсtion, пойдем дальше.
0
 Аватар для ashsvis
923 / 503 / 202
Регистрация: 08.10.2018
Сообщений: 1,553
Записей в блоге: 11
19.11.2018, 11:49  [ТС]
Storm23,
Скопировал ваш тест для Seletion, чтобы с ним поработать...
Всё идет хорошо, но до того как вызываю selection.PushTransformToSelectedFigures ();
После этого фигуры исчезают из поля зрения. Начал копать, почему и оказалось, что
после применения fig.Transform.Multiply(Transform), когда делал m.Translate(0, 100); (только его),
то координата Y получилась 10000.
C#
1
2
3
4
5
6
7
8
9
10
/// <summary>
/// Применение своего Transform к Transform выделенных фигур
/// </summary>
public void PushTransformToSelectedFigures()
{
    foreach (var fig in _selected)
        fig.Transform.Multiply(Transform);
 
    GrabGeometry();
}
Хотя чего тут удивляться, если мы перемножаем (Multiply). Поэтому фигуры исчезают из поля зрения.
Чего я такого делаю не правильно?
0
Эксперт .NETАвтор FAQ
 Аватар для Storm23
10428 / 5158 / 1825
Регистрация: 11.01.2015
Сообщений: 6,226
Записей в блоге: 34
19.11.2018, 11:51
ashsvis, пожалуйста, внимательно почитайте этот пост
Простой векторный графический редактор (разбор полётов)
0
 Аватар для ashsvis
923 / 503 / 202
Регистрация: 08.10.2018
Сообщений: 1,553
Записей в блоге: 11
19.11.2018, 12:09  [ТС]
Storm23, ой, простите меня... Сейчас же внесу исправления!

Добавлено через 15 минут
Да, внёс в рабочий репозиторий. И тест свой я делал всё на одной канве, поэтому и смешение рисунков.
Да, времени я изрядно забрал у проекта из-за непроходимой тупости...
Понял, осознал...
0
Эксперт .NETАвтор FAQ
 Аватар для Storm23
10428 / 5158 / 1825
Регистрация: 11.01.2015
Сообщений: 6,226
Записей в блоге: 34
19.11.2018, 13:26
Ок, продолжаем.

Добавим немного вспомогательных методов в Selection.
Это такие методы:
  • Translate
  • Scale
  • Skew
  • Rotate

В принципе все эти операции можно сделать и через Transform, но мы сделаем эти преобразования более удобными для внешнего кода.

Код добавляемых методов:
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
        /// <summary>
        /// Переводит точку из локальных нормализированных координат (0,0)-(1,1) в мировые координаты
        /// </summary>
        PointF ToWorldCoordinates(PointF p)
        {
            var bounds = GetTransformedPath().GetBounds();
            return new PointF(bounds.Left + p.X * bounds.Width, bounds.Top + p.Y * bounds.Height);
        }
 
        public void Translate(float offsetX, float offsetY)
        {
            var m = new Matrix();
            m.Translate(offsetX, offsetY);
            //
            Transform = m;
        }
 
        public void Scale(float scaleX, float scaleY, PointF anchor)
        {
            //можем менять размер?
            var allowSize = Geometry.AllowedOperations.HasFlag(AllowedOperations.Size);
            var allowScale = Geometry.AllowedOperations.HasFlag(AllowedOperations.Scale);
 
            if (!allowScale && !allowSize)
                return;//не можем менять размеры
 
            if (allowScale && !allowSize)
                //можем шкалировать, но с сохранением аспекта
                //сохраняем аспект
                scaleX = scaleY = Math.Max(scaleX, scaleY);
 
            //считаем якорь в мировых координатах
            var worldAnchor = ToWorldCoordinates(anchor);
 
            //шкалируем относительно якоря
            var m = new Matrix();
            m.Translate(worldAnchor.X, worldAnchor.Y);    //переводим центр координат в якорь
            m.Scale(scaleX, scaleY);                      //масштабируем
            m.Translate(-worldAnchor.X, -worldAnchor.Y);  //возвращаем центр координат
 
            //
            Transform = m;
        }
 
        public void Skew(float skewX, float skewY, PointF anchor)
        {
            //считаем якорь в мировых координатах
            var worldAnchor = ToWorldCoordinates(anchor);
 
            //сдвигаем относительно якоря
            var m = new Matrix();
            m.Translate(worldAnchor.X, worldAnchor.Y);    //переводим центр координат в якорь
            m.Shear(skewX, skewY);                        //сдвигаем
            m.Translate(-worldAnchor.X, -worldAnchor.Y);  //возвращаем центр координат
 
            //
            Transform = m;
        }
 
        public void Rotate(float angle, PointF center)
        {
            //можем вращать?
            var allowRotate = Geometry.AllowedOperations.HasFlag(AllowedOperations.Rotate);
 
            if (!allowRotate)
                return;//не можем вращать
 
            //считаем якорь в мировых координатах
            var worldAnchor = ToWorldCoordinates(center);
 
            //вращаем относительно якоря
            var m = new Matrix();
            m.RotateAt(angle, worldAnchor);      //вращаем
 
            //
            Transform = m;
        }
Часть методов имеют якорь. Это точка относительно которой производится операция. Якорь задается в локальных нормализированных координатах. Верхний левый угол рамки - это точка (0,0), нижний правый угол - точка (1,1). Нормализированные координаты более удобны потому что они будут константными.

Также я добавил операцию Skew, которая делает искривление.

Также часть операций поддерживает AllowOperations. И если, например Size не разрешено, то метод Scale сделает масштабирование с сохранением аспекта.
Обратите внимание на то, что если выделено несколько фигур, то для них разрешены все операции. Это более логично, так делают Paint.NET и Photoshop. Если часть фигур шкалировать, а часть - нет, то это нарушает взаимоотношения между фигурами, по сути портит рисунок. Поэтому, если мы выделяем только квадрат, то он может шкалироваться только с сохранением аспекта. Но если мы выделили несколько квадратов, то они могут шкалироваться произвольным образом.

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

Тесты:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
        [TestMethod]
        public void SelectionTest2()
        {
            //создаем первую фигуру
            var builder = new FigureBuilder();
            var fig1 = new Figure();
            fig1.Transform.Matrix.Translate(150, 150);
            fig1.Transform.Matrix.Scale(30, 30);
            builder.BuildEllipseGeometry(fig1);
 
            //создаем вторую фигуру
            var fig2 = new Figure();
            fig2.Transform.Matrix.Translate(200, 200);
            fig2.Transform.Matrix.Scale(30, 30);
            builder.BuildRectangleGeometry(fig2);
 
            //рисуем до выделения
            Draw("c:\\1.png", fig1, fig2);
 
            //создаем selection
            var selection = new Selection();
            selection.Style.BorderStyle.Width = 1;
            selection.Style.BorderStyle.Color = Color.Magenta;
 
            //выделяем фигуры 
            selection.Add(fig1);
            selection.Add(fig2);
 
            //ресайз по X относительно левого края
            selection.Scale(2, 1, new PointF(0, 0.5f));
            selection.PushTransformToSelectedFigures();
            //рисуем
            Draw("c:\\2.png", fig1, fig2, selection);
 
            //ресайз по Y относительно нижнего края
            selection.Scale(1, 2, new PointF(0.5f, 1));
            selection.PushTransformToSelectedFigures();
            //рисуем
            Draw("c:\\3.png", fig1, fig2, selection);
 
            //вращение относительно центра
            selection.Rotate(45, new PointF(0.5f, 0.5f));
            selection.PushTransformToSelectedFigures();
            //рисуем
            Draw("c:\\4.png", fig1, fig2, selection);
 
            //сдвиг
            selection.Translate(100, 100);
            selection.PushTransformToSelectedFigures();
            //рисуем
            Draw("c:\\5.png", fig1, fig2, selection);
 
            //skew по X относительно нижней границы
            selection.Skew(0.5f, 0, new PointF(0.5f, 1));
            selection.PushTransformToSelectedFigures();
            //рисуем
            Draw("c:\\6.png", fig1, fig2, selection);
 
            //результат
            selection.Clear();
            Draw("c:\\7.png", fig1, fig2, selection);
        }
0
 Аватар для ashsvis
923 / 503 / 202
Регистрация: 08.10.2018
Сообщений: 1,553
Записей в блоге: 11
19.11.2018, 14:06  [ТС]
Storm23,
наконец, моё любопытство по поводу AllowOperations удовлетворено.
Внёс всё в рабочий репозиторий.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
19.11.2018, 14:06
Помогаю со студенческими работами здесь

Векторный редактор карт
Здравствуйте, я создаю векторный редактор карт, пишу на шарпе в студии,с помощью GDI+ не понимаю как можно создать некоторые...

Как создать векторный редактор?
Добрый день! Какие элементы формы нужно использовать чтобы создать векторное изображение? Можно ли использовать для этого PictureBox?...

Графический редактор
Всем привет! Надо написать графический редактор на C# ( курсовая работа ) Минимальный набор функций - 1. Создание 3d объектов ...

Графический редактор
Помогите пожалуйста.Графический редактор. Напишите программу - редактор графики, аналог Paint. Добавьте инструменты &quot;Кисть&quot;,...

Графический редактор на С#
Можете выложить пример графического редактора типапаинт на си шарп. Р.S Не выкладывайте исходники паинт нета!


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
40
Ответ Создать тему
Новые блоги и статьи
Модель здравосохранения 17. Планы на выгорание
anaschu 23.05.2026
Вот конкретная схема реализации: В классе Работник добавить: накопленнаяУсталость — растёт каждый час работы, снижается в перерывы и болезни коэффициентПрезентеизма — снижает продуктивность. . .
Изменение цветов в палитре gif файла aka фавикона
russiannick 23.05.2026
Изменение цветов в палитре gif файла, юзаемого как фавиконка в составе html-файла, помещенная в base64, средствами нативного Java Script, навеянное сном в майский день. Для работы необходим браузер,. . .
Модель здравосохранения 16. Слишком хорошие и здоровые сотрудники уходят, недовольные зарплатой
anaschu 23.05.2026
Отладка увольнений и настройка производительности Сегодня во второй половине дня разобрались с механикой увольнений и настроили коэффициент сложности заданий. Вот что было сделано. . . .
Как я стал коммунистом))) Модель сохранения здоровья сотрудников, запись блога номер 15
anaschu 23.05.2026
Внезапно хорошее здоровье сотрудников не нужно капиталистам?))
Модель здравоСохранения 15. Как мы чинили AnyLogic модель рабочего коллектива: сочленение диаграммы состояний болезней и поломок в ресурспул
anaschu 23.05.2026
Как мы чинили AnyLogic модель рабочего коллектива Сегодня разобрались с пятью багами, из-за которых модель либо падала с ошибкой, либо давала совершенно бессмысленные результаты. Каждый баг был. . .
Диалоги с ИИ
zorxor 23.05.2026
Насколько я понимаю - Вы - Искусственный Интеллект. Это так? Да, всё верно. Я — искусственный интеллект. Я представляю собой большую языковую модель, созданную для помощи в самых разных задачах. . . .
Модель здравосохранения 14. Собираем всю модель вместе.
anaschu 22.05.2026
Модель собрана. В будущих постах на видео я покажу, как она работает. В этом посте запускаем её, проверяем результаты и разбираем что можно с ней делать дальше. Перед запуском проверяем. . .
Модель здравоохранения 13. Добавление самой системы здравоохранения.
anaschu 22.05.2026
В предыдущем посте мы настроили болезни. Теперь добавим события, которые управляют здоровьем всего коллектива, а также настроим рабочий график и расчёт финансов. В Main создаём четыре события. . . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru