Основные инструменты графической подсистемы

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

К основным инструментам относятся:

перо (Pen) – отображение контурных примитивов («карандаш»);

кисть (Brush) – заполнение внутренних областей примитивов;

шрифт (Font) – отображение символов и строк;

битовая карта (Bitmap) – «готовые» растровые изображения.

Инструмент идентифицируется его описателем и создается соответствующей функцией вида Create..., которая возвращает этот описатель (NULL – признак ошибки). Количество создаваемых инструментов искусственно не ограничивается, но в любом контексте одновременно может быть активным только один инструмент каждого типа. Выбор инструмента отменить нельзя, но можно повторять его сколько угодно раз с любыми другими инструментами того же типа. Инструменты различного типа между собой взаимно независимы. Перед удалением инструмент следует дезактивировать, выбрав активным другой инстру­мент того же типа, например, сохраненный предыдущий.

Инструмент Pen

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

По умолчанию устанавливается одно из трех стандартных перьев, рисующих сплошные линии толщиной в единицу, выбранного цвета: BLACK­PEN – черное перо, WHITE_PEN – белое перо и NULL_PEN – пустое перо, которое ничего не рисует.

Определив переменную, например, hPen типа HPEN (описатель пера –handle to a pen): HPEN hPen; получить описатель одного из стандартных перьев, например белого пера, можно, вызывая функцию

hPen = GetStockObject (WHITE_PEN);

сделаем это перо текущим, вызвав функцию

SelectObject (hdc, hPen);

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

Все вышесказанное можно совместить в одной инструкции:

hPen = SelectObject (hdc, GetStockObject (WHITE_PEN));

Если это первый вызов, функция SelectObject возвращает описатель того пера, которое уже было выбрано в контексте устройства; текущим пером становится белое, а переменная hPen получает описатель предыдущего (по умолчанию – черного) пера, вернуться к которому можно, используя вызов

SelectObject (hdc, hPen);

Для создания пера используются функции CreatePen или CreatePen­Indirect (ExtCreatePen).

Функция CreatePen:

hPen = CreatePen (iPenStyle, iWidth, rgbColor);

параметр iPenStyle определяет стиль линии и может принимать одно из семи значений (семь стилей пера): PS_SOLID – сплошное, PS_DASH, PS_DOT, PS_DASHDOT, PS_DASHDOTDOT – штриховое, пунктирное и штрихпунктирное, PS_NULL – нерисующее (пустое) перо, PS_INSIDEFRAME – внутренняя обводка, в замкнутом контуре автоматически отступает внутрь в соответствии с толщиной линии.

Для стилей PS_SOLID, PS_NULL и PS_INSIDEFRAME параметр iWidth задает ширину пера, при iWidth = 0 – перо шириной в один пиксел.

Параметр rgbColor – цвет пера; для перьев всех стилей, кроме PSINSIDE­FRAME, преобразуется в ближайший чистый цвет, стиль PS_INSIDE­FRAME позволяет использовать полутона при ширине больше 1.

Можно создать перо, определив структуру LOGPEN logpen («логи­ческое перо» – logical pen), содержащую поля:

lopnStyle (UINT) – стиль пера;

lopnWidth (POINT) – ширина пера в логических единицах измерения;

lopnColor (COLORREF) – цвет пера.

Затем создаем перо, передавая адрес структуры в функцию CreatePenIndirect:

hPen = CreatePenIndirect (&logpen);

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

GetObject (hPen, sizeof(LOGPEN), &logpen);

Функции CreatePen и CreatePenIndirect не требуют описателя контекста устройства. Эти функции создают логические перья, которые никак не связаны с контекстом устройства до тех пор, пока не вызвать функцию SelectObject.

Рассмотрим метод создания, выбора и удаления перьев. Предположим, в программе используются два пера – красное шириной 3 и черное точечное. Определим переменные для хранения описателей этих перьев:

static HPEN hPen1, hPen2;

В процессе обработки сообщения WM_CREATE создадим перья:

hPen1 = CreatePen (PS_SOLID, 3, RGB (255, 0, 0));

