netlib.narod.ru< Назад | Оглавление | Далее >

Сочлененные линии

Выше я сказал, что в некоторых средах программирования графики используется текущая позиция, что можно считать странным: ведь чтобы нарисовать одну линию, приходится вызывать дне функции. Однако текущая позиция серьезно помогает при рисовании нескольких соединенных друг с другом линий, позволяя задавать при вызове каждой функции координаты лишь одной точки. GDI+ не столь экономичен. Например, чтобы нарисовать прямоугольник по периметру клиентской области программы, приходится четырежды вызывать DrawLine:

  grfx.DrawLine(pen, 0,      0,      сх - 1, 0);
  grfx.DrawLine(per, сх - 1, 0,      сх - 1, су - 1);
  grfx.DrawLine(pen, сх - 1, су - 1, 0,      су - 1);
  grfx.DrawLine(pen, 0,      су - 1, 0,      0);

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

В силу этих и некоторых других причин (которые мы обсудим чуть позже) в класс Graphics включен метод рисования нескольких соединенных линий, в совокупности обычно называемых ломаной. Есть две версии DrawLines (обратите внимание на множественное число в названии метода):


Методы DrawLines класса Graphics



void DrawLines(Pen pen, Point[] apt)
void DrawLines(Pen pen, PointF[] aptf)


Эти методы требуют массив целочисленных координат (Point) или координат с плавающей точкой (PointF).

Вот код, использующий метод DrawLines для очерчивания клиентской области:

BoxingTheClient.cs

  //------------------------------------------------
  // BoxingTheClient.cs (C) 2001 by Charles Petzold
  //------------------------------------------------
  using System;
  using System.Drawing;
  using System.Windows.Forms;

  class BoxingTheClient: PrintableForm
  {
      public new static void Main()
      {
          Application.Run(new BoxingTheClient());
      }
      public BoxingTheClient()
      {
          Text = "Boxing the Client";
      }
      protected override void DoPage(Graphics grfx, Color clr, int cx, int cy)
      {
          Point[] apt = {new Point(0,      0),
                         new Point(cx - 1, 0),
                         new Point(cx - 1, cy - 1),
                         new Point(0,      cy - 1),
                         new Point(0,      0)};

          grfx.DrawLines(new Pen(clr), apt);
      }
  }

Используемый каасс является производным от PrintableForm, так что программа поддерживает еще и печать.

Массив структур Point может быть определен прямо в методе DrawLines, и приведенная ниже программа так и делает. Она воплощает решение детской головоломки, в которой нужно нарисовать домик, не отрывая карандаша от бумаги.

DrawHouse.cs

  //------------------------------------------
  // DrawHouse.cs (C) 2001 by Charles Petzold
  //------------------------------------------
  using System;
  using System.Drawing;
  using System.Windows.Forms;

  class DrawHouse: PrintableForm
  {
      public new static void Main()
      {
          Application.Run(new DrawHouse());
      }
      public DrawHouse()
      {
          Text = "Draw a House in One Line";
      }
      protected override void DoPage(Graphics grfx, Color clr, int cx, int cy)
      {
          grfx.DrawLines(new Pen(clr),
                         new Point[] 
                         {
                         new Point(    cx / 4, 3 * cy / 4), // Нижний левый
                         new Point(    cx / 4,     cy / 2),
                         new Point(    cx / 2,     cy / 4), // Крыша
                         new Point(3 * cx / 4,     cy / 2),
                         new Point(3 * cx / 4, 3 * cy / 4), // Нижний правый
                         new Point(    cx / 4,     cy / 2),
                         new Point(3 * cx / 4,     cy / 2),
                         new Point(    cx / 4, 3 * cy / 4), // Нижний левый
                         new Point(3 * cx / 4, 3 * cy / 4)  // Нижний правый
                         });
      }
  }

