Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Grafika.docx
Скачиваний:
2
Добавлен:
27.08.2019
Размер:
63.19 Кб
Скачать

1. Теоретическая часть

Обзор графического рисования

Первое, что следует запомнить относительно кода обработки графики — то, что система Windows не хранит в памяти прорисовку открытого окна, если оно заслонено другими окнами. Когда заслоненное окно становится видимым, Windows дает команду приложению нарисовать его. Программист должен позаботиться только о прорисовке содержимого своего окна. Windows сама заботится о границе, строке заголовка и всех остальных особенностях окна.

При создании окна для рисования, как правило, объявляют класс, производный от System. Windows. Forms. Form. При создании нестандартного элемента управления объявляют класс, производный от System. Windows. Forms. UserControl. В любом случае это ведет к переопределению виртуальной функции OnPaint (). Система Windows вызывает эту функцию каждый раз, когда требуется выполнить повторную прорисовку любой части окна.

При наступлении этого события класс PaintEventArgs передается в качестве аргумента. PaintEventArgs содержит два важных элемента информации: объект Graphics и ClipRectangle. Класс Graphics мы рассмотрим первым.

Класс Graphics

Класс Graphics инкапсулирует поверхность рисования GDI+. Существуют три основных типа поверхностей рисования:

  • окна и элементы управления на экране;

  • страницы, отправляемые на принтер;

  • битовые карты и изображения в памяти.

Класс Graphics предоставляет функции для рисования на любой из этих поверхностей рисования. В числе прочих возможностей его можно использовать для рисования дуг, кривых, эллипсов, изображений, линий, прямоугольников и текста.

Объект Graphics для окна можно получить двумя различными способами. Первый — переопределение метода OnPaint (). Класс Form наследует метод OnPaint () из класса Control, и этот метод является обработчиком события Paint, которое генерируется при каждом перерисовывании элемента управления. Объект Graphics можно получить из класса PaintEventArgs, который передается событием:

protected override void OnPaint(PaintEventArgs e)

{

Graphics g = e.Graphics;

// Выполнить здесь рисование.

}

В других случаях может требоваться выполнение рисования непосредственно в окне, не дожидаясь генерации события Paint. Такая ситуация может возникать при создании кода для выбора графического объекта в окне (подобно выбору пиктограмм в окне проводника Windows) или перетаскивании объекта мышью. Объект Graphics можно получить, вызывая в форме метод CreateGraphics (), который является еще одним методом, унаследованным Form от класса Control:

protected void Forml_Click (object sender, System.EventArgs e)

{

Graphics g = this.CreateGraphics ();

// Выполнить здесь рисование.

g. Dispose (); // это важно

}

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

Удаление объектов

При недостаче ресурсов. Система Windows начинает работать очень медленно и иногда приложения прорисовываются некорректно. Правильно ведущие себя приложения освобождают свои ресурсы по завершении работы с ними. При разработке приложений, использующих .NET Framework, приходится иметь дело с несколькими типами данных, для которых важно вызывать метод Dispose () — в противном случае некоторые ресурсы не будут освобождены. Эти классы реализуют интерфейс IDisposable, и Graphics — один из них.

При получении объекта Graphics от метода OnPaint (), мы не создавали его, поэтому вызов метода Dispose () — не наша забота. Но при вызове метода CreateGraphics () вызов метода Dispose () становится нашей обязанностью.

Вызов метода Dispose () важен при получении объекта Graphics посредством вызова метода CreateGraphics().

Метод Dispose () автоматически вызывается в деструкторе различных классов, которые реализуют интерфейс IDisposable. Может показаться, что это освобождает от обязанности вызывать метод Dispose (), однако это не так. Это связано с тем, что только сборщик мусора (garbage collector — GC) регулярно вызывает деструктор, а мы не можем гарантировать, когда именно GC будет запущен. В частности, в операционной системе Windows 9Х, располагающей очень большим объемом памяти, GC может запускаться очень редко, и все ресурсы вполне могут быть исчерпаны, прежде чем он будет запущен. Хотя нехватка памяти ведет к запуску GC, нехватка ресурсов не оказывает такого действия. Windows 2000 и Windows ХР значительно менее чувствительны к нехватке ресурсов и, согласно их спецификациям, эти две операционные системы не имеют никаких определенных ограничений на использование указанных типов ресурсов. Однако нередко приходится сталкиваться с непредвиденным поведением Windows 2000 при наличии слишком большого количества открытых приложений, когда закрытие некоторых из них быстро восстанавливает правильную работу системы. В любом случае лучше вручную корректно и вовремя удалять любые объекты, потребляющие много ресурсов.

