Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Глобальные системы координат GDI ( Win 32 API )

 

Внимание! В данном разделе рассматриваются дополнительные возможности по преобразованию систем координат, поддерживаемые 32х разрядными подсистемами в Windows NT. Остальные реализации Win32 API и все реализации Windows API не поддерживают этих возможностей.

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

система координат физического устройства (physical device coordinate space)

система координат устройства (device coordinate space)

логическая система координат (page coordinate space)

глобальная система координат (world coordinate space)

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

Система координат физического устройства соответствует координатам и единицам устройства; для того, что бы можно было удобно работать с самыми различными устройством вводится система координат устройства, использующая какие–­либо независимые от устройства единицы отсчета — например, дюймы и миллиметры. Логическая система координат соответствует логическим координатам в понимании Windows API и на нее распространяются все рассмотренные в предыдущих разделах преобразования. Следующий уровень абстракции — глобальная система координат — добавляет дополнительный механизм пересчета координат, обеспечивающий возможность поворота, перекоса, отражения и масштабирования координат.

Все эти системы координат 2х мерные, различаются только ориентацией осей, ценой деления и максимальным диапазоном изменения координат. Так координаты физического устройства ограничены, естественно, размерами самого устройства (или окна), координаты устройства могут изменяться в диапазоне 227 единиц как по горизонтали, так и по вертикали, а логические и глобальные координаты —в диапазоне ±231 единиц.

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


x’ = M11 * x + M21 * y + Dx

y’ = M12 * x + M22 * y + Dy

 

Полученные в результате такого преобразования координаты x’ и y’ рассматриваются как логические и затем подвергаются преобразованию, соответствующему переходу от логической системы координат к координатам устройства (см. функцию SetMapMode).

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

int GetGraphicsMode (hDC);

int SetGraphicsMode (hDC, nIndex);

Для задания индекса режима можно использовать одно из двух символических имен:

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

GM_ADVANCED — расширенный режим Win32 API. В этом режиме вы можете определять или изменять матрицу преобразования глобальных координат. Точнее говоря, вы можете вызывать функции для задания или изменения матрицы преобразования координат. Если такая матрица уже задана и отличается от стандартной, то даже при переходе в GM_COMPATIBLE она будет использоваться по–прежнему. Для отключения преобразований вы должны установить стандартную матрицу преобразований (M11 и M22 равны 1.0, остальные коэффициенты M21, M12, Dx и Dy равны 0.0) с помощью функции SetWorldTransform, либо, воспользовавшись функцией ModifyWorldTransform установить исходную матрицу.

BOOL GetWorldTransform (hDC, lpxformMatrix);

BOOL SetWorldTransform (hDC, lpxformMatrix);

BOOL ModifyWorldTransform (hDC, lpxformMatrix, dwMode);

BOOL CombineTransform (lpxformResult, lpxformA, lpxformB);

typedef struct tagXFORM {

FLOAT eM11;

FLOAT eM12;

FLOAT eM21;

FLOAT eM22;

FLOAT eDx;

FLOAT eDy;

} XFORM;

Функция GetWorldTransform возвращает текущую матрицу преобразований, SetWorldTransform позволяет задать новую матрицу, а функции ModifyWorldTransform и CombineWorldTransfrom используются для изменения и вычисления коэффициентов матрицы. Считается, что матрица XFORM используется следующим образом:

 

 

В этой форме матрица XFORM сделана квадратной, добавлением третьего столбца с неизменяемыми значениями, равно как и вектора сделаны трехкомпонентными добавлением еще одного компонента, равного 1. Векторная форма записи этой матрицы будет полезна при рассмотрении функции ModifyWorldTransform, которая выполняет умножение текущей матрицы преобразований на заданную вами. Такое умножение может выполняться двумя способами (умножение матриц не коммутативно): если параметр dwMode равен MWT_LEFTMULTIPLY, то задаваемая вами матрица используется как левый операнд умножения, а текущая — как правый; а если dwMode равен MWT_RIGHTMULTIPLY, то задаваемая вами матрица будет располагаться справа от текущей. Еще одно возможное значение параметра dwMode — MWT_IDENTITY — устанавливает стандартную матрицу преобразований, при этом параметр lpxformMatrix не используется.

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

Перемещение (translation). Перемещение осуществляется добавлением постоянных величин к координатам x (коэффициент Dx) и y (коэффициент Dy). При этом коэффициенты M11 и M12 должны быть равны 1, а M12 и M21 равны 0. Формула в матричном виде раскрывается следующим образом:

 

x’ = x + Dx y’ = y + Dy

 

Масштабирование (scaling) и зеркальное отражение (reflection). Обе эти операции выполняются одним способом, для их задания необходимо указать масштабные коэффициенты M11 (масштаб по оси X) и M22 (масштаб по оси Y).

Отрицательные значения коэффициентов соответствуют перевернутому виду. Для получения зеркального отражения задают коэффициенты равными -1.

 

x’ = x * M11 y’ = 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 * cos a - y * sin a y’ = x * M12 + y * M22 = x * sin a + y * cos a

 

Сдвиг (shear). Для задания сдвига (описание неперпендикулярных осей координат) необходимо задать два коэффициента M12 и M21, задающих величину сдвига осей. При этом коэффициенты M11 и M22 оба равны 1.

 

x’ = x + y * M21 y’ = 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х разрядных (Windows NT, 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 — перо, так как применение всех остальных типов объектов аналогично.

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) (об этом см. методы связывания данных с окном).

 

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...