Перемещение (translation). Перемещение осуществляется добавлением постоянных величин к координатам x (коэффициент Dx) и y (коэффициент Dy). При этом коэффициенты M11 и M12 должны быть равны 1, а M12 и M21 равны 0. Формула в матричном виде раскрывается следующим образом:
x’ = x + Dxy’ = y + Dy |
Масштабирование (scaling) и зеркальное отражение (reflection). Обе эти операции выполняются одним способом, для их задания необходимо указать масштабные коэффициенты M11 (масштаб по оси X) и M22 (масштаб по оси Y).
Отрицательные значения коэффициентов соответствуют перевернутому виду. Для получения зеркального отражения задают коэффициенты равными -1.
x’ = x*M11y’ = y*M22 |
`Поворот (rotation). Для задания коэффициентов необходимо узнать угол поворота a. Если он известен, то коэффициенты M11 и M22 оба будут равны cos a, коэффициент M12 будет равен sin a, а коэффициент M21 = ‑sin a. То есть для задания поворота необходимо вычислить коэффициенты M11 и M12, а коэффициенты M22 и M21 получаются из уже вычисленных: M22 = M11 и M21 = -M12.
x’ = x*M11 - y*M21 = x*cosa - y*sinay’ = x*M12 + y*M22 = x*sina + y*cosa |
Сдвиг (shear). Для задания сдвига (описание неперпендикулярных осей координат) необходимо задать два коэффициента M12 и M21, задающих величину сдвига осей. При этом коэффициенты M11 и M22 оба равны 1.
x’ = x+ y*M21y’ = x*M12 + y |
Внимание! Помимо возможности использовать функции для изменения матрицы преобразований режим GM_ADVANCED отличается от GM_COMPATIBLE рисованием прямоугольников и эллипсов — нижняя и правая границы в этом режиме включаются в рисуемый объект и рисованием дуг — они всегда рисуются против часовой стрелки.
В этом вопросе в документации встречаются некоторые неточности. Так, например, обычно утверждается, что для использования функций по заданию или изменению матрицы преобразований глобальных координат необходимо работать в расширенном режиме (GM_ADVANCED), а обратный переход от GM_ADVANCED к GM_COMPATIBLE осуществляется только при стандартной матрице преобразований. Вторая часть утверждений не совсем корректна.
Режим GM_ADVANCED отличается от GM_COMPATIBLE тем, что он позволяет изменять матрицу преобразований плюс включение нижней и правой границ ограничивающего прямоугольника в рисуемый объект, плюс рисование дуг всегда против часовой стрелки плюс некоторые особенности отображения растровых шрифтов. Сама матрица преобразований используется постоянно, вне зависимости от текущего режима, в то время как в режиме GM_COMPATIBLE вы ее просто не можете изменять. Переключение из расширенного режима в совместимый вовсе не запрещается и корректно выполняется системой.
Объекты GDI
При рассмотрении таблицы атрибутов контекста устройства вы наверное заметили, что значительное количество атрибутов изменяются с помощью функции SelectObject. Эти атрибуты представлены специальными структурами данных, описывающими так называемые объекты GDI. Эти объекты описывают некоторые примитивы GDI, используемые при выводе изображений. В качестве примера можно привести перья (pen) и кисти (brush), применяемые при рисовании линий и закраске фона фигур.
Объекты GDI не имеют ничего общего с объектами ООП, они являются объектами с только точки зрения Windows и принадлежат модулю GDI. Фактически такой объект реализован как специальная структура (иногда несколько структур) данных, управление которыми осуществляется системой, а вы можете этими данными манипулировать, используя хендл. Эти структуры данных не являются интерактивными и они не получают никаких сообщений. Так что использование в данном случае термина объект является не слишком удачным, хотя и общепринятым.
Общие правила
Объектов GDI существует достаточно большое количество, но все они имеют сходные правила применения. Перед тем как приступить к их использованию целесообразно рассмотреть основные правила применения объектов GDI.
1) Объекты могут создаваться и уничтожаться в любой момент времени, причем они могут сохраняться и между обработкой разных сообщений. Поэтому объекты часто создаются в функции WinMain или при обработке сообщения WM_CREATE, а уничтожаются, соответственно, либо при обработке сообщения WM_DESTROY, либо перед выходом из функции WinMain.
2) Все созданные объекты обязательно должны быть уничтожены до завершения приложения. Windows сам не уничтожает оставленных приложением объектов, что может привести к быстрому исчерпанию ресурсов. Это связано с тем, что объекты GDI размещаются не в глобальной памяти Windows, а в локальной памяти модуля GDI (USER.EXE или GDI32.EXE). Для этого модуля ограничен максимальный размер локальной кучи в 64К для 16ти разрядных (Windows 3.x, Windows–95) и 4М для 32х разрядных (WindowsNT, Windows–98) графических подсистем, причем объекты GDI, созданные каким–либо приложением, с этим приложением не ассоциируются, в следствие чего автоматического уничтожения этих объектов не происходит[3].
3) Перед уничтожением объекта вы должны быть уверены, что он не выбран контекст устройства. Если объект в момент уничтожения используется, то он не будет уничтожен.
4) Объекты GDI кэшируется системой. То есть повторное создание часто используемого объекта осуществляется существенно быстрее, чем в первый раз.
5) Помимо создаваемых в приложении объектов, система может вам предоставить некоторые стандартные, которые соответствуют наиболее часто применяемым объектам. Например — тонкое перо черного цвета или кисть белого цвета и т.д. Стандартные объекты нельзя уничтожать. Вообще–то система должна заметить, что предпринимается попытка уничтожения стандартного объекта и запретить эту операцию. Но ошибки встречаются везде, даже в системе, так что лучше не уповать на ее надежность.
6) Разные объекты имеют хендлы со специфичными названиями HPEN, HBRUSH, HFONT и др. Вы можете применять просто HANDLE или HGDIOBJ вместо всех этих типов. Применение специфичных типов может быть предпочтительным при осуществлении строгой проверки типов. В разных API и реализациях windows.h для разных компиляторов стандартные функции GDI могут использовать несколько различающиеся типы хендлов. Так, например, функция SelectObject, которая может работать с объектами разных типов, обычно декларирована как функция, получающая хендл типа HGDIOBJ. Однако в старых 16ти разрядных версиях windows.h она может быть описана как получающая просто HANDLE. Часто может быть удобнее применять макросы, определенные в windowsx.h, которые осуществляют соответствующие операции с необходимым приведением типов. Например, вместо SelectObject можно использовать макросы SelectPen, SelectBrush, SelectFont и т.д., смотря по типу выбираемого объекта.
7) Стандартные объекты можно получить с помощью функции
HGDIOBJ GetStockObject (nIndex);
Она возвращает хендл стандартного объекта. Нужный объект задается параметром nIndex. Например, это может быть BLACK_PEN, WHITE_BRUSH, SYSTEM_FONT и т.д. Подробнее — см. в описании функции. Либо, вместо этой универсальной функции можно применять макросы, определенные в windowsx.h: GetStockPen, GetStockBrush, GetStockFont и пр. Эти макросы будут возвращать результат соответствующего типа (HPEN, HBRUSH, HFONT, ...). При использовании макросов надо проследить, что бы индекс требуемого объекта соответствовал используемому макросу. Например, вы можете по ошибке использовать макрос GetStockPen для получения хендла стандартного шрифта — так как макрос в итоге обратиться к универсальной функции GetStockObject, то фатальной ошибки не возникнет, просто возвращаемый результат будет приведен к некорректному типу (в примере: HPEN вместо HFONT).
8) Для создания собственных объектов применяются функции, начинающиеся со слова Create... Например, CreatePen или CreateSolidBrush.
9) Полученный объект (стандартный или созданный вами) выбирается в контекст устройства функцией:
HGDIOBJ SelectObject (hDC, hObject);
Эта функция возвращает хендл объекта того–же типа, выбранного ранее в этот контекст. В большинстве случаев может быть удобнее воспользоваться вместо функции SelectObject макросами из windowsx.h, предназначенными для работы с конкретными объектами:
HPEN SelectPen (hDC, hPen);
HBRUSH SelectBrush (hDC, hBrush);
HFONT SelectFont (hDC, hFont);
HBITMAP SelectBitmap (hDC, hBitmap);
10) Для получения информации об объекте применяется функция
int GetObject (hObject, nSize, lpvStruct);
где hObject — хендл объекта GDI, информацию о котором вы запрашиваете, lpvStruct — указатель на структуру данных, которая будет заполняться информацией об объекте; для объектов разных типов определены разные структуры (BITMAP, LOGPEN, LOGBRUSH, LOGFONT и т.д.), nSize — размер этой структуры.
11) Объект уничтожается функцией
BOOL DeleteObject (hObject);
Эта функция удаляет указанный объект, если только это не стандартный объект и если он не выбран в контекст устройства. Вместо одной универсальной функции в windowsx.h можно найти макросы, удаляющие объекты конкретных типов: DeletePen, DeleteBrush, DeleteFont и т.д.
Обычное использование
Сейчас мы рассмотрим обычные схемы использования объектов. В самом типичном случае объект создается непосредственно тогда, когда он используется, и уничтожается сразу после освобождения из контекста устройства. Несмотря на часто выполняемую операцию создания и уничтожения этот метод является предпочтительным, так как до минимума сводится количество одновременно существующих объектов (что снижает требования к объему свободных ресурсов GDI), а затраты времени минимизируются благодаря кэшированию объектов системой.
В качестве примера мы будем использовать только один объект GDI — перо, так как применение всех остальных типов объектов аналогично.