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

Шаг второй. Базовый класс и его спутники.

Запись от ashsvis размещена 06.11.2018 в 19:31
Обновил(-а) ashsvis 07.11.2018 в 13:02 (Исправлены ошибки в методе рисования)

Наверняка, многие из вас, други мои юные программисты, читая про создание векторного редактора с нуля,
ждут, ну когда же будем наконец что-то рисовать... Терпение, други мои... Всё по порядку и я продолжаю.

Выбор функционала для базового класса, на котором будут основаны все другие фигуры, уже придуманные и те,
которые ещё будут придуманы, очень важен, поэтому сначала постараюсь изложить, что именно я хочу получить
в краткосрочной перспективе от этого редактора:
  1. Базовые возможности, как-то: создание, перемещение и изменение размеров простых геометрических фигур.
  2. Простые трансформации фигур, как-то: повороты на 90°, отражения по горизонтали и вертикали.
  3. Изменение свойств линии контура и свойств заполнения замкнутых фигур.
  4. Было бы здорово делать объединения или вычитания фигур, чтобы получались более сложные фигуры.
Ну, пока хватит.
Первые три пункта ничего особенного не представляют, а вот четвёртый пункт интересный!
Для операций с фигурами необходим в конечном счёте внутренний массив точек (или узлов), по которым строится фигура.
Для треугольника необходимо три точки, для прямоугольника - четыре, ну и так далее.

Так вот, задумка в том, чтобы не городить кучу наследников базового класса, типа class Triangle {}, class Rectangle {}.
И сделать пока базовый класс, внутри которого хранится массив точек, ну а количество узлов можно задавать сначала
базовое количество, а потом добавлять по необходимости...

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

С лирикой пока всё, приступим к практической части:
Создадим абстрактный класс Drawing, который имеет внутренний массив точек и вот два его конструктора:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// контейнер для хранения точек фигуры
protected readonly List<PointF> Points = new List<PointF>();
 
/// <summary>
///  Конструктор без параметра с настройкой по умолчанию
/// </summary>
protected Drawing(): this(new Stroke(), new Fill()) { }
 
/// <summary>
/// Конструктор с параметрами
/// </summary>
/// <param name="stroke">Объект настроек карандаша</param>
/// <param name="fill">Объект настроек кисти</param>
protected Drawing(Stroke stroke, Fill fill)
{
    Stroke = stroke;
    Fill = fill;
}
Почему абстрактный класс? Только для того, чтобы его не создавали для работы, а пользовались его
интерфейсом и базовыми методами. Классы-наследники будут проще и нагляднее.

Тип точек массива выбран PointF с той перспективой, чтобы делать масштабирование векторных рисунков,
как это сейчас модно, крутнул колёсико мышки на себя и всё стало большое и красивое, крутнул от себя,
и весь рисунок уместился на экране... Ну, это в перспективе, как вы понимаете.

Вернёмся к конструкторам. Конструктор параметрами принимает два параметра - объекта, задающих
свойства карандаша и кисти для нашего объекта. Мы пока рассматривать их подробно не будем, ну а кому
очень невтерпёж, то прошу под спойлер:
Кликните здесь для просмотра всего текста

Класс для хранения свойств контура фигуры:
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
 
namespace SimpleVectorGraphicsEditor
{
    /// <summary>
    /// Описание класса хранения свойств для рисования контура фигуры
    /// </summary>
    [Serializable]
    public class Stroke: ICloneable
    {
        /// <summary>
        /// Конструктор без параметров, со свойствами по умолчанию
        /// </summary>
        public Stroke() : this(Color.Black, 1.0F) { }
        
        /// <summary>
        /// Конструктор с параметрами
        /// </summary>
        /// <param name="color">Цвет линии</param>
        /// <param name="width">Ширина линии</param>
        public Stroke(Color color, Single width)
        {
            Color = color;
            Width = width;
            DashStyle = DashStyle.Solid;
            Alpha = 255;
        }
        
        /// <summary>
        /// Цвет линии фигуры
        /// </summary>
        public Color Color { get; set; }
        