В то же время существует более простой способ работы с объектами, которые нужно удалять надлежащим образом. Можно использовать конструкцию using, которая автоматически вызывает метод Dispose (), когда объект выходит из области видимости. Следующий код демонстрирует правильное применение ключевого слова using в этом контексте:

using (Graphics g = this.CreateGraphics())

{

g.DrawLine(Pens.Black, new Point(0, 0), new Point(3, 5));

Приведенный код полностью эквивалентен следующему:

Graphics g = this.CreateGraphics() ;

try

{

g.DrawLine(Pens.Black, newPoint(0, 0), newPoint(3, 5));

}

finally

{

if (g ! = null)

((IDisposable)g).Dispose();

}

Приведенный пример демонстрирует совершенно особое применение ключевого слова using — блок кода, обозначенный ключевым словом using, можно считать блоком using.

Обработка обращений к методу Dispose() выполняется с использованием обоих стилей. В одних случаях вызов метода Dispose() выполняется непосредственно, в других — через блок using. Последний метод значительно более понятен, как видно из приведенных фрагментов кода, но ни один из них не дает особых преимуществ.

Прежде чем приступить к исследованию первого примера, необходимо рассмотреть еще два аспекта рисования графических изображений — систему координат и цвета.

Система координат

При разработке программы, которая прорисовывает сложные, замысловатые изображения, очень важно, чтобы код рисовал то, и только то, что требуется. Единственный неправильно помещенный пиксель может оказать отрицательное влияние на визуальную привлекательность графического изображения, поэтому важно четко представлять, какие пиксели рисуются при вызове операций рисования. Это наиболее важно при создании нестандартных элементов управления, которые содержат множество прямоугольников и горизонтальных и вертикальных линий. Линия, которая короче или длиннее, чем требуется, всего на один пиксель, очень бросается в глаза. Однако это несколько менее заметно в случае кривых, диагональных линий и других графических элементов.

GDI+ поддерживает систему координат, построенную на основе линий воображаемой сетки, которые проходят через центры пикселей. Эти линии нумеруются, начиная с нуля: пересечение линий сетки в верхнем левом пикселе любого координатного пространства — точка X = 0, Y = 0. Более кратко точку можно обозначать как точку 1,2, что эквивалентно обозначению X = 1, Y = 2. Каждое окно, в котором выполняется рисование, имеет собственное пространство координат. При создании нестандартного элемента управления, который может использоваться в других окнах, сам этот элемент обладает собственным пространством координат. Другими словами, при рисовании в этом нестандартном элементе управления верхним левым пикселем является точка 0,0. При этом не нужно беспокоиться о том, где нестандартный элемент управления размещается в содержащем его окне.

При рисовании линий GDI+ центрирует нарисованные пиксели на указанной линии сетки. При рисовании горизонтальной линии, имеющей целочисленные координаты, можно считать, что половина каждого пикселя размещается над линией воображаемой сетки, а половина — под ней.

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

Для указания координат при рисовании часто придется использовать три структуры: Point (точка), Size (размер) и Rectangle (прямоугольник).

Point

Интерфейс GDI+ использует Point для представления точки, имеющей целочисленные координаты. Она представляет собой точку на двумерной плоскости — т.е. спецификацию одиночного пикселя. Многие функции GDI+, такие как DrawLine (), принимают Point в качестве аргумента. Объявление и конструирование структуры Point выполняется следующим образом:

Point р = new Point (1, 1) ;

Для получения и установки координат X и Y структуры Point используют общедоступные свойства X и Y.

Size

GDI+ использует структуру Size для представления размера в пикселях. Структура Size содержит как ширину, так и высоту. Объявление и конструирование структуры Size выполняется следующим образом:

Size s = new Size (5, 5) ;

Для получения и установки высоты и ширины структуры Size используют общедоступные свойства Height и Width.

Rectangle

Интерфейс GDI+ использует эту структуру во множестве различных мест для указания координат прямоугольника. Структура Point определяет верхний левый угол прямоугольника, а структура Size — его размеры. Существуют два конструктора структуры Rectangle. Один принимает в качестве аргументов позицию X, позицию Y, ширину и высоту. Второй принимает структуры Point и Size. Ниже приведены два примера объявления и конструирования структуры Rectangle:

Rectangle r1 = new Rectangle(1, 2, 5, 6) ;

Point p = new Point (1, 2); Size s = new Size (5, 6);

Rectangle r2 = new Rectangle (p, s) ;

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

GraphicsPath

В интерфейсе GDI+ доступны два более важных типа данных, которые используются в качестве аргументов различных функций рисования. Класс Graphics Path представляет серию связанных линий и кривых. При конструировании пути к нему можно добавлять линии, кривые Безье, дуги, сектора, многоугольники, прямоугольники и другие элементы. По завершении конструирования сложного пути его можно рисовать посредством одной операции: вызова метода DrawPath (). Путь можно заполнить (выполнить заливку), вызывая метод FillPath ().

Конструирование объекта Graphics Path осуществляется с использованием массива точек и объекта PathTypes. Объект PathTypes — это массив типа byte, в котором каждый элемент соответствует элементу массива точек и представляет дополнительную информацию о способе конструирования пути, проходящего через каждую конкретную точку. Информация о пути через точку может быть получена с помощью перечисления PathPointType. Например, если точка — начало пути, тип пути для этой точки — PathPointType. Start. Если точка — соединение между двумя линиями, тип пути для этой точки — PathPointType. Line. Если точка применяется для конструирования кривой Безье из точек, расположенных до и после данной, типом пути является PathPointType.Bezier.

Области

Класс Region (область) — сложная графическая форма, состоящая из прямоугольников и путей. После создания объекта Region эту область можно нарисовать, используя метод FillRegion ().

Цвета

Многие операции рисования в среде GDI+ требуют указания цвета. При рисовании линии или прямоугольника необходимо задать нужный цвет.

В интерфейсе GDI+ цвета инкапсулированы в структуре Color. Цвет можно создать, передавая функции структуры Color значения красного, зеленого и синего компонентов, но это почти никогда не требуется. Структура Color содержит около 150 свойств, которые позволяют получать широкое множество заранее определенных цветов. Забудьте о красном, зеленом, синем, желтом и черном цветах — если нужно выполнить рисование цветом LightGoldenrodYellow или LavenderBlush, для этого существует заранее определенный цвет. Объявление переменной типа Color и ее инициализация цветом из структуры Color выполняется следующим образом:

Color redColor = Color.Red;

Color anotherColor = Color.LightGoldenrodYellow;

Мы почти готовы к созданию какого-либо рисунка, но вначале следует привести несколько замечаний.

Еще один способ представления цвета — разбиение его на три компонента: оттенок, насыщенность и яркость. Структура Color содержит вспомогательные методы для выполнения этой задачи, а именно — GetHue (), GetSaturation () и GetBrightness ().

Рисование линий с использованием класса Pen

Первый пример, который мы рассмотрим — рисование линий. В следующем практическом занятии мы нарисуем линии с использованием класса Pen (перо), который позволяет определять цвет, толщину и стиль линии, отображаемой кодом. Свойства цвета и толщины очевидны, а стиль линии показывает, является ли линия сплошной или состоит из штрихов и точек. Класс Реп определен в пространстве имен System. Drawing.

Рисование форм с использованием класса Brush

Следующий пример демонстрирует использование класса Brush (кисть) для рисования таких форм, как прямоугольники, эллипсы, сектора и многоугольники. Класс Brush — абстрактный базовый класс. Для создания экземпляров объекта Brush применяются такие производные от Brush классы, как SolidBrush, TextureBrush и LinearGradientBrush.

Классы Brush и SolidBrush определены в пространстве имен System.Drawing. Однако классы TextureBrush и LinearGradientBrush принадлежат пространству имен System. Drawing. Drawing2D. Классы кистей предоставляют перечисленные ниже возможности.

  • SolidBrush выполняет заливку формы чистым цветом.

  • TextureBrush выполняет заливку формы растровым изображением. При создании этой кисти указывают также ограничивающий прямоугольник и режим укладки. Ограничивающий прямоугольник указывает, какая часть растрового изображения применяется для кисти — вовсе не обязательно использовать все растровое изображение, если это не требуется. Режим укладки обладает рядом опций, включая Tile (Плитка), которая вызывает укладку текстуры в виде плитки, и TileFlipX, TileFlipY и TileFlipXY, которые вызывают укладку изображения в виде плитки с переворотом при укладке последующих плиток. TextureBrush позволяет создавать очень интересные и впечатляющие эффекты.

  • LinearGradientBrush инкапсулирует кисть, которая рисует градиентный переход между двумя цветами по указанным углом. Угол задается в градусах. Нулевой угол означает, что переход между цветами будет выполняться слева направо. Угол в 90 градусов означает, что переход между цветами будет выполняться сверху вниз.

Еще одна заслуживающая упоминания кисть — PathGradientBrush, создающая сложный эффект затенения, при котором затенение прорисовывается от центра пути к его краю. Эта кисть напоминает раскрашивание контурных карт цветными карандашами, с выделением границ между областями или странами более темными оттенками.

Для объектов Brush всегда вызывайте метод Dispose ().

Как и при использовании объектов Graphics и Pen, для объектов Brush важно либо вызывать метод Dispose () по завершении работы с ними, либо использовать блок using. В противном случае приложение может истощать ресурсы Windows. В следующем практическом занятии мы с помощью кистей выполним заливку ряда форм.

Прорисовка текста с использованием класса Font

Следующий пример использует класс Font (шрифт) для прорисовки текста. Класс Font инкапсулирует три основных характеристики шрифта: семейство шрифта, его размер и стиль. Класс Font определен в пространстве имен System. Drawing.

В соответствии с документацией .NET, семейство шрифта "обобщает группу гарнитур шрифтов, обладающих аналогичным базовым начертанием". Это определение — несколько усложненное утверждение того, что семейства шрифтов представляют собой такие группы схожих шрифтов, как Courier, Arial или Times New Roman.

Свойство Size представляет размер гарнитуры шрифта. Однако в среде .NET Framework этот размер — не обязательно размер в пунктах. Он может быть размером в пунктах, но при желании свойство GraphicsUnit, определяющее единицу измерения размера шрифта, можно изменить с помощью свойства Unit. Вспомните, что один пункт равен 1/72 дюйма — т.е. высота шрифта размером в 10 пунктов составляет 10/72 дюйма. Посредством перечисления GraphicsUnit в качестве единицы изменения шрифтов можно указывать одну из следующих:

  • пункт (1/72 дюйма)

  • дисплей 0/75 дюйма)

  • документ 0/300 дюйма)

  • дюйм

  • миллиметр

  • пиксель

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

