Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

лекции / Shchupak_Yu._Win32_API_Razrabotka_prilozheniy_dlya_Windows

.pdf
Скачиваний:
0
Добавлен:
11.02.2026
Размер:
13.15 Mб
Скачать

Общие операции с графическими объектами

81

 

 

 

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

Режим смешивания фона и цвет фона графического элемента

Некоторые графические примитивы GDI содержат пикселы двух видов: основ ные (foreground) и фоновые (background). Например, при выводе текстового сим вола пикселы, образующие глиф1 символа, считаются основными, а остальные пикселы из прямоугольной области вокруг символа2 считаются фоновыми. При выводе пунктирных линий пикселы отрезков считаются основными, а пикселы промежутков — фоновыми. Для шаблона штриховой кисти штриховые линии ри суются основными пикселами, а промежутки между линиями — фоновыми пик селами.

Фоном графического элемента будем в дальнейшем считать совокупность его фоновых пикселов. Цвет фона графических элементов (background color) являет ся атрибутом контекста устройства, который можно устанавливать с помощью функции SetBkColor.

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

Если основные пикселы графического элемента выводятся всегда, то вывод фоновых пикселов зависит от еще одного атрибута контекста устройства, кото рый называется режимом смешивания фона (background mix mode)3.

По умолчанию режим смешивания фона имеет значение OPAQUE4. Этот режим указывает, что Windows выводит фон графического элемента (цветом background color) поверх фона окна. Разработчик может изменить режим смешивания фона, запретив отображение фона графических элементов. Для этого нужно вызвать функ цию SetBkMode, передав ей в качестве второго параметра константу TRANSPARENT5.

Чтобы узнать, какой режим смешивания фона является текущим, следует выз вать функцию GetBkMode.

Общие операции с графическими объектами

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

1 Глиф (glyph) — графическая форма символа.

2 Эту область иногда называют ячейкой символа.

3 В литературе встречается другой перевод этого термина — режим заполнения фона.

4 Непрозрачный режим смешивания фона.

5 Прозрачный режим смешивания фона.

82

Глава 2. GDI — графический интерфейс устройства

 

 

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

1.Создать графический объект.

2.Выбрать созданный объект в контекст устройства.

3.Вызвать графическую функцию, работающую с объектом.

4.Удалить объект из контекста устройства, вернув предшествующий объект.

5.Уничтожить объект.

Для создания GDI объектов предназначены соответствующие функции Create…, которые в случае успешного завершения возвращают дескриптор объекта.

Выбор объекта в контекст устройства осуществляется при помощи функции SelectObject (палитры выбираются с помощью функции SelectPalette). Функция SelectObject имеет следующий прототип:

HGDIOBJ SelectObject(

 

HDC hdc,

// дескриптор контекста устройства

HGDIOBJ hgdiobj

// дескриптор GDI-объекта

);

 

В результате ее выполнения новый объект hgdiobj заменяет предшествующий объект того же типа в контексте устройства hdc.

Функция возвращает дескриптор предшествующего объекта. Для корректной работы приложение должно запомнить этот дескриптор и после окончания рисо вания с новым объектом (шаг 3) вернуть в контекст устройства предшествующий объект (шаг 4).

Для уничтожения объекта, ставшего ненужным, вызывается функция Delete Object.

Следует отметить, что не всегда для создания GDI объекта нужно вызывать соответствующую функцию типа Create…. В системе имеется набор предопреде ленных (стандартных) графических объектов, и если параметры предопределен ного объекта (перо, кисть, шрифт и т. д.) подходят для решаемой задачи, то при ложение может получить такой объект с помощью функции GetStockObject. После окончания работы с предопределенным объектом его не нужно удалять при по мощи функции DeleteObject. Предопределенные объекты существуют в системе постоянно.

Линии и кривые

Теоретически, рисование любой линии можно было бы свести к многократному вызову функции SetPixel с соответствующим изменением координат x и y. Однако, во первых, это не очень удобно для программиста, а во вторых, подобный способ рисования был бы очень медленным. Значительно более эффективной в графических системах является реализация функций рисования отрезков и дру гих сложных графических операций на уровне драйвера устройства, который со держит код, оптимизированный для этих операций. Поэтому Win32 GDI предос тавляет соответствующий набор графических функций.

Линии и кривые

83

 

 

 

Рисование отрезков

Любая линия рисуется в Windows с использованием графического объекта, назы ваемого пером. Контекст устройства содержит перо по умолчанию — сплошное перо черного цвета толщиной 1 пиксел. Многие графические функции начинают рисование с так называемой текущей позиции пера.

Текущая позиция пера

Текущая позиция пера является одним из атрибутов контекста устройства. Она определяется значением типа POINT. По умолчанию текущая позиция пера уста новлена в точку (0, 0). Если нужно изменить текущую позицию, вызывается функ ция MoveToEx:

BOOL MoveToEx(

 

HDC hdc,

// дескриптор контекста устройства

int X,

// x-координата новой текущей позиции

int Y,

// y-координата новой текущей позиции

LPPOINT lpPoint

// предыдущая позиция пера

);

 

Если предыдущая позиция пера вас не интересует, передавайте последнему параметру значение NULL. В случае успешного завершения функция возвращает ненулевое значение.

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

GetCurrentPositionEx(hDC, &pt);

Результат выполнения функции помещается в переменную pt типа POINT.

Рисование прямой линии

Для создания прямой линии используется функция LineTo:

BOOL LineTo(

 

 

 

 

HDC hdc,

// дескриптор контекста устройства

int xEnd,

//

x-координата

конечной

точки

int yEnd

//

y-координата

конечной

точки

);

 

 

 

 

Эта функция рисует отрезок, начиная с точки, в которой находится текущая позиция пера, до точки (xEnd, yEnd), не включая последнюю точку в отрезок. Ко ординаты конечной точки задаются в логических единицах. Если функция завер шается успешно, то она возвращает ненулевое значение, а текущая позиция пера устанавливается в точку (xEnd, yEnd).

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

{

POINT pt[5] = {{100,100}, {200,100}, {200,200}, {100,200}, {100,100}};

MoveToEx(hDC, pt[0].x, pt[0].y, NULL); for (int i = 0; i < 5; ++i)

LineTo(hDC, pt[i].x, pt[i].y);

}

84

Глава 2. GDI — графический интерфейс устройства

 

 

Вставьте этот фрагмент в код программы Hello2 (глава 1) после вывода текста функцией DrawText, чтобы можно было быстро посмотреть, как он работает. На ружные фигурные скобки в данном фрагменте делают возможным объявление локальных переменных внутри блока оператора switch.

Рисование связанных отрезков (ломаной линии)

Последовательность связанных отрезков гораздо проще нарисовать с помощью функции Polyline:

BOOL Polyline(HDC hdc, CONST POINT* lppt, int cPoints);

Второй параметр здесь — это адрес массива точек, а третий — количество точек.

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

{

POINT pt[5] = {{100,100}, {200,100}, {200,200}, {100,200}, {100,100}};

Polyline(hDC, pt, 5);

}

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

Функция PolylineTo предназначена для рисования последовательности связан ных отрезков:

BOOL PolylineTo(HDC hdc, CONST POINT* lppt, DWORD cPoints);

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

MoveToEx(hDC, pt[0].x, pt[0].y, NULL);

PolylineTo(hDC, pt + 1, 4);

Функция PolyPolyline позволяет нарисовать несколько ломаных линий за один вызов. Она имеет следующий прототип:

BOOL PolyPolyline(

 

HDC hdc,

// дескриптор контекста устройства

CONST POINT* lppt,

// массив точек для нескольких ломаных линий

CONST DWORD* lpPolyPoints, // массив с числом точек в каждой ломаной

DWORD cCount

// количество ломаных линий

);

 

Эта функция не использует и не изменяет текущей позиции пера.

Äóãè

Дуги в Windows рисуются как часть эллипса. Размеры и расположение эллипса определяются ограничивающим прямоугольником. Ограничивающий прямоуголь ник задается координатами левой верхней и правой нижней вершин. Если обозна чить эти координаты как (xLeft, yTop) и (xRight, yBottom), тогда центром эллипса бу дет точка (x0, y0), где x0 = xLeft + (xRight – xLeft)/2, а y0 = yTop + (yBottom – yTop)/2.

Для рисования дуг предназначены функции Arc, ArcTo и AngleArc. Первые две функции имеют одинаковый набор параметров, поэтому рассмотрим прототип функции Arc:

Линии и кривые

85

 

 

 

BOOL Arc(HDC hdc, int xLeft, int yTop, int xRight, int yBottom, int xStart, int yStart, int xEnd, int yEnd);

Параметры со второго по пятый задают вершины ограничивающего прямоуголь ника. Начало и конец дуги определяются начальным и конечным углами, которые задаются косвенно через две дополнительные точки с координатами (xStart, yStart) и (xEnd, yEnd). Начало дуги — это пересечение эллипса с лучом, который начинает ся в центре эллипса и проходит через точку (xStart, yStart). Конец дуги — это пересе чение эллипса с лучом, который начинается в центре эллипса и проходит через точ ку (xEnd, yEnd). Все координаты задаются в логических единицах.

В Windows 95/98 дуга рисуется против часовой стрелки. В Windows NT/2000 направление дуги определяется соответствующим атрибутом в контексте устрой ства, значение которого можно получить вызовом функции GetArcDirection или ус тановить вызовом функции SetArcDirection. Когда вы пользуетесь функцией SetArcDirection, в качестве второго параметра можно передать одно из значений: AD_COUNTERCLOCKWISE, устанавливающее режим рисования против часовой стрел ки, либо AD_CLOCKWISE, устанавливающее режим рисования по часовой стрелке. По умолчанию в контексте устройства используется значение AD_COUNTERCLOCKWISE.

На рис. 2.5 показана дуга, нарисованная при помощи вызова функции

Arc(hDC, 100, 100, 400, 300, 350, 50, 50, 300);

Рис. 2.5. Дуга, нарисованная с использованием функции Arc (направление по умолчанию — против часовой стрелки)

Если перед обращением к функции Arc вызвать функцию

SetArcDirection(hDC, AD_CLOCKWISE);

то функция Arc нарисует дугу, показанную на рис. 2.6.

Функция Arc не использует текущую позицию пера и не обновляет ее. Функция ArcTo отличается от функции Arc тем, что она проводит линию из те

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

Функция AngleArc, поддерживаемая только в Windows NT/2000, существенно отличается от двух предыдущих функций способом определения рисуемой дуги, что выражено и в ее прототипе:

BOOL AngleArc(HDC hdc, int X, int Y, DWORD radius, FLOAT startAngle, FLOAT sweepAngle);

86

Глава 2. GDI — графический интерфейс устройства

 

 

Рис. 2.6. Дуга, нарисованная с использованием функции Arc (направление по часовой стрелке)

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

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

Функция AngleArc, так же как и ArcTo, проводит линию из текущей позиции пера в заданную начальную точку дуги, после чего рисует дугу и перемещает те кущую позицию пера в конечную точку дуги.

Кривые Безье

В дополнение к рисованию кривых, являющихся частью эллипса, Windows по зволяет рисовать нерегулярные кривые, называемые кривыми Безье. Кривая Бе зье (сплайн Безье) — это кубическая кривая, положение и кривизна которой за даются четырьмя определяющими точками p1, p2, p3 и p4. Точка p1 является стартовой точкой, точка p4 — конечной точкой. Точки p2, p3 называются конт рольными точками — именно они определяют форму кривой, играя роль «магни тов», оттягивающих линию от прямой, соединяющей p1 и p4. Пример кривой Бе зье показан на рис 2.7.

Рис. 2.7. Кривая Безье

Перья

87

 

 

 

Математический аппарат для рисования таких кривых предложил П. Безье (P.Bezier) в ходе разработки систем автоматизированного проектирования для компаний «Ситроен» и «Рено». Впоследствии оказалось, что кривые Безье — очень удобный инструмент для рисования кривых в компьютерной графике.

Win32 GDI содержит две функции, позволяющие рисовать набор связанных кривых Безье за один вызов:

BOOL PolyBezier(HDC hdc, CONST POINT* lppt, DWORD cPoints);

BOOL PolyBezierTo(HDC hdc, CONST POINT* lppt, DWORD cCount);

Для рисования n кривых функция PolyBezier получает адрес массива lppt, содержащего 3n + 1 точек, при этом параметр cPoints должен быть равен 3n + 1. Первые четыре точки, lppt[0], lppt[1], lppt[2], lppt[3], задают первую кри вую. Точка lppt[3] вместе со следующими тремя точками определяет вторую кривую и т. д. Функция PolyBezier не использует текущую позицию пера и не обновляет ее.

В отличие от PolyBezier, функция PolyBezierTo получает 3n точек в массиве lppt, при этом параметр cCount должен быть равен 3n. Функция рисует первую кривую, начиная с текущей позиции пера, до позиции, заданной третьей точкой, исполь зуя первые две точки в качестве контрольных. Каждая последующая кривая ри суется так же, как и функцией PolyBezier. По окончании рисования текущая пози ция пера переводится в последнюю точку из массива lppt.

Более подробную информацию об использовании кривых Безье можно найти в издании [6].

Перья

Любая функция рисования линий и кривых, а также контуров замкнутых фигур использует перо (pen), выбранное в контексте устройства в данный момент. Если вы не выбирали никакого пера, то используется перо по умолчанию BLACK_PEN. Оно рисует сплошные черные линии толщиной 1 пиксел независимо от режима отображения. Возможно, вы захотите большего разнообразия при рисовании ли ний, например, захотите придать линиям разный цвет, разную толщину и даже разный стиль!

Для удовлетворения таких запросов Win32 GDI позволяет создавать объекты логических перьев. Логическое перо представляет собой описание требований к перу со стороны приложения. Эти требования могут в каких то деталях и не соответствовать тому, как будут выводиться линии на поверхности физического устройства. Драйвер графического устройства может поддерживать собственные структуры данных, определяющие реализацию логического пера, — такие внут ренние объекты называются физическими перьями.

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

Дескрипторы объектов GDI описываются общим типом HGDIOBJ; для дескрип торов логических перьев зарезервирован тип HPEN (handle to a pen). Следует заме тить, что тип HGDIOBJ определен в заголовочных файлах Windows как указатель на тип void, а HPEN и другие специализированные типы указателей — как указатели

88

Глава 2. GDI — графический интерфейс устройства

 

 

на совершенно разные структуры. Таким образом, типом HPEN можно заменить тип HGDIOBJ, но попытка использования HGDIOBJ вместо HPEN требует обязатель ного преобразования типа.

Итак, объект пера с дескриптором hPen объявляется следующим образом:

HPEN hPen;

Значение дескриптора получают вызовом соответствующей функции. Вид вы зываемой функции зависит от типа пера.

Объект логического пера, как и другие объекты GDI, поглощает ресурсы опера ционной системы. Следовательно, когда необходимость в нем отпадает, рекоменду ется исключить его из контекста устройства и удалить функцией DeleteObject1.

Стандартные перья

В Win32 GDI определено четыре типа стандартных перьев, которые приведены в табл. 2.4.

Таблица 2.4. Стандартные перья

Индекс пера

Описание

Примечание

 

 

 

BLACK_PEN

Сплошное черное перо толщиной

Установлено по умолчанию

 

1 пиксел

 

WHITE_PEN

Сплошное белое перо толщиной

 

 

1 пиксел

 

NULL_PEN

Пустое перо (ничего не рисует)

Может использоваться для вывода

 

 

фигур без внешнего контура

DC_PEN

Перо DC — сплошное перо толщи-

Реализовано только в Windows 2000

 

ной 1 пиксел. По умолчанию оно

 

 

имеет черный цвет. Этот цвет

 

 

может быть изменен функцией

 

 

SetDCPenColor

 

 

 

 

Для получения дескриптора стандартного объекта нужно вызвать функцию GetStockObject, передав ей индекс стандартного объекта, как показано в следую щей инструкции:

hPen = (HPEN)GetStockObject(WHITE_PEN);

Функция GetStockObject возвращает значение типа HGDIOBJ, поэтому для кор ректного присваивания выполняется преобразование типа.

Получив значение дескриптора, нужно выбрать объект пера в контекст уст ройства:

SelectObject(hDC, hPen);

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

Вместо того чтобы определять переменную hPen, можно совместить вызовы

GetStockObject и SelectObject в одной инструкции:

SelectObject(hDC, GetStockObject(WHITE_PEN));

1 Стандартные перья удалять не нужно.

Перья

89

 

 

 

Хорошим стилем считается сохранение предшествующего дескриптора объекта и возврат его в контекст устройства после завершения операций рисования с но вым объектом, например, как показано в следующем фрагменте кода:

HPEN hOldPen = (HPEN)SelectObject(hDC, GetStockObject(WHITE_PEN));

//... рисуем с пером WHITE_PEN

//возврат в контекст устройства предыдущего пера SelectObject(hDC, hOldPen);

Стандартное перо DC, возвращаемое вызовом GetStockObject(DC_PEN), является представителем «нового поколения» объектов GDI. Обычные объекты GDI «на мертво» фиксируются при создании. Их можно использовать, можно удалять, но их нельзя изменять. Если вам понадобился слегка отличающийся объект GDI, приходится создавать новый объект и удалять старый. Это может приводить к снижению быстродействия в определенных ситуациях.

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

COLORREF SetDCPenColor(HDC hdc, COLORREF crColor);

Параметр crColor задает новый цвет пера DC. Функция возвращает предшеству ющий цвет пера DC.

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

{

HGDIOBJ hOld = SelectObject(hDC, GetStockObject(DC_PEN)); for (int i = 0; i < 256; ++i) {

SetDCPenColor(hDC, RGB(255-i, 128, i)); MoveToEx(hDC, 10, i+10, NULL); LineTo(hDC, 266, i+10);

}

SelectObject(hDC, hOld);

}

Вставьте этот фрагмент в код программы Hello2 (глава 1) после вывода текста функцией DrawText, чтобы посмотреть на его работу1.

Компиляция модифицированной программы, скорее всего2, будет неудачной. Компилятор сообщит, что идентификатор DC_PEN ему неизвестен. Чтобы испра вить эту ошибку, необходимо добавить в самом начале файла (еще до директивы

#include <windows.h>) следующую строку:

#define _WIN32_WINNT 0x500

После этого компиляция должна пройти успешно.

Простые перья

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

1Наружные фигурные скобки в данном фрагменте делают возможным объявление локальных пере менных внутри блока оператора switch.

2 При использовании компилятора Visual C++ 6.0.

90

Глава 2. GDI — графический интерфейс устройства

 

 

объекты логического пера. Простые перья создаются вызовом функции CreatePen

или CreatePenIndirect.

Функция CreatePen имеет следующий прототип:

HPEN CreatePen(int fnPenStyle, int nWidth, COLORREF crColor);

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

Таблица 2.5. Стили простых перьев

Стиль

Вид линии

Выравнивание

Ограничения на толщину

 

 

 

 

PS_SOLID

Сплошная

По центру

Íåò

PS_DASH

Пунктирная

По центру

nWidth <= 1

PS_DOT

Точечная

По центру

nWidth <= 1

PS_DASHDOT

Пунктирно-точечная

По центру

nWidth <= 1

PS_DASHDOTDOT

Отрезок и две точки

По центру

nWidth <= 1

PS_NULL

Не рисуется

 

 

PS_INSIDEFRAME

Сплошная

Внутри контура

nWidth > 1

 

 

 

 

На рис. 2.8 показано, как выглядят линии этих стилей. Все линии имеют тол щину 1 пиксел.

Рис. 2.8. Стили простых перьев

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

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

Параметру nWidth можно передать нулевое значение. В этом случае будет ис пользоваться перо толщиной 1 пиксел независимо от режима отображения.

Если задать точечный или пунктирный стиль (PS_DASH, PS_DOT, PS_DASHDOT, PS_DASHDOTDOT) с физической толщиной более единицы, то Windows будет ис пользовать перо со стилем PS_SOLID. Таким образом, стилевые (прерывистые) ли нии можно рисовать только с толщиной 1 пиксел. Обратите внимание на то, что точки в стилевых линиях изображаются тремя пикселами.