        /// <summary>
        /// Ширина линии фигуры
        /// </summary>
        public Single Width { get; set; }
        
        /// <summary>
        /// Стиль линии фигуры
        /// </summary>
        public DashStyle DashStyle { get; set; }
        
        /// <summary>
        /// Яркость:
        /// 0 - полностью прозрачный,
        /// 255 - полноцветный
        /// </summary>
        public int Alpha { get; set; }
        
        /// <summary>
        /// Стиль начала линии
        /// </summary>
        public LineCap StartCap { get; set; }
        
        /// <summary>
        /// Стиль конца линии
        /// </summary>
        public LineCap EndCap { get; set; }
        
        /// <summary>
        /// Стиль соединения двух отрезков линии
        /// </summary>
        public LineJoin LineJoin { get; set; }
        
        /// <summary>
        /// Метод присваивания свойств другого stroke
        /// </summary>
        /// <param name="stroke">Источник значений свойств stroke</param>
        public void Assign(Stroke stroke)
        {
            if (stroke == null) return;
            Color = stroke.Color;
            Width = stroke.Width;
            DashStyle = stroke.DashStyle;
            Alpha = stroke.Alpha;
            LineJoin = stroke.LineJoin;
            StartCap = stroke.StartCap;
            EndCap = stroke.EndCap;
        }
 
        /// <summary>
        /// Метод возвращает "карандаш", настроенный по текущим свойствам stroke
        /// </summary>
        public Pen Pen()
        {
            var pen = new Pen(Color.FromArgb(Alpha, Color))
                {
                    Width = Width,
                    LineJoin = LineJoin,
                    StartCap = StartCap,
                    EndCap = EndCap
                };
            if (DashStyle == DashStyle.Custom)
            {
                pen.DashStyle = DashStyle.Solid;
                pen.Color = Color.Transparent;
            }
            else
                pen.DashStyle = DashStyle;
            return (pen);
        }
 
        /// <summary>
        /// Реализация интерфейса ICloneable
        /// </summary>
        /// <returns>Возвращаем копию Stroke</returns>
        public object Clone()
        {
            var stroke = new Stroke();
            stroke.Assign(this);
            return stroke;
        }
    }
}
Класс для хранения свойств заливки поверхности замкнутой фигуры:
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
 
namespace SimpleVectorGraphicsEditor
{
    /// <summary>
    /// Перечисление для типа заполнения
    /// </summary>
    public enum FillMode
    {
        None = 0,           // без заливки
        Solid = 1,          // сплошная заливка
        Hatch = 2,          // битовая маска из набора
        LinearGradient = 3  // линейный градиент
    };
 
    [Serializable]
    public class Fill : ICloneable
    {
        // индекс шаблона заливки по умолчанию
        private int _patternIndex = 1;
 
        /// <summary>
        /// Конструктор без параметров, с цветом по умолчанию
        /// </summary>
        public Fill() : this(Color.White) { }
 
        /// <summary>
        /// Конструктор с параметром
        /// </summary>
        /// <param name="color">Цвет заливки фона</param>
        public Fill(Color color)
        {
            Color = color;
            Alpha = 255;
            PatternColor = Color.Black;
            PatternIndex = 1; // FillMode.Solid;
            Mode = FillMode.Solid;
            HatchMode = HatchStyle.Percent50;
            LinearMode = LinearGradientMode.Horizontal;
        }
 
        /// <summary>
        /// Цвет заливки
        /// </summary>
        public Color Color { get; set; }
 
        /// <summary>
        /// Яркость:
        /// 0 - полностью прозрачный,
        /// 255 - полноцветный
        /// </summary>
        public int Alpha { get; set; }
 
        /// <summary>
        /// Цвет линий при заливке выбранной битовой маской
        /// </summary>
        public Color PatternColor { get; set; }
 
        /// <summary>
        /// Режим заполнения поверхности фигуры
        /// </summary>
        public FillMode Mode { get; set; }
 
        /// <summary>
        /// Стиль заполнения битовой маской
        /// </summary>
        public HatchStyle HatchMode { get; set; }
 