hPen2 = CreatePen (PS_DOT, 0, 0);

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

SelectObject (hdc, hPen2);

[функции рисования линий]

SelectObject (hdc, hPen1);

[другие функции рисования линий]

В процессе обработки сообщения WM_DESTROY удалить их:

DeleteObject (hPen1);

DeleteObject (hPen2);

Можно создать перья в процессе обработки сообщения WM_PAINT и удалить их после вызова EndPaint.

Можно объединить вызовы функций CreatePen и SelectObject в одну инструкцию:

SelectObject (hdc, CreatePen (PS_DASH, 0, RGB (255, 0, 0)));

Удалить перо можно, выбрав стандартное перо BLACK_PEN в контекст устройства и удаления значения, возвращаемого функцией SelectObject:

DeleteObject (SelectObject (hdc, GetStockObject (BLACK_PEN)));

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

hPen = SelectObject (hdc, CreatePen (PS_DASH, 0, RGB (255, 0, 0)));

Если это первый вызов, hPen – описатель стандартного черного пера, мы можем теперь выбрать его в контекст устройства и удалить созданное перо (описатель, возвращаемый вызовом функции SelectObject) в одной инструкции:

DeleteObject (SelectObject (hdc, hPen));

Инструмент Brush

Объект кисть – это битовый образ, свойства которого распространяются в горизонтальном и вертикальном направлениях при закрашивании области.

Имеется шесть стандартных (Stock) кистей: WHITE_BRUSH – белая, LTGRAY_ BRUSH – светло-серая, GRAY_ BRUSH – серая, DKGRAY_ BRUSH – темно-серая, BLACK_ BRUSH – черная и NULL_ BRUSH (HOLLOW) – пустая кисть.

Выбрать одну из стандартных кистей в контекст устройства можно аналогично выбору пера, определив переменную типа описателя кисти HBRUSH hBrush; получаем описатель кисти, например, GRAY_BRUSH:

hBrush = GetStockObject (GRAY_BRUSH);

Выберем эту кисть в контекст устройства:

SelectObject (hdc, hBrush);

Теперь внутренняя область рисуемых фигур будет закрашиваться серым.

Если необходимо нарисовать фигуру без рамки, выберем перо NULL_PEN:

SelectObject (hdc, GetStockObject (NULL_PEN));

А если только контур фигуры без закрашивания внутренней области, выберем кисть NULL_BRUSH:

SelectObject (hdc, GetStockObject (NULL_BRUSH));

Для создания сплошной (Solid) логической кисти:

hBrush = CreateSolidBrush (rgbColor);

Для создания штриховой (Hatch) кисти, состоящей из горизонтальных, вертикальных или диагональных линий:

hBrush = CreateHatchBrush (iHatchStyle, rgbColor);

параметр iHatchStyle – стиль штриховки: HS_HORIZONTAL, HS_VERTICAL, HS_BDIAGONAL – диагональная слева направо вверх; HS_FDIAGONAL – диагональная слева направо вниз; HS_CROSS – прямая сетка; HS_DIAG­CROSS – диагональная сетка; в обеих функциях rgbColor – цвет штриховых линий.

Промежутки между штриховыми линиями закрашиваются в соответствии с режимом и цветом фона, если режим фона – OPAQUE, то цвет фона используется для закрашивания промежутков между штриховыми линиями, если режим фона – TRANSPARENT, то промежутки между штриховыми линиями не зарисовываются.

Можно создавать кисти, основанные на битовых шаблонах, используя функцию CreatePatternBrush:

hBrush = CreatePatternBrush (hBitmap);

Функция, включающая три рассмотренные ранее функции, строящие кисти:

hBrush = CreateBrushIndirect (&logbrush);

переменная logbrush – структура типа LOGBRUSH («логическая кисть» – logical brush), содержащая поля:

UINT lbStyle – стиль кисти: BS_SOLID – сплошная; BS_HOLLOW, BS_NULL – «пустая» (невидимая); BS_HATCHED – штрихованная; BS_PATTERN, BS_PATTERN8X8 – задаются битовой картой; BS_DIB­PATTERN, BS_DIBPATTERN8X8, BS_DIBPATTERNPT – задаются битовой картой DIBWindows 95 размер шаблона ограничен 8´8 точек);

