Постановка задачи. Требуется рисовать объекты 2d-графики, используя плавные кривые.
Обсуждение. В примере с корабликом мы уже рисовали окружности и сектора, хотя очевидно они не исчерпывают все необходимые нам графические объекты. В этом примере мы продемонстрируем другие варианты решения этой задачи. Ключевыми понятиями будут сплайн и кривая Безье.
Подготовительные действия:
На форме размером 900х400 поместим надпись (label1) «Упругость сплайна» и окно для ввода textBox1. Создадим меню с кнопками : «Выбор кривой» и «Очистка экрана». Задайте семь типов кривой (подменю «Выбор кривой»):
1) Незамкнутая ломаная,
2) Замкнутая ломаная,
3) Незамкнутый сплайн,
4) Замкнутый сплайн,
5) Одна кривая Безье, 6) Незамкнутая кривая Безье,
7) Замкнутая кривая Безье.
Начнем постепенно с ними разбираться. Нам понадобятся объекты классов Graphics, SolidBrush, Pen, Point и Random. Каждой позиции меню зададим соответствующие обработчики событий, например, для незамкнутой ломаной будет задан метод незамкнутаяЛоманаяToolStripMenuItem_Click(),
а для очистки экрана — метод
очисткаЭкранаToolStripMenuItem_Click().
Добавим два метода класса Form1:
private Point[] star5(int x, int y, int r),
возвращающий массив 10 точек «Звезды», и
public Color RandomColor()
(смотри пример «Парапланы»).
Будем использовать свойства холста g: SmoothingMode и FillMode, добавленные в библиотеке System.Drawing.Drawing2D; а также методы класса Graphics: FromHwnd(), Clear(), DrawLine(), DrawLines(), DrawCurve(), DrawCloseCurve(), DrawBezier(), DrawBeziers(), Fillellipse().
Немного теории
Линии, также как прямоугольники и эллипсы, являются графическими примитивами.
Для того, чтобы нарисовать отрезок прямой, надо указать координаты начала (Point start) и конца (Point end) отрезка, выбрать перо (pen) для рисования и применить метод объекта g.DrawLine(pen, start, end).
Для рисования незамкнутой ломаной, состоящей из отрезков, нужно задать массив последовательных точек Point[] pm и применить метод g.DrawLines(pen, pm). Если в качестве последней точки массива указать координаты первой точки, то контур станет замкнутым.
Для рисования кривых следует использовать методы DrawCurve() или DrawCloseCurve() для рисования сплайном, или же DrawBezier() и DrawBeziers() для рисования кривых Безье.
Справка
Сплайн (основной или фундаментальный сплайн, аналог лекала в черчении) — это последовательность отдельных кривых, объединенных в одну большую кривую. Сплайн задается массивом точек и параметром упругости. Сплайн обязательно проходит через заданные массивом pm точки на плоскости, а параметр упругости (elasticity — вещественное число типа float) определяет плавность их соединения. Если упругость = 0.0f (бесконечная физическая упругость), то соединение точек будет выполнено прямыми отрезками. Если упругость = 1.0f (отсутствие физической упругости), то кривая имеет наименьший суммарный изгиб. Если упругость > 1.0f, то кривая напоминает сдавленный берегами ручей, стремящийся увеличить изгиб своих излучин и течь по более длинному пути. Лучше всего исследовать влияние упругости, меняя ее и рисуя сплайн заново. Для вызова метода достаточно написать
g.DrawCurve(pen, pm, elasticity, FillMode.Alternate);
— незамкнутый сплайн или
g.DrawClosedCurve(pen, pm, elasticity, FillMode.Alternate);
— замкнутый сплайн.
Отличие второго метода от первого в том, что последняя точка массива добавляется от первой (eё можно не добавлять к массиву).
Кривой Безье называется кривая, задаваемая полиномом 3 порядка: y=ax3+bx2+cx+d. То есть для ее определения нужно задать четыре параметра (a,b,c,d).
Кривые Безье используются очень часто в 2d- и 3d-графике. Они позволяют задавать любой тип соединений («сшивания») кривых, в этом они являются более универсальными, чем рассмотренные выше сплайны (для которых кроме заданных точек дополнительно вводится только один параметр — упругость).
Для полного определения кривой Безье, проходящей через начальную (start) и конечную (finish) точки необходимо задать еще две управляющие точки (control1 и control2), задающих касательные к этим точкам и радиусы кривизны. Отдаляя управляющую точку control1 от точки start, мы увеличиваем радиус кривизны, смещая ее относительно точки start, мы изменяем направление касательной к кривой в точке start. Все это справедливо и для пары finish-control2.
Эти четыре точки полностью определяют коэффициенты (a,b,c,d) кривой Безье. Полезно самостоятельно исследовать, как меняется форма кривой Безье при перемещении управляющих точек. Теперь это можно сделать в программе, описанной в статье «Кривая Безье и метод де Кастельжо«.
Остальное понятно из комментариев в тексте программы (файл Form1.cs):
using System; using System.Drawing; using System.Drawing.Drawing2D; // добавлено using System.Windows.Forms; namespace сплайны_и_кривые_Безье { public partial class Form1 : Form { Graphics g; public Form1() { InitializeComponent(); g = Graphics.FromHwnd(this.Handle); // холст // сглаживание: enum SmoothingMode g.SmoothingMode = SmoothingMode.AntiAlias; } private void очисткаЭкранаToolStripMenuItem_Click(object sender, EventArgs e) { g.Clear(BackColor); } private Point[] star5(int x, int y, int r) // звезда { Point point1 = new Point(x, y-6*r); Point point2 = new Point(x+2*r, y-3*r); Point point3 = new Point(x+6*r, y-2*r); Point point4 = new Point(x+3*r, y+r); Point point5 = new Point(x+4*r, y+5*r); Point point6 = new Point(x, y+3*r); Point point7 = new Point(x - 4 * r, y + 5 * r); Point point8 = new Point(x - 3 * r, y + r); Point point9 = new Point(x - 6 * r, y - 2 * r); Point point10 = new Point(x - 2 * r, y - 3 * r); Point[] pm = { point1, point2, point3, point4, point5, point6, point7, point8, point9, point10 }; return pm; } private void незамкнутаяЛоманаяToolStripMenuItem_Click(object sender, EventArgs e) { Pen redPen = new Pen(Color.Red, 2f); // Задание 10 точек, определяющих незамкнутую ломаную Point[] pm = star5(200, 200, 25); // Рисование незамкнутой ломаной из отрезков (Line) g.DrawLines(redPen, pm); // для замыкания добавьте первую точку } private void замкнутаяЛоманаяToolStripMenuItem_Click(object sender, EventArgs e) { Pen greenPen = new Pen(Color.Green, 2f); // Задание 10 точек, определяющих замкнутую область "звезда" Point[] pm = star5(250, 220, 25); // Рисование замкнутой ломаной из отрезков g.DrawClosedCurve(greenPen, pm, 0f, FillMode.Winding); // Упругость: 0f - беск.физ.упругость - прямыми; } private void сплайнToolStripMenuItem_Click(object sender, EventArgs e) { Pen pen = new Pen(RandomColor(), 2f); // Задание точек, определяющих кривую Point[] pm = star5(500, 200, 25); float elasticity = Convert.ToSingle(textBox1.Text); // - упругость // Рисование замкнутой кривой через 10 точек сплайном // g.DrawClosedCurve(bluePen, pm, elasticity, FillMode.Alternate); // Упругость: 0f - беск.физ.упругость - прямыми; // 1f - отсутствие физ.упругости, наименьший суммарный изгиб // >1 - сдавленный берегами ручей, стремящийся увеличить изгиб своих излучин и течь по более длинному пути. g.DrawClosedCurve(pen, pm, elasticity, FillMode.Alternate); } private void незамкнутыйСплайнToolStripMenuItem_Click(object sender, EventArgs e) { Pen pen = new Pen(RandomColor(), 3f); // Задание точек, определяющих кривую Point[] pm = star5(400, 200, 25); float elasticity = Convert.ToSingle(textBox1.Text); // Рисование незамкнутой кривой через 10 точек сплайном g.DrawCurve(pen, pm, elasticity); } public Color RandomColor() { int r, g, b; byte[] bytes1 = new byte[3]; // массив 3 цветов Random rnd1 = new Random(); rnd1.NextBytes(bytes1); // генерация в массив r = Convert.ToInt16(bytes1[0]); g = Convert.ToInt16(bytes1[1]); b = Convert.ToInt16(bytes1[2]); return Color.FromArgb(r, g, b); // возврат цвета } private void однаКриваяБезьеToolStripMenuItem_Click(object sender, EventArgs e) { // кривая Безье: 4 точки (x,y) - старт, // управление1, управление2, end Point start = new Point(500, 100); Point control1 = new Point( 500, 300); Point control2 = new Point( 700, 300 ); Point finish = new Point( 800, 100); g.DrawBezier(new Pen(Color.Black, 3), start, control1, control2, finish); // это кривая Безье, дальше - для наглядности SolidBrush br = new SolidBrush(Color.Green); Pen pen = new Pen(Color.Green, 1); g.FillEllipse(br, start.X-5, start.Y-5, 11, 11); g.FillEllipse(br, control1.X - 5, control1.Y - 5, 11, 11); g.FillEllipse(br, control2.X - 5, control2.Y - 5, 11, 11); g.FillEllipse(br, finish.X - 5, finish.Y - 5, 11, 11); g.DrawLine(pen, start, control1); g.DrawLine(pen, finish, control2); } private void незамкнутаяКриваяБезьеToolStripMenuItem_Click(object sender, EventArgs e) { // Зададим точки для 2-х кривых Безье - всего 7. Point start = new Point(500, 100); Point control1 = new Point(750, 50); Point control2 = new Point(510, 310); Point end1 = new Point(500, 300); Point control3 = new Point(490, 310); Point control4 = new Point(240, 50); Point end2 = new Point(490, 100); Point[] bezierPoints = { start, control1, control2, end1, control3, control4, end2 }; // Рисуем g.DrawBeziers(new Pen(Color.Red, 3), bezierPoints); } private void замкнутаяКриваяБезьеToolStripMenuItem_Click(object sender, EventArgs e) { // Зададим точки для 2-х кривых Безье - всего 7. Point start = new Point(600, 150); Point control1 = new Point(850, 100); Point control2 = new Point(610, 360); Point end1 = new Point(600, 350); Point control3 = new Point(590, 360); Point control4 = new Point(350, 100); Point end2 = start; // первая и третья точки совпадают Point[] bezierPoints = { start, control1, control2, end1, control3, control4, end2 }; // Рисуем g.DrawBeziers(new Pen(Color.Orange, 4), bezierPoints); } } }
Внешний вид формы при выборе другой позиции меню после рисования двух фигур (Незамкнутый сплайн и Замкнутая кривая Безье) выглядит так:
Если после очистки экрана задать 1-4 пункты подменю «Выбор кривой», то мы увидим четыре сплайна типа «Звезда»:
Если после очистки экрана задать 5-7 пункты подменю «Выбор кривой», то мы увидим 3 кривых Безье, где красный (незамкнутый) и желтый (замкнутый) контуры состоят всего из двух кривых Безье.
Поэкспериментировав с параметрами контуров, задаваемых сплайнами и кривыми Безье, вы сможете глубже понять принципы векторной графики.
Перейдем к рассмотрению примера рисования и защиты произвольных областей на холсте.
NEW: Наш Чат, в котором вы можете обсудить любые вопросы, идеи, поделиться опытом или связаться с администраторами.
Добрый день! Вроде бы все было сделано согласно вашей статье, но к сожалению сплайн не рисуется.
Александр! Когда я прошу прислать код, то подразумеваю — что все файлы проекта (кроме exe-файлов, которые надо удалить, не всякая почта их пропускает). Возможно, Вы не связали события (кнопки, таймер) с методами их обработки.
Я перешел на VS 2019, запустил в нем для проверки оба проекта, все работает. Посылаю Вам оба проекта, разархивируйте и запустите, будет работать.
Если не сможете понять, в чем ошибались, можем разобрать через Skype (имя на первой странице).