        /// <summary>
        /// Стиль заполнения градиентной кистью
        /// </summary>
        public LinearGradientMode LinearMode { get; set; }
 
        /// <summary>
        /// Свойство для настройки fill по индексу шаблона,
        /// применяемого для заливки.
        /// Имена всех шаблонов возвращает метод string[] GetAllPatternNames()
        /// </summary>
        public int PatternIndex
        {
            get
            {
                return (_patternIndex);
            }
            set
            {
                _patternIndex = value;
                Mode = FillModeFromIndex(_patternIndex);
                HatchMode = HatchStyleFromIndex(_patternIndex);
                LinearMode = LinearGradientModeFromIndex(_patternIndex);
            }
        }
 
        /// <summary>
        /// Метод для вычисления стиля градиентной заливки из индекса шаблона
        /// </summary>
        /// <param name="index">применяемый индекс шаблона заливки</param>
        /// <returns>Возвращает стиль градиентной заливки</returns>
        public static LinearGradientMode LinearGradientModeFromIndex(int index)
        {
            checked
            {
                return (LinearGradientMode)(index - 2);
            }
        }
 
        /// <summary>
        /// Метод для вычисления стиля битовой заливки
        /// </summary>
        /// <param name="index">применяемый индекс шаблона заливки</param>
        /// <returns></returns>
        public static HatchStyle HatchStyleFromIndex(int index)
        {
            checked
            {
                return (HatchStyle)(index - 2 - LinearGradientModeCount);
            }
        }
 
        /// <summary>
        /// Метод для определения того факта, что заливка не применяется
        /// </summary>
        /// <param name="index">применяемый индекс шаблона заливки</param>
        /// <returns>True - заливки нет</returns>
        public static bool IsNonePatternIndex(int index)
        {
            return (index == 0);
        }
 
        /// <summary>
        /// Метод для определения того факта, что применяется сплошная заливка
        /// </summary>
        /// <param name="index">применяемый индекс шаблона заливки</param>
        /// <returns>True - сплошная заливка</returns>
        public static bool IsSolidPatternIndex(int index)
        {
            return (index == 1);
        }
 
        /// <summary>
        /// Метод для определения того факта, что применяется градиентная заливка
        /// </summary>
        /// <param name="index">применяемый индекс шаблона заливки</param>
        /// <returns>True - градиентная заливка</returns>
        public static bool IsLinearGradientPatternIndex(int index)
        {
            checked
            {
                var idx = index - 2;
                return ((idx >= 0) && (idx < LinearGradientModeCount));
            }
        }
 
        /// <summary>
        /// Метод для определения того факта, что применяется битовая заливка
        /// </summary>
        /// <param name="index">применяемый индекс шаблона заливки</param>
        /// <returns>True - заливка по битовой маске</returns>
        public static bool IsHatchPatternIndex(int index)
        {
            checked
            {
                var idx = index - 2 - LinearGradientModeCount;
                return ((idx >= 0) && (idx < HatchStyleCount));
            }
        }
 
        // Вспомогательный массив со стилями направлений градиентной заливки
        static readonly LinearGradientMode[] LinearGradientModeArray =
            (LinearGradientMode[])Enum.GetValues(typeof(LinearGradientMode));
 
        // Длина вспомогательного массива со стилями направлений градиентной заливки
        static readonly int LinearGradientModeCount = LinearGradientModeArray.Length;
 
        // Вспомогательный массив со стилями битовой заливки по маске
        static readonly HatchStyle[] HatchStyleArray = (HatchStyle[])Enum.GetValues(typeof(HatchStyle));
 
        // Длина вспомогательного массива со стилями битовой заливки по маске
        static readonly int HatchStyleCount = HatchStyleArray.Length - 3;
 
        /// <summary>
        /// Метод определения типа заливки по индексу шаблона заливки
        /// </summary>
        /// <param name="index">применяемый индекс шаблона заливки</param>
        /// <returns>тип заполнения</returns>
        public static FillMode FillModeFromIndex(int index)
        {
            var fillmode = FillMode.None;
            if (IsNonePatternIndex(index)) fillmode = FillMode.None;
            else if (IsSolidPatternIndex(index)) fillmode = FillMode.Solid;
            else if (IsLinearGradientPatternIndex(index)) fillmode = FillMode.LinearGradient;
            else if (IsHatchPatternIndex(index)) fillmode = FillMode.Hatch;
            return (fillmode);
        }
 