COLORREF lbColor – цвет кисти, для пустой или «шаблонной» кисти игнорируется, для кистей с DIB-шаблоном младшее слово определяет, следует ли интерпретировать его цвета как заданные цветовыми компонентами (DIB_RGB_COLORS) или как палитровые (DIB_PAL_COLORS).

Значение поля lbStyle определяет, как интерпретируются другие поля:

lbStyle lbColor lbHatch
BS_SOLID Цвет кисти Игнорируется
BS_HOLLOW Игнорируется Игнорируется
BS_HATCHED Цвет штриховых линий Стиль штриховки
BS_PATTERN Игнорируется Описатель битового шаблона

Получить описатель логической кисти: SelectObject (hdc, hBrush);

Удалить созданную кисть: DeleteObject (hBrush);

Получить информацию о кисти:

GetObject (hBrush, sizeof (LOGBRUSH), &logbrush);

Инструмент Font

Более сложным инструментом является шрифт. Все символы в оконном интерфейсе формируются в соответствии с одним из зарегистрированных в системе шрифтов.

Физический шрифт – файл (образ в памяти) с описанием начертаний всех известных в данном шрифте символов. Логический шрифт – объект GDI, характеризуемый как физическим шрифтом, так и его конкретными характеристиками. Он же является и инструментом, отвечающим за формирование символов.

Для создания логического шрифта используется функция

HFONT CreateFont (int nHeight, int nWidth, int nEscapement,

int nOrientation, int fnWeight, DWORD fdwItalic, DWORD fdwUnderline,

DWORD fdwStrikeOut, DWORD fdwCharSet,

DWORD fdwOutputPrecision, DWORD fdwClipPrecision,

DWORD fdwQuality, DWORD fdwPitchAndFamily,

LPCTSTR lpszFace);

возвращающая описатель созданного инструмента (NULL – ошибка), параметры:

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

nWidth – «приблизительная» ширина шрифта, если 0 – стандартная для выбранного основного размера;

nEscapement – направление вывода строки символов (угол между базовой линией и горизонтальной осью по часовой стрелке в десятых долях градуса);

nOrientation – ориентация отдельного символа (исчисление аналогично предыдущему параметру, в Windows 9x ориентация символов и направление вывода должны совпадать);

fnWeight – толщина символов в условных единицах от 0 до 1000, например: 0 (FW_DONTCARE) – стандартный (по умолчанию), 400 (FW_NORMAL, FW_REGULAR) – стандартная толщина, обычный шрифт, 700 (FW_BOLD) – жирный, выделенный, и т.д.;

fdwItalic, fdwUnderline, fdwStrikeOut – флаги, указывающие, является ли шрифт наклонным, подчеркнутым или перечеркнутым;

fdwCharSet – тип символьного набора: DEFAULT_CHARSET, ANSI_CHARSET, OEM_CHARSET и т.д. (национальные наборы символов);

fdwOutputPrecision – «точность вывода» символов, фактически предпочтение типа шрифта при наличии альтернативного выбора: OUT_DEFAULT_PRECIS, OUT_­DEVICE_PRECIS и т.д.;

fdwClipPrecision – точность отсечения;

fdwQuality – качество вывода;

fdwPitchAndFamily – комбинация по «ИЛИ» двух групп параметров, действующих при неопределенном имени шрифта:

– питч (шаг символов) – 2 младших бита: DEFAULT_PITCH, FIXED_PITCH, VARIABLE_PITCH;

– «семейство» шрифта – 4 старших бита: FF_DONTCARE – по умолчанию, FF_MODERN – моноширинные шрифты, FF_ROMAN – «книжные» с засечками (переменный шаг), FF_SWISS – без засечек (переменный шаг), FF_SCRIPT – «рукописные» и курсивные;

lpszFace – имя шрифта, обычно совпадает с именем его дискового файла; если NULL – система подбирает шрифт, наиболее отвечающий заданным требованиям, иначе явно указанный шрифт перекрывает их.