При прорисовке текста с применением конкретного шрифта на конкретной поверхности рисования часто требуется знать ширину пикселей указанной строки текста. Понятно, почему различные шрифты будут оказывать влияние на ширину пикселей строки — более мелкий шрифт будет приводить к подсвечиванию меньшего количества пикселей. Однако столь же важно знать особенности поверхности рисования, поскольку "пиксельное" разрешение различных поверхностей рисования различно. Как правило, разрешение экрана составляет 72 пикселя на дюйм. Принтеры поддерживают разрешения 300 пикселей на дюйм, 600 пикселей на дюйм и выше. Для вычисления ширины текста при использовании данного шрифта служит метод MeasureString () объекта Graphics. Свойство Style определяет, является ли шрифт курсивным, полужирным, зачеркнутым или подчеркнутым.

Для объектов Font всегда вызывайте метод Dispose ().

Для создаваемых объектов Font важно вызывать метод Dispose () либо использовать блок using: в противном случае приложение может истощать ресурсы Windows.

При прорисовке текста объект Rectangle используют для указания координат границ рисуемого текста. Как правило, высота этого прямоугольника должна быть равной высоте шрифта или кратной ей. Исключение их этого правила требуется только при прорисовке ряда специальных эффектов с применением усеченного шрифта.

Класс StringFormat инкапсулирует информацию о размещении текста, в том числе о выравнивании и межстрочном интервале. Следующий пример демонстрирует выравнивание текста по правому краю и по центру с использованием класса StringFormat. В этом практическом занятии мы создадим объект Font и с его помощью выполним прорисовку текста.