        /// <summary>
        /// Метод возвращает "кисть", настроенный по текущим свойствам fill
        /// </summary>
        /// <param name="rect">прямоугольная область для заливки градиентом</param>
        /// <returns>Настроенная кисть для заполнения контура фигуры</returns>
        public Brush Brush(RectangleF rect)
        {
            Brush brush = null;
            switch (Mode)
            {
                case FillMode.None:
                    // нет заливки, берём прозрачный цвет
                    brush = new SolidBrush(Color.Transparent);
                    break;
                case FillMode.Solid:
                    // сплошная заливка, берём цвет и канал яркости
                    brush = new SolidBrush(Color.FromArgb(Alpha, Color));
                    break;
                case FillMode.Hatch:
                    brush = new HatchBrush(HatchMode,
                                           Color.FromArgb(Alpha, PatternColor),
                                           Color.FromArgb(Alpha, Color));
                    break;
                case FillMode.LinearGradient:
                    brush = new LinearGradientBrush(rect, Color.FromArgb(Alpha, PatternColor),
                                                    Color.FromArgb(Alpha, Color), LinearMode);
                    break;
            }
            return (brush);
        }
 
        /// <summary>
        /// Метод, возвращающий имена всех доступных шаблонов для заливки контура фигуры
        /// </summary>
        /// <returns>Массив имён шаблонов заливки</returns>
        public static string[] GetAllPatternNames()
        {
            var hatchNameArray = Enum.GetNames(typeof(HatchStyle));
            var linearGradientNameArray = Enum.GetNames(typeof(LinearGradientMode));
            var n = 2 + LinearGradientModeCount + HatchStyleCount;
            var names = new string[n];
            names[0] = "Прозрачный";
            names[1] = "Сплошной";
            var i = 2;
            for (var j = 0; j < LinearGradientModeCount; j++)
                names[i++] = linearGradientNameArray[j];
            for (var j = 0; j < HatchStyleCount; j++)
                names[i++] = hatchNameArray[j];
            return (names);
        }
 
        /// <summary>
        /// Метод присваивания свойств другого fill
        /// </summary>
        /// <param name="fill">Источник значений свойств fill</param>
        public void Assign(Fill fill)
        {
            if (fill == null) return;
            Color = fill.Color;
            Alpha = fill.Alpha;
            PatternColor = fill.PatternColor;
            PatternIndex = fill.PatternIndex;
            Mode = fill.Mode;
            HatchMode = fill.HatchMode;
            LinearMode = fill.LinearMode;
        }
 
        /// <summary>
        /// Реализация интерфейса ICloneable
        /// </summary>
        /// <returns>Возвращаем копию Fill</returns>
        public object Clone()
        {
            var fill = new Fill();
            fill.Assign(this);
            return fill;
        }
    }
}

Теперь о свойствах.
C#
1
2
3
4
/// <summary>
/// Свойство определяющее вид фигуры
/// </summary>
public DrawingKind Kind { get; set; }
Свойство Kind имеет тип-перечисление и это перечисление выглядит вот так:
C#
1
2
3
4
5
public enum DrawingKind
{
    Polygon,
    Polyline
}
Этим я говорю, что моя фигура - это замкнутый контур или некая ломаная.

Свойства для поддержки размещения и размеров фигуры:
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
/// <summary>
/// Базовая точка, обычно левый верхний угол прямоугольника,
/// описывающего фигуру
/// </summary>
public virtual PointF Location { get; set; }
 
/// <summary>
/// Размер фигуры, ширина и высота прямоугольника,
/// описывающего фигуру
/// </summary>
public virtual SizeF Size { get; set; }
 
/// <summary>
/// Прямоугольник, описывающий фигуру
/// </summary>
public RectangleF BoundsRect
{
    get
    {
        return new RectangleF(Location, Size);
    }
}
 