Функция CreateFontIndirect (const LOGFONT* lplgFont); использует в качестве аргумента структуру с полями аналогичного назначения.

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

COLORREF SetTextColor (hdc, crColor);

COLORREF GetTextColor (hdc);

Базовой функцией вывода символа является

BOOL TextOut (hdc, nXStart, nYStart, lpString, cbString);

Позицией символа считается верхний левый угол его знакоместа.

 

Закрашивание пустот

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

COLORREF SetBkColor(hdc, crColor);

COLORREF GetBkColor(hdc);

первая из которых устанавливает новый, а вторая – получает текущий фоновые цвета; признак ошибки – значение CLR_INVALID. Функция SetBkColor возвращает значение предыдущего цвета.

Функции

int SetBkMode (hdc, iBkMode);

int GetBkMode (hdc);

соответственно устанавливают новый или определяют текущий режим фона, 0 – признак ошибки; параметр int iBkMode – режим фона, по умолчанию белый (OPAQUE) – сначала рисуется фон, затем передний план; режим TRANSPARENT – отменяет заполнение пустот, в этом случае цвет фона игнорируется и пустоты заполняться не будут.

 

Рисование линий и кривых

Теоретически все, что необходимо драйверу устройства для рисования, это функции SetPixel и GetPixel.

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

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

Функция

LineTo (hdc, xEnd, yEnd);

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

Для рисования отрезка из точки (xStart, yStart) в точку (xEnd, yEnd) необходимо сначала для установки (изменения) текущего положения пера использовать функцию

MoveToEx (hdc, xStart, yStart, &pt);

pt – структура типа POINT, определяющая предыдущую позицию. После чего, используя LineTo (hdc, xEnd, yEnd); будет нарисован отрезок до точки (xEnd, yEnd), не включая ее в отрезок, и текущее положение пера установится в точку (xEnd, yEnd).

Узнать текущее положение пера можно с помощью функции

GetCurrentPositionEx (hdc, &pt);

Следующий фрагмент программы отображает в рабочей области окна сетку с интервалом в 100 пикселей, начиная от левого верхнего угла:

GetClientRect(hwnd, &rect); (см. Пример 1, Сообщение WM_PAINT);

for (x = 0; x < rect.right; x += 100) {

MoveToEx (hdc, x, 0, NULL);

LineTo (hdc, x, rect.bottom);

}

for (y = 0; y < rect.bottom; y += 100) {

MoveToEx (hdc, 0, y, NULL);

LineTo (hdc, rect.right, y);

}

Когда необходимо соединить отрезками массив точек pt размером cPoint, можно использовать функцию

Polyline (hdc, pt, cPoint);

Например, определим массив из 5 точек (10 значений), описывающих контур прямоугольника:

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

используем функцию Polyline (hdc, pt, 5). Следует обратить внимание, что первая и последняя точки совпадают.

Функция Polyline не учитывает и не изменяет текущее положение пера. Функция PolylineTo использует текущее положение для начальной точки и устанавливает текущее положение в конец последнего нарисованного отрезка. Предыдущий пример будет выглядеть

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

PolylineTo (hdc, pt + 1, 4);

Для рисования дуги эллипса (рис. П 6.2) используется функция

Arc (hdc, x1, y1, x2, y2, xStart, yStart, xEnd, yEnd);

в которой значения (x1, y1) задают левый верхний угол, (x2, y2) – правый нижний; (xStart, yStart) – начало дуги; (xEnd, yEnd) – конец дуги.

Рис. П 6.2. Фигура, нарисованная с использованием функции Arc

 

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

PolyBezier (hdc, pt, iCount);

PolyBezierTo (hdc, pt, iCount);

pt – массив структур типа POINT. В функции PolyBezier первые четыре точки идут в таком порядке: начальная точка, первая контрольная точка, вторая контрольная точка, конечная точка кривой Безье. Каждая следующая кривая Безье требует три новых точки, поскольку начальная точка следующей кривой есть конечная точка предыдущей и т.д. Параметр iCount = 1+3*n – равен единице плюс три, умноженное на число отображаемых кривых.