netlib.narod.ru | < Назад | Оглавление | Далее > |
Начнем изучение графических контуров с задачи из программирования графики. Допустим, требуется нарисовать пером, толщина которого существенно больше 1 пиксела, фигуру, образованную полуокружностью и двумя прямыми, соединенными с ее концами. Вот что получилось в результате первой попытки нарисовать такую фигуру:
LineArcCombo.cs
//--------------------------------------------- // LineArcCombo.cs (C) 2001 by Charles Petzold //--------------------------------------------- using System; using System.Drawing; using System.Windows.Forms; class LineArcCombo: PrintableForm { public new static void Main() { Application.Run(new LineArcCombo()); } public LineArcCombo() { Text = "Line & Arc Combo"; } protected override void DoPage(Graphics grfx, Color clr, int cx, int cy) { Pen pen = new Pen(clr, 25); grfx.DrawLine(pen, 25, 100, 125, 100); grfx.DrawArc (pen, 125, 50, 100, 100, -180, 180); grfx.DrawLine(pen, 225, 100, 325, 100); } }
Программа рисует две линии длиной по 100 единиц (т.е. 100 пикселов в случае дисплея или 1 дюйм в случае принтера) и полуокружность, образующую дугу диаметром 100 единиц, толщина пера составляет 25 единиц. А вот как все это выглядит:
Такой результат, может, удовлетворит вас, но не меня. Я бы хотел, чтобы прямые и дуга соединялись. Конечно, эти линии соприкасаются, но они никак не выглядят соединенными, да и выступы на концах дуги здесь тоже ни к чему.
Если изменить программу LineArcCombo и заставить ее рисовать эту фигуру дважды (первый раз толстым пером серого цвета, затем — тонким черным толщиной в 1 пиксел), картина станет яснее:
Оказывается, линии толщиной 25 пикселов просто выдаются на 12 пикселов по сторонам от линий толщиной 1 пиксел. Поскольку и обе прямые, и дуга нарисованы при помощи отдельных вызовов соответствующих методов, каждый элемент фигуры — это отдельная сущность. В двух точках, где встречаются прямые и дуга, толстые линии соприкасаются, но не образуют единое целое.
Быть может, этой фигуре все же удастся придать должный вид, немного поколдовав с координатами. Например, можно опустить дугу на 12 пикселов или что-то в этом роде. Но это, честно говоря, всего лишь косметические меры, но не решение задачи.
Чтобы решить ее, нужно придумать, как намекнуть графической системе, что от нее требуется соединить прямые с дугой. Если бы мы имели дело только с прямыми, задача решалась бы элементарно: вместо рисования отдельных линий методом DrawLine можно было бы нарисовать составную линию при помощи DrawLines. Вот, к примеру, программа, рисующая нечто похожее на то, что нам нужно.
WidePolyline.cs
//--------------------------------------------- // WidePolyline.cs (C) 2001 by Charles Petzold //--------------------------------------------- using System; using System.Drawing; using System.Windows.Forms; class WidePolyline: PrintableForm { public new static void Main() { Application.Run(new WidePolyline()); } public WidePolyline() { Text = "Wide Polyline"; } protected override void DoPage(Graphics grfx, Color clr, int cx, int cy) { Pen pen = new Pen(clr, 25); grfx.DrawLines(pen, new Point[] { new Point( 25, 100), new Point(125, 100), new Point(125, 50), new Point(225, 50), new Point(225, 100), new Point(325, 100) }); } }
При вызове метода DrawLines создается массив из шести структур Point, определяющий ломаную линию из пяти отрезков:
Графическая система «знает», что пользователь хотел бы соединитьэти линии, поскольку все они заданы в одном вызове функции, поэтому в местах соединения отрезков толстая линия нарисована корректно.
Удачное применение составной линии в WidePolyline наводит на мысль о существовании другого решения нашей задачи рисования фигуры, содержащей дугу. Заглянув в главу 5, можно вспомнить, как рисуют эллипс при помощи составной линии, и нарисовать дугу именно так. Другой вариант — преобразовать прямые в кривые Безье (для этого нужно расположить управляющие точки кривых между конечными и на одной прямой с ними); преобразовать дугу в одну или несколько кривых Безье (при помощи формул из главы 13) и нарисовать всю фигуру целиком, вызвав метод DrawBeziers.
Но должен же быть менее замысловатый способ, позволяющий указать графической системе, что нужно соединить концы дуги с прямыми! Здесь требуется метод вроде DrawLines, который работал бы с комбинациями прямых и кривых. Раз уж мы взялись за это, можно пожелать, чтобы наша «волшебная функция» заодно работала с кривыми Безье и каноническими сплайнами.
Такой «волшебной функцией» (точнее, волшебным классом) является GraphicsPath. Вот программа LineArcPath, которая корректно рисует нашу фигуру. Она всего на три инструкции длиннее LineArcCombo.
LineArcPath.cs
//-------------------------------------------- // LineArcPath.cs (C) 2001 by Charles Petzold //-------------------------------------------- using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; class LineArcPath: PrintableForm { public new static void Main() { Application.Run(new LineArcPath()); } public LineArcPath() { Text = "Line & Arc in Path"; } protected override void DoPage(Graphics grfx, Color clr, int cx, int cy) { GraphicsPath path = new GraphicsPath(); Pen pen = new Pen(clr, 25); path.AddLine( 25, 100, 125, 100); path.AddArc (125, 50, 100, 100, -180, 180); path.AddLine(225, 100, 325, 100); grfx.DrawPath(pen, path); } }
Первая из трех дополнительных инструкций размещена в начале метода DoPage и создает контур:
GraphicsPath path = new GraphicsPath();
Хотя класс, в котором реализованы контуры, называется GraphicsPath, я назвал переменную, содержащую экземпляр этого класса, просто path. Класс GraphicsPath определяется в пространстве имен System.Drawing.Drawing2D, а дополнительная инструкция using нужна другим инструкциям, добавленным к этой программе.
LineArcCombo рисует первую прямую, используя метод DrawLine из класса Graphics:
grfx.DrawLine(pen, 25, 100, 125, 100);
В LineArcPath вместо него применяется метод AddLine класса GraphicsPath:
path.AddLine(25, 100, 125, 100);
У AddLine нет аргумента Pen, но остальные аргументы идентичны таковым метода DrawLine. Метод AddArc также отличается от DrawArc отсутствием аргумента Pen. AddLine и AddArc ничего не рисуют: координаты, заданные при их вызове, просто накапливаются, составляя контур.
Наконец, контур появляется на экране (его рисует третья из добавленных к этой программе инструкций):
grfx.DrawPath(pen, path);
Заметьте DrawPath — это метод класса Graphics, нашего старого знакомого. В результате вызова DrawPath получается именно такая фигура, которая нам нужна:
Разработчики с опытом программирования Win32 API или MFC могут заметить, что реализация графических контуров в Windows Forms концептуально иная. В Win32 API функция BeginPath переводит контекст устройства в специальный режим, в котором вызовы обычных графических функций (LineTo, BezierTo и т.п.) не визуализируются, а становятся частью контура. Формирование контура завершается вызовом EndPath, после чего контур можно нарисовать, вызвав StrokePath (или сделать с ним что-то еще).
Подход, используемый в Windows Forms, намного гибче. Win32 API допускает существование лишь одного контура в контексте некоторого устройства, а в Windows Forms можно создавать и хранить сколько угодно контуров. Кроме того, для создания контура не нужен объект Graphics. Контур существует независимо от объектов Graphics, пока он не будет визуализирован методом DrawPath (или пока над контуром не выполнят иное действие).
Фактически можно изменить программу LineArcPath, чтобы она сохраняла объект GraphicsPath как поле. Можно создавать контур и вызывать методы AddLine и DrawPath в конструкторе формы, после чего методу DoPage останется просто создать перо и вызвать DrawPath. Если же действительно нужно покончить со всеми приготовлениями в конструкторе, можно сделать инструкцию для создания пера полем формы.
netlib.narod.ru | < Назад | Оглавление | Далее > |