/// <summary>
/// Свойство возвращает реальный прямоугольник, охватывающий точки фигуры
/// </summary>
public virtual RectangleF GetBounds
{
    get
    {
        using (var gp = new GraphicsPath())
        {
            gp.AddPolygon(GetPoints());
            var rect = gp.GetBounds();
            // если фигура очень узкая по горизонтали
            if (Math.Abs(rect.Width - 0) < float.Epsilon)
            {
                rect.X -= 2; 
                rect.Width += 4;
            }
            // если фигура очень узкая по вертикали
            if (Math.Abs(rect.Height - 0) < float.Epsilon)
            {
                rect.Y -= 2;
                rect.Height += 4;
            }
            return (rect);
        }
    }
}
Свойства, предназначенные для поддержки визуальной составляющей:
C#
1
2
3
4
5
6
7
8
9
/// <summary>
/// Карандаш для рисования контура фигуры
/// </summary>
public Stroke Stroke { get; set; }
 
/// <summary>
/// Кисть для заливки контура фигуры
/// </summary>
public Fill Fill { get; set; }
Теперь методы.
Вот основной метод, который всё рисует (наконец-то!):
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// <summary>
/// Метод рисования фигуры по точкам базового списка
/// </summary>
/// <param name="graphics">"холст" - объект для рисования</param>
public virtual void DrawFigure(Graphics graphics)
{
    var points = Points.ToArray();
    // если можно заливать
    if (Kind == DrawingKind.Polygon && Fill != null)
    {
        using (var brush = Fill.Brush(GetBounds))
            graphics.FillPolygon(brush, points); // заливаем фон кистью
    }
    // рисуем контур карандашом
    using (var pen = Stroke.Pen() ?? new Pen(Color.Black))
        graphics.DrawPolygon(pen, points);
}
Когда мы рисуем фигуру, то сначала делаем заливку, а потом "обводим".
Заливка возможна, если у нас замкнутый контур и если объект Fill свойств заливки задан.

Метод Fill.Brush(GetBounds) возвращает настроенную кисть, в том числе и градиентную,
для которой требуется указать размеры поверхности для работы в виде прямоугольника,
который возвращает свойство GetBounds.

Метод Stroke.Pen() возвращает настроенный карандаш, а если нет, то берётся чёрный.

И два вспомогательных метода.
Метод, проверяющий принадлежность точки фигуре:
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
/// <summary>
/// Метод проверяет принадлежность точки фигуре
/// </summary>
/// <param name="point">проверяемая точка</param>
/// <returns>True - точка принадлежит фигуре</returns>
public virtual Boolean PointInFigure(PointF point)
{
    switch (Kind)
    {
        case DrawingKind.Polygon:
            using (var gp = new GraphicsPath())
            {
                gp.AddPolygon(GetPoints());
                return (gp.IsVisible(point));
            }
        case DrawingKind.Polyline:
            using (var gp = new GraphicsPath())
            {
                var ps = GetPoints();
                for (var i = 1; i < ps.Length; i++)
                {
                    gp.StartFigure();
                    gp.AddPolygon(Onebuff(ps[i - 1], ps[i]));
                    gp.CloseFigure();
                }
                return (gp.IsVisible(point));
            }
    }
    return false;
}
Метод, возвращающий полигон из четырёх точек вокруг отрезка прямой,
для работы с линиями, у которых вырождаются либо ширина, либо высота.
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
/// <summary>
/// Вспомогательный метод, строит прямоугольник на перпендикулярах
/// на концах отрезка между двух точек
/// </summary>
/// <param name="p1">первая точка</param>
/// <param name="p2">вторая точка</param>
/// <returns>массив точек четырёхугольника, охватывающего отрезок</returns>
private PointF[] Onebuff(PointF p1, PointF p2)
{
    var selWidth = Stroke.Width * 0.5F;
    if ((selWidth < 4F) || (Stroke.DashStyle == DashStyle.Custom)) selWidth = 4F;
    var x1 = p1.X;
    var y1 = p1.Y;
    var x2 = p2.X;
    var y2 = p2.Y;
    var k = (y2 - y1) / (x2 - x1);
    var fi1 = (Single)(Math.Atan(k) + Math.PI * 0.5);
    var fi2 = (Single)(Math.Atan(k) - Math.PI * 0.5);
    var buff = new PointF[4];
    buff[0].X = (Single)Math.Round(selWidth * Math.Cos(fi1) + x1);
    buff[0].Y = (Single)Math.Round(selWidth * Math.Sin(fi1) + y1);
    buff[1].X = (Single)Math.Round(selWidth * Math.Cos(fi2) + x1);
    buff[1].Y = (Single)Math.Round(selWidth * Math.Sin(fi2) + y1);
    buff[2].X = (Single)Math.Round(selWidth * Math.Cos(fi2) + x2);
    buff[2].Y = (Single)Math.Round(selWidth * Math.Sin(fi2) + y2);
    buff[3].X = (Single)Math.Round(selWidth * Math.Cos(fi1) + x2);
    buff[3].Y = (Single)Math.Round(selWidth * Math.Sin(fi1) + y2);
    return (buff);
}
Ну, хватит пока. В следующем шаге разберём подробно устройство
классов-спутников Stroke и Fill.

