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

Другие виды модификации контура

Transform — не единственный метод класса GraphicsPath, модифицирующий все координаты контура. Метод Flatten предназначен для преобразования всех кривых Безье в контуре в прямолинейные сегменты:


Методы Flatten класса GraphicsPath



void Flatten()
void Flatten(Matrix matrix)
void Flatten(Matrix matrix, float fFlatness)


Есть возможность перед выравниванием преобразовать координаты точек при помощи объекта Matrix.

По мере увеличения значения аргумента fFlatness число сегментов линии уменьшается. По умолчанию для этого аргумента задано значение 0,25. Для значения 0 метод Flatten не определен.

Метод Widen влияет на контур намного сильнее, чем Flatten. Первым аргументом этого метода всегда является объект Pen:


Методы Widen класса GraphicsPath



void Widen(Pen pen)
void Widen(Pen pen, Matrix matrix)
void Widen(Pen pen, Matrix matrix, float fFlatness)


Этот метод игнорирует цвет пера и использует только его толщину, которая обычно не меньше двух единиц. Представьте себе тот же контур, но нарисованный толстым пером. Так вот, новый контур будет представлять собой контуры этой толстой линии. Каждый открытый контур преобразуется в закрытый, а каждый закрытый — в пару закрытых контуров. Перед утолщением контура метод Widen преобразует все кривые Безье в составные линии. Для этого преобразования также можно задать коэффициент сглаживания или преобразовать координаты контура перед его утолщением при помощи объекта Matrix.

Порой результаты метода Widen выглядят странно, поэтому полезно взглянуть на следующую программу: в конструкторе создается контур, состоящий из открытой V-образной фигуры и одной закрытой фигуры в форме треугольника.

WidenPath.cs

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

  class WidenPath: PrintableForm
  {
      GraphicsPath path;

      public new static void Main()
      {
          Application.Run(new WidenPath());
      }
      public WidenPath()
      {
          Text = "Widen Path";

          path = new GraphicsPath();

          // Создаем открытый подконтур

          path.AddLines(new Point[] { new Point(20, 10),
                                      new Point(50, 50),
                                      new Point(80, 10) });

          // Создаем закрытый подконтур

          path.AddPolygon(new Point[] { new Point(20, 30),
                                        new Point(50, 70),
                                        new Point(80, 30) });
      }
      protected override void DoPage(Graphics grfx, Color clr, int cx, int cy)
      {
          grfx.ScaleTransform(cx / 300f, cy / 200f);

          for (int i = 0; i < 6; i++)
          {
              GraphicsPath pathClone = (GraphicsPath) path.Clone();
              Matrix       matrix    = new Matrix();
              Pen          penThin   = new Pen(clr, 1);
              Pen          penThick  = new Pen(clr, 5);
              Pen          penWiden  = new Pen(clr, 7.5f);
              Brush        brush     = new SolidBrush(clr);

              matrix.Translate((i % 3) * 100, (i / 3) * 100);

              if (i < 3)
                  pathClone.Transform(matrix);
              else
                  pathClone.Widen(penWiden, matrix);

              switch (i % 3)
              {
              case 0:  grfx.DrawPath(penThin, pathClone);   break;
              case 1:  grfx.DrawPath(penThick, pathClone);  break;
              case 2:  grfx.FillPath(brush, pathClone);     break;
              }
          }
      }
  }

Метод DoPage создает 6 копий контура при помощи метода Clone, а затем вызывает метод Transform, чтобы поместить каждую копию на свое место. После этого программа прорисовывает контур 6 разными способами. Вот результат:


Рис. 15.6.

В верхнем ряду показаны контуры, нарисованные пером шириной в 1 единицу, 5 единиц и залитый контур. Контуры в нижнем ряду нарисованы так же, как соответствующие фигуры из верхнего ряда, но после этого был вызван метод Widen с пером шириной в 7,5 единиц.

Эффект метода Widen отчетливее всего виден на картинках слева. Открытая V-образная фигура преобразуется в закрытый контур, который обводит исходную фигуру, как будто для ее рисования применялось широкое перо. Закрытая треугольная фигура преобразуется в два контура. Один из полученных контуров проходит по внутреннему, а другой — по внешнему контуру толстой линии, что выглядит как если бы треугольник был нарисован толстым пером. Конечно, все эти внутренние завитушки и уголки выглядят странно, но таковы особенности работы алгоритма метода Widen.

Две картинки в центре почти не отличаются от левых за исключением того, что они нарисованы более толстым пером.

Область в центре залитой фигуры, расположенной в правом верхнем углу, осталась свободной и результат применения режима заливки по умолчанию FillMode.Alternating. Если изменить режим заливки на FillMode.Winding, все внутренние области фигуры будут залиты. Самая интересная версия этой фигуры — в нижнем правом углу. Она нарисована в результате применения метода FillPath на утолщенном контуре. Очень похожая фигура получится, если при помощи метода DrawPath прорисовать исходный контур толстым пером.

Метод GetBounds позволяет определить наименьший прямоугольник, в котором может поместиться контур, с учетом или без учета матричного преобразования и применения толстого пера:


Методы GetBounds класса GraphicsPath



RectangleF GetBounds()
RectangleF GetBounds(Matrix matrix)
RectangleF GetBounds(Matrix matrix, Pen pen)


Аргументы этих методов не влияют на координаты, хранимые н контуре. Имейте в виду: размеры вычисляемого прямоугольника отражают минимальные и максимальные значения координат x и y для любой точки контура. Если контур содержит кривые Безье, то прямоугольник отражает координаты управляющих точек, а не реальной кривой. Чтобы определить размеры фигуры более точно, перед GetBounds надо вызнать метод Flatten.

В главе 7 сказано, что матричное преобразование является линейным. Свойство линейности налагает ряд ограничений на действия, которые можно выполнить при помощи преобразования. Например, параллелограммы могут быть преобразованы только в параллелограммы.

Сейчас мы познакомимся еще с одним преобразованием в лице метода Warp из класса GraphicsPaht. Подобно Transform метод Warp модифицирует все координаты контура. Но преобразование, выполпяемое Warp, нелинейно, и это единственное нелинейное преобразование в GDI+.

Для использования Warp нужно задать 4 исходные и 4 целевые координаты. Метод отображает 4 исходных координаты на соответствующие целевые координаты. Исходные координаты задают в виде структуры RectangleF. Удобно (но не обязательно) заносить в аргумент RectangleF структуру RectangleF, возвращаемую GetBounds. Целевые координаты задают в виде массива структур PointF:


Методы Warp класса GraphicsPath



void Warp(PointF[] aptfDst, RectangleF rectfSrc)
void Warp(PointF[] aptfDst, RectangleF rectfSrc, Matrix matrix)
void Warp(PointF[] aptfDst, RectangleF rectfSrc, Matrix matrix, WarpMode wm)
void Warp(PointF[] aptfDst, RectangleF rectfSrc, Matrix matrix,
          WarpMode wm, float fFlatness)


Эти методы также позволяют задать объект Matrix и коэффициент сглаживания. Исходные точки преобразуются в целевые так:

Необязательный аргумент задает способ расчета промежуточных точек:


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



Член Значение

Perspective 0
Bilinear 1


Программа PathWarping позволяет поэкспериментировать с функцией Warp. Конструктор формы создает контур в виде шахматной доски размером 8 × 8 клеток. Затем мышью можно указать целевые точки для углов этого контура.

PathWarping.cs

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

  class PathWarping: Form
  {
      MenuItem     miWarpMode;
      GraphicsPath path;
      PointF[]     aptfDest = new PointF[4];

      public static void Main()
      {
          Application.Run(new PathWarping());
      }
      public PathWarping()
      {
          Text = "Path Warping";

          // Создаем меню

          Menu = new MainMenu();

          Menu.MenuItems.Add("&Warp Mode");
          EventHandler ehClick = new EventHandler(MenuWarpModeOnClick);

          miWarpMode = new MenuItem("&" + (WarpMode)0, ehClick);
          miWarpMode.RadioCheck = true;
          miWarpMode.Checked = true;
          Menu.MenuItems[0].MenuItems.Add(miWarpMode);

          MenuItem mi = new MenuItem("&" + (WarpMode)1, ehClick);
          mi.RadioCheck = true;
          Menu.MenuItems[0].MenuItems.Add(mi);

          // Создаем контур

          path = new GraphicsPath();

          for (int i = 0; i <= 8; i++)
          {
              path.StartFigure();
              path.AddLine(0, 100 * i, 800, 100 * i);
              path.StartFigure();
              path.AddLine(100 * i, 0, 100 * i, 800);
          }
          // Инициализируем массив точек

          aptfDest[0] = new Point( 50,  50);
          aptfDest[1] = new Point(200,  50);
          aptfDest[2] = new Point( 50, 200);
          aptfDest[3] = new Point(200, 200);
      }
      void MenuWarpModeOnClick(object obj, EventArgs ea)
      {
          miWarpMode.Checked = false;
          miWarpMode = (MenuItem) obj;
          miWarpMode.Checked = true;

          Invalidate();
      }
      protected override void OnMouseDown(MouseEventArgs mea)
      {
          Point pt;

          if (mea.Button == MouseButtons.Left)
          {
              if (ModifierKeys == Keys.None)
                  pt = Point.Round(aptfDest[0]);
              else if (ModifierKeys == Keys.Shift)
                  pt = Point.Round(aptfDest[2]);
              else
                  return;
          }
          else if (mea.Button == MouseButtons.Right)
          {
              if (ModifierKeys == Keys.None)
                  pt = Point.Round(aptfDest[1]);
              else if (ModifierKeys == Keys.Shift)
                  pt = Point.Round(aptfDest[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.None)
                  aptfDest[0] = pt;
              else if (ModifierKeys == Keys.Shift)
                  aptfDest[2] = pt;
              else
                  return;
          }
          else if (mea.Button == MouseButtons.Right)
          {
              if (ModifierKeys == Keys.None)
                  aptfDest[1] = pt;
              else if (ModifierKeys == Keys.Shift)
                  aptfDest[3] = pt;
              else
                  return;
          }
          else
              return;

          Invalidate();
      }
      protected override void OnPaint(PaintEventArgs pea)
      {
          Graphics     grfx       = pea.Graphics;
          GraphicsPath pathWarped = (GraphicsPath) path.Clone();
          WarpMode     wm         = (WarpMode) miWarpMode.Index;

          pathWarped.Warp(aptfDest, path.GetBounds(), new Matrix(), wm);
          grfx.DrawPath(new Pen(ForeColor), pathWarped);
      }
  }

Левая и правая кнопки мыши позволяют устанавливать целевые координаты точек, в которых располагаются верхние углы прямоугольника, а эти же кнопки с нажатой клавишей Shift — координаты нижних углов прямоугольника. Меню позволяет переключаться между режимами Perspective и Bilinear. (Обратите внимание на рациональный прием: метод OnPaint приводит тип значения свойства Index выбранного пользователем элемента меню к члену перечисления WarpMode.) Вот пример применения метода Warp в режиме Perspective.


Рис. 15.7.

С помощью контура удобно реализовывать собственные нелинейные преобразования. Прежде всего следует сохранить фигуру, которую нужно вывести, в виде контура. Далее надо получить координаты все контх точек контура из свойств PathPoints и PathTypes. Модифицировать эти точки как угодно, после чего при помощи одного из конструкторов объекта GraphicsPath, отличного от заданного по умолчанию, нужно создать новый контур на основе модифицированного массива. В главе 19 я покажу пару примеров использования этой методики.


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

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