Прорисовка с использованием изображений

Изображения находят много применений в интерфейсе GDI+. Конечно, можно рисовать изображения в окнах приложения, но можно также создать кисть (TextureBrush), содержащую изображение, и рисовать формы, которые затем будут залиты этим изображением. На основе кисти TextureBrush можно создать перо и рисовать линии, используя выбранное изображение. Кисть TextureBrush можно передать при прорисовке текста, в результате чего текст будет прорисовываться с выбранным изображением. Класс Image определен в пространстве имен System. Drawing.

Иногда изображение, которое нужно создать, очень сложно и причудливо, и его прорисовка занимает значительное время даже на современных быстрых компьютерах. Наблюдение за тем, как графическое изображение "выползает" на экран по мере прорисовки — не самое приятное занятие. Примеры таких типов приложений — картографические приложения и сложные приложения CAD/CAM. Рассматриваемая технология вместо прорисовки непосредственно в окне использует прорисовку в изображении, по завершении которой выполняется прорисовка изображения в окне. Эта технология называется двойной буферизацией. Некоторые другие графические технологии применяют прорисовку по слоям, когда вначале выполняется прорисовка фона, затем прорисовка объектов поверх него и, наконец, прорисовка текста поверх объектов. Если эта прорисовка осуществляется непосредственно на экране, пользователи будут наблюдать эффект мерцания. Двойная буферизация устраняет его. Пример использования двойной буферизации будет рассмотрен немного позже.

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