Вот весь код класса Drawing:
Кликните здесь для просмотра всего текста

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
 
namespace SimpleVectorGraphicsEditor
{
    public enum DrawingKind
    {
        Polygon,
        Polyline
    }
 
    /// <summary>
    /// Описание класса базовой фигуры 
    /// </summary>
    [Serializable]
    public abstract class Drawing
    {
        // контейнер для хранения точек фигуры
        protected readonly List<PointF> Points = new List<PointF>();
 
        /// <summary>
        ///  Конструктор без параметра с настройкой по умолчанию
        /// </summary>
        protected Drawing(): this(new Stroke(), new Fill()) { }
 
        /// <summary>
        /// Конструктор с параметрами
        /// </summary>
        /// <param name="stroke">Объект настроек карандаша</param>
        /// <param name="fill">Объект настроек кисти</param>
        protected Drawing(Stroke stroke, Fill fill)
        {
            Stroke = stroke;
            Fill = fill;
        }
 
        /// <summary>
        /// Свойство определяющее вид фигуры
        /// </summary>
        public DrawingKind Kind { get; set; }
 
        /// <summary>
        /// Базовая точка, обычно левый верхний угол прямоугольника,
        /// описывающего фигуру
        /// </summary>
        public virtual PointF Location { get; set; }
 
        /// <summary>
        /// Размер фигуры, ширина и высота прямоугольника,
        /// описывающего фигуру
        /// </summary>
        public virtual SizeF Size { get; set; }
 
        /// <summary>
        /// Прямоугольник, описывающий фигуру
        /// </summary>
        public RectangleF BoundsRect
        {
            get
            {
                return new RectangleF(Location, Size);
            }
        }
 
        /// <summary>
        /// Карандаш для рисования контура фигуры
        /// </summary>
        public Stroke Stroke { get; set; }
 
        /// <summary>
        /// Кисть для заливки контура фигуры
        /// </summary>
        public Fill Fill { get; set; }
 
        /// <summary>
        /// Метод рисования фигуры по точкам базового списка
        /// </summary>
        /// <param name="graphics">"холст" - объект для рисования</param>
        public virtual void DrawFigure(Graphics graphics)
        {
            var points = Points.ToArray();
            // если можно заливать
            if (Kind == DrawingKind.Polygon && Fill != null)
            {
                using (var brush = Fill.Brush(GetBounds))
                    graphics.FillPolygon(brush, points); // заливаем фон кистью
            }
            // рисуем контур карандашом
            using (var pen = Stroke.Pen() ?? new Pen(Color.Black))
                graphics.DrawPolygon(pen, points);
        }
 