Но метод DrawLines создан не для решения детских головоломок. В главе 17 вы узнаете, как создавать перья на основе комбинации точек и штрихов, а также как при создании толстых перьев определять вид концов линии (закругленные, квадратные и т.д.) или вид двух соединенных вместе линий. Эти элементы линий называется концами и соединениями. Чтобы придать концам и соединениям правильный вид, GDI+ должен знать, какими являются линии, имеющие общие точки: отдельными или соединенными. Об этом можно сообщить GDI+, используя метод DrawLines вместо DrawLine.

Дополнительный довод в пользу DrawLines — большая производительность. Приведенные программы-примеры не продемонстрировали особого прироста производительности, да это для них и не важно. На самом деле мы еще не задействовали метод DrawLines в полной мере. Видите ли, DrawLines создан не для рисования прямых, его настоящее назначение — кривые. Хитрость DrawLines в том, что он способен нарисовать множество очень коротких отдельных линий, позволяя изобразить любую кривую, которая может быть задана математически.

Можно спокойно передавать сотни и даже тысячи структур Point или PointF при единственном вызове метода DrawLines — он создан именно для этого Даже если передать DrawLines миллион структур Point или PointF, их визуализация займет всего пару секунд.

Сколько потребуется точек, чтобы нарисовать кривую? Пожалуй, меньше миллиона. Максимальная гладкость кривой достигается, если число ее точек по крайней мере равно числу пикселов. Часто вы можете примерно аппроксимировать это число.

Этот фрагмент кода рисует один цикл синусоиды, вписанный в клиентскую область.

SineCurve.cs

  //------------------------------------------
  // SineCurve.cs (C) 2001 by Charles Petzold
  //------------------------------------------
  using System;
  using System.Drawing;
  using System.Windows.Forms;

  class SineCurve: PrintableForm
  {
      public new static void Main()
      {
          Application.Run(new SineCurve());
      }
      public SineCurve()
      {
          Text = "Sine Curve";
      }
      protected override void DoPage(Graphics grfx, Color clr, int cx, int cy)
      {
          PointF[] aptf = new PointF[cx];

          for (int i = 0; i < cx; i++)
          {
              aptf[i].X = i;
              aptf[i].Y = cy / 2 * (1 - (float) 
                                  Math.Sin(i * 2 * Math.PI / (cx - 1)));
          }
          grfx.DrawLines(new Pen(clr), aptf);
      }
  }

Это первая программа из этой книги, в которой применен тригонометрический метод из класса Math — очень важного класса, определяемого в пространстве имен System. Подробнее об этом классе говорится в приложении Б. Аргументы тригонометрических методов выражаются в радианах, а не в градусах. Класс Math также включает два поля типа const с именами PI и E которые удобно использовать с методами этого класса. Теперь больше не надо помещать в начало программмы ничего такого:

  #define PI 3.14159           // Здорово, что теперь это можно убрать!

Однако замечу, что большинство методов класса Math возвращают значения типа double, которые нужно явно привести к типу float, прежде чем использовать в PointF и подобных структурах.

Возможно, будет полезно разобрать оператор присваивания для свойства Y массива PointF в подробностях. Аргумент функции Math.Sin выражается в радианах. Полный круг (360o) соответствует 2π радиан. Таким образом, аргумент может принимать значения от 0 (когда i = 0) до 2π (когда i = ClientSize.Width – 1). Значение метода Math.Sin находится в интервале от –1 до +1 , поэтому обычно его нужно умножить на полувысоту клиентской области. Полученные значения будут изменяться от -(ClientSize.Height / 2) до +(ClientSize.Height / 2). Затем надо вычесть их из половины высоты клиентской области, чтобы результат варьировался от 0 до ClientSize.Height. Но здесь я использовал более эффективный код, прибавляющий 1 к отрицательному результату метода Sin, получая в результате числа от 0 до 2, которые умножаются на полувысоту клиентской области. Вот как выглядит результат выполнения этой программы:


Рис. 5.4.


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz