Смекни!
smekni.com

Основы графического вывода (стр. 7 из 26)

void Cls_OnPaint (HWND hwnd)

{PAINTSTRUCT ps;

HPEN hpenOld, hpenRed, hpenBlue;

BeginPaint (hwnd, &ps);

// создаемперья:

hpenRed = CreatePen (PS_SOLID, 0, RGB (255,0,0));

hpenBlue = CreatePen (PS_SOLID, 0, RGB (0,0,255));

// выбираем его в контекст и запоминаем прежнее:

hpenOld = (HPEN)SelectObject (ps.hdc, (HGDIOBJ)hpenRed);

... // осуществляем рисование красным пером

// выбираем другое перо, причем запоминать предыдущее не надо – оно

// и так известно - это hpenRed

SelectObject (ps.hdc, (HGDIOBJ)hpenBlue);

... // осуществляем рисование синим пером

// освобождаем созданное перо из контекста (то есть выбираем первоначальное)

SelectObject (ps.hdc, (HGDIOBJ)hpenOld);

// иудаляемсозданные

DeleteObject ( (HGDIOBJ)hpenRed);

DeleteObject ( (HGDIOBJ)hpenBlue);

EndPaint (hwnd, &ps);}

Этот пример иллюстрирует все основные шаги по работе с создаваемыми объектами GDI. Однако в реальной жизни он используется не слишком часто — как правило при рисовании используется не один объект данного типа, а несколько. Это приводит к появлению большого количества переменных, как–то: hpen1, hpen2, ..., hpen100. Читаемость такого текста сравнительно невелика, да и вероятность запутаться остается высокой. Другое соображение — часто встречающееся приведение типов. В этом случае удобнее использовать макросы из windowsx.h. В третьих — сохранять исходный объект для восстановления его в контексте устройства не обязательно. Если существуют стандартные объекты данного типа, то вместо восстановления исходного можно перед удалением объектов выбрать в контекст любой стандартный того же типа (скажем, стандартных регионов или битмапов не определено — для них рекомендуется сохранять исходный).

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

void Cls_OnPaint (HWND hwnd)

{PAINTSTRUCT ps;

BeginPaint (hwnd, &ps);

// создаем красное перо и выбираем его в контекст;

// прежнее НЕ запоминаем:

SelectPen (ps.hdc, CreatePen (PS_SOLID, 0, RGB (255,0,0)));

... // осуществляем рисование красным пером

// создаем и выбираем синее перо;

// предыдущее - созданное нами красное - сразу уничтожаем

DeletePen (SelectPen (ps.hdc, CreatePen (PS_SOLID, 0, RGB (0,0,255))));

... // осуществляем рисование синим пером

// освобождаем синее перо из контекста и уничтожаем его

DeletePen (SelectPen (ps.hdc, GetStockPen (BLACK_PEN)));

EndPaint (hwnd, &ps);}

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

Более редкий случай — когда объекты создаются при обработке WM_CREATE и уничтожаются при обработке WM_DESTROY (или при запуске и завершении приложения). Этот способ используется редко, так как требуется описание дополнительных статических переменных и, кроме того, интенсивнее расходуются ресурсы GDI.

LRESULT WINAPI _export WinProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{PAINTSTRUCT ps;

static HPEN hpenRed; // хендлперадолженсохранятьсявпромежутке

// междуобработкойсообщений

switch (uMsg) {case WM_CREATE:

...

hpenRed= CreatePen (PS_SOLID, 0, RGB (255,0,0));

break;

case WM_PAINT:

BeginPaint (hWnd, &ps);

SelectPen (ps.hdc, hpenRed);

... // здесь мы используем выбранный объект

// теоретически, объект сохраняется и после уничтожения контекста -

// так что мы можем не заменять его на стандартный; правда так делать

// не рекомендуется[4]

EndPaint (hWnd, &ps);

break;

case WM_DESTROY:

DeletePen (hpenRed);

...

break;

...

default:

return DefWindowProc (hwnd, uMsg, wParam, lParam);}

return 0L;}

Довольно часто, если объект создается на все время жизни окна, вместо статической переменной используются связанные с окном данные: либо размещенные в структуре окна (см. функции GetWindowLong, SetWindowLong), либо списки свойств окна (Property) (об этом см. методы связывания данных с окном).

Отображение основных графических объектов

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

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

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

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

Задание цвета

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

Для обеспечения универсальности в GDI принято, что цвет обозначается с помощью 24х битового параметра типа COLORREF. Этот параметр реально соответствует одному двойному слову, самый старший байт которого не используется. Младшие три байта задают интенсивности трех основных компонент цвета — красной, зеленой и синей. Каждая компонента задается своим байтом, так что предельный диапазон ее изменения лежит от 0 до 255. Это позволяет задавать до 16 777 216 различных цветов.

Для удобного задания параметра типа COLORRREF используется специальный макрос RGB, который из трех по отдельности указанных компонент формирует одно двойное слово.

COLORREF RGB (byRed, byGreen, byBlue);

Конечно, Windows не всегда может использовать 24х битовый цвет непосредственно, так как аппаратура может поддерживать от 2 цветов (монохроматические устройства) до более чем 16 млн. цветов (режимы TrueColor и HighColor для современных видеоадаптеров). Вместо отображения указанного вами цвета, GDI будет подбирать ближайший цвет, который в состоянии воспроизвести данная аппаратура в данном режиме; такой цвет называется чистый (pure color).

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

В некоторых случаях GDI может использовать вместо чистого цвета смесь точек разных цветов, наиболее точно передающих желаемый цвет. Такой цвет называется смешанным (dithered color). Однако такая операция выполняется далеко не всегда. Так при выводе текста цвет символов будет чистым цветом; аналогично линии (за небольшим исключением) рисуются тоже чистым цветом.

Работа с отдельными пикселями

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

Собственно, для рисования точек существуют всего две функции:

СOLORREF GetPixel (hDC, nX, nY);

COLORREF SetPixel (hDC, nX, nY, crColor);

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

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

Если используемый пиксель находится вне области рисования, то обе функции возвращают -1, а если пиксель находится внутри области рисования, то возвращаемое значение соответствует текущему цвету пикселя. Для функции SetPixel оно может отличаться от заданного crColor, указывая примененный чистый цвет (если ваша аппаратура не может отобразить требуемый цвет). Этот прием иногда используется для проверки возможностей устройства, хотя эффективнее анализировать возможности устройства с помощью функции GetDeviceCaps.

Рисование линий

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

Текущее перо (Pen).

Определяет вид проводимой линии — толщину, цвет, стиль (сплошная или прерывистая). Перо является объектом GDI, поэтому при его использовании необходимо соблюдать правила работы с объектами GDI.

Текущая позиция пера (Pen Position).

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

Цвет фона (Background Color).

Заполняет промежутки между штрихами прерывистой линии (а также фон под символами выводимого текста или промежутки между линиями штрихованных кистей — об этом см. в обсуждении рисования заполненных фигур и обсуждении вывода текста).

Режим заполнения фона (Background Mode).

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