Для объектов Image всегда вызывайте метод Dispose ().

Для создаваемых объектов Image важно вызывать метод Dispose () либо применять блоки using. В противном случае приложение может истощать ресурсы Windows.

Существует несколько возможных источников растровых изображений. Растровое изображение можно загрузить из файла, создать его из другого существующего изображения или же создать в виде пустого растрового изображения, поверх которого можно выполнять рисование. Изображение в файле может храниться в формате JPEG, GIF или BMP. Следующее практическое занятие демонстрирует чтение изображения из файла его рисование в окне.

Рисование с использованием текстурной кисти

Теперь создадим кисть TextureBrush на основе только что использованного изображения и рассмотрим три различных примера ее применения:

  • рисование эллипса;

  • создание пера;

  • прорисовка текста.

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

Двойная буферизация

Вспомните, что ситуация, когда прорисовка занимает слишком длительное время и пользователь вынужден долго дожидаться, пока нарисованное изображение появится на экране, может оказаться неприемлемой. Как уже было отмечено ранее, решением этой проблемы является выполнение рисования в изображение, а по завершении всех операций прорисовки — прорисовка всего изображения в окне.

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

Расширенные возможности GDI+

Мы лишь вскользь коснулись многих возможностей, предоставляемых интерфейсом GDI+. Существует множество других возможных его применений — значительно больше, чем можно рассмотреть в одной главе. Однако, чтобы придать этой главе завершенный вид, опишу несколько областей применения этих расширенных возможностей.

Отсечение

Отсечение важно в трех ситуациях. Во-первых, при вызове метода OnPaint () в сочетании с объектом Graphics событие передает прямоугольник отсечения. В простых подпрограммах рисования этому прямоугольнику отсечения можно не уделять особого внимания, но в очень сложных подпрограммах прорисовки, занимающих длительное время, время рисования можно уменьшить, проверяя этот прямоугольник до начала рисования. Ограничивающий прямоугольник любого графического изображения или фигуры, которые нужно нарисовать, известен. Если этот ограничивающий прямоугольник не пересекается с прямоугольником отсечения, операцию прорисовки можно пропустить.

На этом этапе метод OnPaint () должен быть вызван применительно к окну гистограммы, причем прямоугольник отсечения должен быть установлен в соответствии с областью. Теперь гистограмма должна была бы выполнить прорисовку тех участков своего окна, которые ранее располагались под перекрывающим его окном. Столбцы Cars и Trains перерисовки не требуют, и фактически, даже если бы метод OnPaint () попытался выполнить рисование в других областях окна, кроме открытой, он не смог бы этого сделать. Любые выполненные им операции рисования игнорировались бы.

Пространство имен System.Drawing.Drawing2D

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

Векторная графика — это технология, при которой программист вообще не обращается к пикселям. Вместо этого он записывает множество векторов, указывая такие операции, как "рисование от одной точки к другой "рисование прямоугольника в определенной позиции " и т. п. Затем разработчик может применять масштабирование, поворот и другие трансформации. В случае применения трансформаций все операции визуализируются в окне одновременно.

Это пространство имен включает перечисленные ниже классы.

  • Усовершенствованные кисти. Мы уже рассмотрели кисть LinearGradientBrush и вскользь затронули кисть PathGradientBrush. Существует также кисть HatchBrush, которая выполняет прорисовку в стиле штриховки, используя цвета переднего плана и фона.

  • Класс Matrix. Этот класс определяет геометрические преобразования. Он позволяет выполнять преобразования применительно к операциям рисования. Например, используя класс Matrix, можно нарисовать скошенный овал.

  • Класс GraphicsPath. Мы уже вскользь затрагивали этот класс. С его помощью можно определить сложный путь и выполнить рисование сразу всего пути.

Пространство имен System.Drawing. Imaging

Классы этого пространства имен предоставляют расширенную поддержку работы с изображениями, такую как работа с метафайлами. Метафайл описывает последовательность графических операций, которые могут быть записаны и впоследствии воспроизведены, и пространство имен System. Drawing. Imaging содержит ряд классов, которые позволяют расширить возможности GDI+ для обеспечения поддержки других форматов изображений.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]