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

Канонический сплайн

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


Методы DrawCurve класса Graphics (выборочно)



DrawCurve(Pen pen, Point[] apt)
DrawCurve(Pen pen, PointF[] aptf)
DrawCurve(Pen pen, Point[] apt, float fTension)
DrawCurve(Pen pen, PointF[] aptf, float fTension)


Для этого метода нужно задать минимум две точки. Если массив, переданный методу, содержит всего две точки, то DrawCurve соединяет их прямой. Если точек три или больше, метод рисует кривую, соединяющую все точки.

Существенное отличие канонического сплайна от кривых Безье в том, что он проходит через все точки, заданные массивом. Иногда участок кривой между двумя соседними точками называют сегментом кривой. Форма каждого сегмента определяется не только начальной и конечной точками сегмента, но и их соседними точками. Допустим, кривая задана массивом структур Point с именем apt. В этом случае форма сегмента, ограниченного точками apt[3] и apt[4], также будет определяться точками apt[2] и apt[5].

Форма сплайна, помимо прочего, зависит от натяжения (tension) — параметра, который задают как явный аргумент в перегруженных версиях метода DrawCurve. По своей сути этот параметр напоминает жесткость традиционного лекала. По умолчанию натяжение равняется 0,5. Натяжение, равное 0, дает прямые, т.е. метод DrawCurve превращается в DrawLines. При натяжении больше 0,5 изгиб кривой увеличивается. Если задать натяжение меньше 0 (или больше 1), кривая может сделать петлю.

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

CanonicalSpline.cs

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

  class CanonicalSpline: Form
  {
      protected Point[] apt      = new Point[4];
      protected float   fTension = 0.5f;

      public static void Main()
      {
          Application.Run(new CanonicalSpline());
      }
      public CanonicalSpline()
      {
          Text = "Canonical Spline";
          BackColor = SystemColors.Window;
          ForeColor = SystemColors.WindowText;
          ResizeRedraw = true;

          ScrollBar scroll = new VScrollBar();
          scroll.Parent = this;
          scroll.Dock = DockStyle.Right;
          scroll.Minimum = -100;
          scroll.Maximum =  109;
          scroll.SmallChange =  1;
          scroll.LargeChange = 10;
          scroll.Value = (int) (10 * fTension);
          scroll.ValueChanged += new EventHandler(ScrollOnValueChanged);

          OnResize(EventArgs.Empty);
      }
      void ScrollOnValueChanged(object obj, EventArgs ea)
      {
          ScrollBar scroll = (ScrollBar) obj;

          fTension = scroll.Value / 10f;

          Invalidate(false);
      }
      protected override void OnResize(EventArgs ea)
      {
          base.OnResize(ea);

          int cx = ClientSize.Width;
          int cy = ClientSize.Height;

          apt[0] = new Point(    cx / 4,     cy / 2);
          apt[1] = new Point(    cx / 2,     cy / 4);
          apt[2] = new Point(    cx / 2, 3 * cy / 4);
          apt[3] = new Point(3 * cx / 4,     cy / 2);
      }
      protected override void OnMouseDown(MouseEventArgs mea)
      {
          Point pt;

          if (mea.Button == MouseButtons.Left)
          {
              if (ModifierKeys == Keys.Shift)
                  pt = apt[0];
              else if (ModifierKeys == Keys.None)
                  pt = apt[1];
              else
                  return;
          }
          else if (mea.Button == MouseButtons.Right)
          {
              if (ModifierKeys == Keys.None)
                  pt = apt[2];
              else if (ModifierKeys == Keys.Shift)
                  pt = apt[3];
              else
                  return;
          }
          else
              return;

          Cursor.Position = PointToScreen(pt);
      }
      protected override void OnMouseMove(MouseEventArgs mea)
      {
          Point pt = new Point(mea.X, mea.Y);

          if (mea.Button == MouseButtons.Left)
          {
              if (ModifierKeys == Keys.Shift)
                  apt[0] = pt;
              else if (ModifierKeys == Keys.None)
                  apt[1] = pt;
              else
                  return;
          }
          else if (mea.Button == MouseButtons.Right)
          {
              if (ModifierKeys == Keys.None)
                  apt[2]= pt;
              else if (ModifierKeys == Keys.Shift)
                  apt[3] = pt;
              else
                  return;
          }
          else
              return;

          Invalidate();
      }
      protected override void OnPaint(PaintEventArgs pea)
      {
          Graphics grfx  = pea.Graphics;
          Brush    brush = new SolidBrush(ForeColor);

          grfx.DrawCurve(new Pen(ForeColor), apt, fTension);

          grfx.DrawString("Tension = " + fTension, Font, brush, 0, 0);

          for (int i = 0; i < 4; i++)
              grfx.FillEllipse(brush, apt[i].X - 3, apt[i].Y - 3, 7, 7);
      }
  }

Как и в программе Bezier, левая и правая кнопки мыши служат для перемещения точек p1 и p2. Кроме того, при нажатии клавиши Shift программа CanonicalSpline позволяет перемещать точки p0 и p3 левой и правой кнопками. Обычно эта программа выглядит так:


Рис. 13.12.

Натяжение регулируется положением ползунка на полосе прокрутки, значение натяжения выводится в левом верхнем углу окна программы. Я разрешил менять натяжение от 10 до –10, чтобы стало видно, как крайние значения заставляют кривую выделывать сумасшедшие петли. Вот одна из моих любимых кривых, созданная при помощи заданного по умолчанию массива структур Point:


Рис. 13.13.

Следующие методы DrawCurve позволяют задействовать подмножество точек массива:


Методы DrawCurve класса Graphics (выборочно)



DrawCurve(Pen pen, PointF[] aptf, int iOffset, int iSegments)
DrawCurve(Pen pen, Point[] apt, int iOffset, int iSegments,
          float fTension)
DrawCurve(Pen pen, PointF[] aptf, int iOffset, int iSegments,
          float fTension)


Аргумент iOffset — это индекс начальной точки кривой в массиве Point или PointF. Аргумент iSegments задает число рисуемых сегментов, а кроме того, показывает, сколько дополнительных структур Point или PointF будет использовать метод. Вызов:

  grfx.DrawCurve(pen, aptf, 2, 3);

рисует три сегмента: aptf[2] – aptf[3], aptf[3] – aptf[4] и aptf[4] – aptf[5]. Результат выглядит не так, как вызов более простой версии DrawCurve с теми же четырьмя точками, заданными явно. В версии DrawCurve, использующей аргументы iOffset и iSegments точка apt[1] используется для определения формы сегмента aptf[2] – aptf[3], а точка apt[6] определяет форму сегмента aptf[4] – aptf[5].

Метод DrawClosedCurve соединяет первую и последнюю точки массива дополнительной кривой:


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



DrawClosedCurve(Pen pen, Point[] apt)
DrawClosedCurve(Pen pen, PointF[] aptf)
DrawClosedCurve(Pen pen, Point[] apt, float fTension, FillMode fm)
DrawClosedCurve(Pen pen, PointF[] aptf, float fTension, FillMode fm)


Своеобразие DrawClosedCurve не только в том, что он рисует дополнительный сегмент. Первый сегмент, нарисованный этим методом, несколько отличается от сегмента, нарисованного DrawCurve, поскольку на его форму также влияет последняя точка массива. Аналогично первая точка массива влияет на форму предпоследнего сегмента.

У двух перегруженных версий метода DrawClosedCurve есть аргумент FillMode. Вы, конечно, помните перечисление FillMode, определяемое в пространстве имен System.Drawing.Drawing2D. Оно используется методом DrawPolygon и определяет, какие замкнутые области будут залиты, а какие — нет:


Перечисление FillMode



Член Значение Комментарий

Alternate 0 Задано по умолчанию, чередует закрашенные и незакрашенные области фигуры
Winding 1 Закрашивает максимально возможное число внутренних областей фигуры


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

Намного больше смысла у этого аргумента в методах FillClosedCurve.


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



FillClosedCurve(Brush brush, Point[] apt)
FlllClosedCurve(Brush brush, PointF[] aptf)
FillClosedCurve(Brush brush, Point[] apt, FillMode fm)
FillClosedCurve(Brush brush, PointF[] aptf, FillMode fm)
FillClosedCurve(Brush brush, Point[] apt, FillMode fm, float fTension)
FillClosedCurve(Brush brush, PointF[] aptf, FillMode fm, float fTension)


Программа ClosedCurveFillModes почти идентична FillModesClassical из главы 5. Она рисует две пятиконечные звезды, чтобы продемонстрировать отличие между режимами FillMode.Alternate и FillMode.Winding.

ClosedCurveFillModes.cs

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

  class ClosedCurveFillModes: PrintableForm
  {
      public new static void Main()
      {
          Application.Run(new ClosedCurveFillModes());
      }
      ClosedCurveFillModes()
      {
          Text = "FillClosedCurve Fill Modes";
          ClientSize = new Size(2 * ClientSize.Height, ClientSize.Height);
      }
      protected override void DoPage(Graphics grfx, Color clr, int cx, int cy)
      {
          Brush   brush = new SolidBrush(clr);
          Point[] apt   = new Point[5];
          
          for (int i = 0; i < apt.Length; i++)
          {
              double dAngle = (i * 0.8 - 0.5) * Math.PI;
              apt[i] = new Point(
                             (int)(cx *(0.25 + 0.24 * Math.Cos(dAngle))),
                             (int)(cy *(0.50 + 0.48 * Math.Sin(dAngle))));
          }
          grfx.FillClosedCurve(brush, apt, FillMode.Alternate);

          for (int i = 0; i < apt.Length; i++) 
              apt[i].X += cx / 2;

          grfx.FillClosedCurve(brush, apt, FillMode.Winding);
      }
  }

Хотя в этих фигурах нетрудно узнать звезды, у них закругленные лучи:


Рис. 13.14.

Они больше похожи на печенья в форме звездочек. Когда те выходят из-под формы, их лучи еще остры, но в духовке тесто немного поднимается, и печенья становятся округлыми, совсем как наши фигуры.


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

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