        /// <summary>
        /// Метод проверяет принадлежность точки фигуре
        /// </summary>
        /// <param name="point">проверяемая точка</param>
        /// <returns>True - точка принадлежит фигуре</returns>
        public virtual Boolean PointInFigure(PointF point)
        {
            switch (Kind)
            {
                case DrawingKind.Polygon:
                    using (var gp = new GraphicsPath())
                    {
                        gp.AddPolygon(GetPoints());
                        return (gp.IsVisible(point));
                    }
                case DrawingKind.Polyline:
                    using (var gp = new GraphicsPath())
                    {
                        var ps = GetPoints();
                        for (var i = 1; i < ps.Length; i++)
                        {
                            gp.StartFigure();
                            gp.AddPolygon(Onebuff(ps[i - 1], ps[i]));
                            gp.CloseFigure();
                        }
                        return (gp.IsVisible(point));
                    }
            }
            return false;
        }
 
        /// <summary>
        /// Вспомогательный метод, строит прямоугольник на перпендикулярах
        /// на концах отрезка между двух точек
        /// </summary>
        /// <param name="p1">первая точка</param>
        /// <param name="p2">вторая точка</param>
        /// <returns>массив точек четырёхугольника, охватывающего отрезок</returns>
        private PointF[] Onebuff(PointF p1, PointF p2)
        {
            var selWidth = Stroke.Width * 0.5F;
            if ((selWidth < 4F) || (Stroke.DashStyle == DashStyle.Custom)) selWidth = 4F;
            var x1 = p1.X;
            var y1 = p1.Y;
            var x2 = p2.X;
            var y2 = p2.Y;
            var k = (y2 - y1) / (x2 - x1);
            var fi1 = (Single)(Math.Atan(k) + Math.PI * 0.5);
            var fi2 = (Single)(Math.Atan(k) - Math.PI * 0.5);
            var buff = new PointF[4];
            buff[0].X = (Single)Math.Round(selWidth * Math.Cos(fi1) + x1);
            buff[0].Y = (Single)Math.Round(selWidth * Math.Sin(fi1) + y1);
            buff[1].X = (Single)Math.Round(selWidth * Math.Cos(fi2) + x1);
            buff[1].Y = (Single)Math.Round(selWidth * Math.Sin(fi2) + y1);
            buff[2].X = (Single)Math.Round(selWidth * Math.Cos(fi2) + x2);
            buff[2].Y = (Single)Math.Round(selWidth * Math.Sin(fi2) + y2);
            buff[3].X = (Single)Math.Round(selWidth * Math.Cos(fi1) + x2);
            buff[3].Y = (Single)Math.Round(selWidth * Math.Sin(fi1) + y2);
            return (buff);
        }
 
        /// <summary>
        /// Свойство возвращает реальный прямоугольник, охватывающий точки фигуры
        /// </summary>
        public virtual RectangleF GetBounds
        {
            get
            {
                using (var gp = new GraphicsPath())
                {
                    gp.AddPolygon(GetPoints());
                    var rect = gp.GetBounds();
                    // если фигура очень узкая по горизонтали
                    if (Math.Abs(rect.Width - 0) < float.Epsilon)
                    {
                        rect.X -= 2; 
                        rect.Width += 4;
                    }
                    // если фигура очень узкая по вертикали
                    if (Math.Abs(rect.Height - 0) < float.Epsilon)
                    {
                        rect.Y -= 2;
                        rect.Height += 4;
                    }
                    return (rect);
                }
            }
        }
 
        /// <summary>
        /// Метод возвращает массив точек фигуры
        /// </summary>
        /// <returns>Массив, копия списка точек фигуры</returns>
        public virtual PointF[] GetPoints()
        {   
            // возвращает массив точек линии
            var ps = new PointF[Points.Count];
            Points.CopyTo(ps);
            return (ps);
        }
 
    }
}

До скорого!
Всего комментариев 2
Комментарии
  1. Старый комментарий
    Аватар для Storm23
    Строки 86, 88 - утечка памяти.
    Запись от Storm23 размещена 07.11.2018 в 11:28 Storm23 вне форума
  2. Старый комментарий
    Аватар для ashsvis
    Спасибо! Исправил.
    Запись от ashsvis размещена 07.11.2018 в 12:01 ashsvis на форуме
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2019, vBulletin Solutions, Inc.
Рейтинг@Mail.ru