Сохранение контекста устройства



 

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

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

HDC SaveDC (hDC);

BOOL RestoreDC (hDC, hSavedDC);

Возможно специальное применение функции RestoreDC (hDC, -1) — восстановить в том виде, какой был перед последним вызовом SaveDC.

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

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

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

При работе с окнами, имеющими стиль CS_OWNDC удобно настраивать атрибуты контекста при создании окна, например так:

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)

{WNDCLASS wc;

// при регистрации класса окна задать стиль CS_OWNDC:

wc.style= CS_OWNDC;...

RegisterClass (&wc);...}

// при обработке сообщений в оконной функции:

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

{HDC hDC;

PAINTSTRUCT ps;

switch (wMsg) {

case WM_CREATE:...

hDC= GetDC (hWnd);

// ... установить атрибуты при создании окна

ReleaseDC (hWnd, hDC);...

break;

case WM_PAINT:

hDC= BeginPaint (hWnd, &ps);

// обычные функции получения хендла контекста устройства будут теперь

// возвращать хендл сохраненного контекста.

// здесь используются атрибуты, установленные ранее...

EndPaint (hWnd, &ps); // контекст по-прежнему должен быть освобожден

break;

case ...:...}

...}

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

В частном случае все окна одного класса могут иметь одинаковые атрибуты. Тогда можно указать стиль не CS_OWNDC, а CS_CLASSDC. Этот стиль указывает на то, что используется только одна сохраненная копия контекста в памяти, которая используется всеми окнами данного класса. В этом случае можно настраивать атрибуты контекста еще в функции WinMain, сразу после создания первого окна этого класса.

 

Системы координат GDI

 

Для начала надо уточнить то место, которое занимает система координат GDI в Windows. Как отмечалось при первом знакомстве, в Windows используется одновременно несколько разных систем координат. Среди них надо выделить следующие:

Система координат менеджера окон; в документации никак особо не оговаривается, что применяется именно эта система координат.

Система координат панели диалога; применяется только при разработке собственных диалогов. В тексте часто можно понять, что речь идет именно о ней, если оговаривается, что используются единицы диалога (dialog units). Если не оговаривается, то по контексту — все, связанное с шаблонами и ресурсами описания диалогов использует именно эту систему отсчета.

Система координат GDI; в документации координаты в системе координат GDI часто называют логическими координатами (logical coordinates). Там же может встретиться понятие координаты устройства (device coordinates).

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

 

Основные понятия

 

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

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

В любом случае в Windows ту систему координат, по отношению к которой задается система координат GDI, называют системой координат устройства (device coordinates, viewport coordinates). А саму систему координат GDI называют логической системой координат (logical coordinates, window coordinates).

Обратите внимание, что английская терминология в этой области очень путаная, одно и то же понятие может обозначаться разными терминами даже в пределах одного абзаца. Так, термины viewport и device относятся к системе координат устройства, а термины logical и window описывают логическую систему координат. Это несколько странно, так как при выводе в окно система координат окна будет соответствовать координатам устройства, а логические координаты, используемые GDI, почему–то будут обозначаться термином window.

Когда система предоставляет вам контекст устройства, то его система координат совпадает с координатами устройства (окна), но у вас есть возможность эту систему координат самостоятельно изменить во время рисования на контексте.

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


 

В этих формулах используются нижние индексы в виде view и win, в соответствии с принятыми названиями атрибутов контекста устройства. Индекс view относится к системе координат устройства, а индекс win — к логической системе координат. То есть xview и yview — координаты какой–либо точки в системе координат устройства, Xview.org и Yview.org— относительное смещение начал отсчета систем координат, выраженное в единицах устройства (viewport origin), Xwin.org и Ywin.org — то же самое смещение, но выраженное в логических единицах (window origin), а Xview.ext, Yview.ext и Xwin.ext, Ywin.ext— масштабные коэффициенты (viewport extents, window extents).

Естественно, что в этих формулах смещение начала отсчета должно задаваться лишь единожды — либо для логической системы координат, либо для системы координат устройства. В каком именно виде — зависит исключительно от удобства. Например, если вы хотите начало отсчета логической системы координат поместить точно в центре окна (листа бумаги и пр.), то фактически вы знаете положение точки начала отсчета в координатах устройства — размеры контекста устройства, деленные пополам — тогда удобнее задать величины Xview.org и Yview.org, а Xwin.org и Ywin.org оставить равными нулю.

 


 

Рисунок 2. Система координат устройства и логическая система координат

 

Для обратного преобразования (из системы координат устройства в логическую систему координат), будут применяться такие же формулы, но с переставленными индексами win и view:

 

 

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

BOOL DPtoLP (hDC, lpPoints, nCount);

BOOL LPtoDP (hDC, lpPoints, nCount);

Функция DPtoLP преобразует координаты точек, заданных массивом lpPoints из nCount объектов типа POINT, заданные в системе координат устройства в логические координаты (DPtoLP — Device Point to Logical Point), то есть из «view» в «win», а функция LPtoDP — выполняет обратное преобразование.

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

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

POINT pt;

pt.x = 10; pt.y = 0;

DPtoLP (hdc, &pt, 1); // пересчитаем 10 пиксель (ед. устройства) в логические

// единицы. Далее считаем, что в компоненте pt.x записана нужная нам величина

Ошибка, которая присутствует в этом фрагмента, сразу и не видна. Более того, во многих случаях вы получите вполне приемлемый результат и даже не заподозрите об ошибке — до тех пор, пока у вашей логической системы координат не окажется смещенным начало отсчета по оси x от левой границы контекста. В этом случае вы получите ширину 10 пиксель, преобразованную в логические единицы плюс смещение начала отсчета:

 

Рисунок 3. Из–за смещения начала отсчета возможно возникновение ошибок.

 

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

POINT vector[ 2 ];

vector[0].x = 0; vector[0].y = 0;

vector[1].x = 10; vector[1].y = 0;

DPtoLP (hdc, &vector, 2);

vector[1].x -= vector[0].x;

// Далее считаем, что в компоненте vector[1].x записана нужная нам величина

 

Выбор системы координат

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

 

Название атрибута

Значение по умолчанию Обозначение в формулах
Mapping mode Система координат MM_TEXT  
Window origin Начало отсчета в логических координатах 0,0 Xwin.org, Ywin.org
Viewport origin Начало отсчета в координатах устройства 0,0 Xview.org, Yview.org
Window extents Масштабные коэффициенты системы координат 1,1 Xwin.ext, Ywin.ext
Viewport extents Масштабные коэффициенты системы координат 1,1 Xview.ext, Yview.ext

 

Стандартная система координат GDI, выбираемая в контекст устройства при его создании совпадает с системой координат самого устройства (или окна). Такая система координат получила название текстовой (MM_TEXT). Вы можете отказаться от этой системы координат и установить некоторую собственную систему, у которой ориентация осей или масштабные коэффициенты отличаются от стандартной. Очевидно, что чаще всего придется устанавливать какие–либо системы координат, базирующиеся на метрической или английской системах мер. Раз так, то Microsoft предоставляет несколько дополнительных систем координат, так что во многих случаях вы можете просто выбрать подходящую вам метрическую (MM_LOMETRIC, MM_HIMETRIC), английскую систему (MM_LOENGLISH, MM_HIENGLISH) или полиграфическую (MM_TWIPS), не заботясь о точном вычислении масштабных коэффициентов. Более того, используя какую–либо из вышеназванных систем вы вообще не можете изменять масштабные коэффициенты, хотя можете перемещать точку начала отсчета.

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

 

Название Единица Ориентация осей
MM_TEXT 1 пиксель
MM_LOMETRIC 0.1 мм
MM_HIMETRIC 0.01 мм
MM_LOENGLISH 0.01"
MM_HIENGLISH 0.001"
MM_TWIPS 1/20 полиграфической точки = 1/1440"  (предполагается, что полиграфическая точка = 1/72")[2]
MM_ISOTROPIC x=y цена единицы определяется пользователем
MM_ANISOTROPIC x!=y цена единицы определяется пользователем

 

Обратите внимание, что при выборе любой системы координат начало отсчета размещается в верхнем левом углу контекста, даже если ось Y направлена вверх (!). При этом получается, что весь рисунок располагается в области отрицательных значений координаты Y. На практике это значит, что для большинства систем координат (кроме MM_TEXT и MM_TWIPS) вы как правило должны задать новое положение начала отсчета.

Внимание! На 16ти разрядных платформах координаты задаются целым 16ти разрядным числом со знаком, так что минимальное значение -32768, а максимальное +32767.

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

UINT GetMapMode (hDC);

UINT SetMapMode (hDC, nIndex);

Функции, изменяющие положение точки начала отсчета и масштабные коэффициенты, возвращают информацию о предыдущих или ныне действующих значениях атрибутов различным образом. Существует некоторый «старый» набор функций, изначально ориентированный на 16ти разрядную платформу. Эти функции возвращают обе компоненты атрибута (масштабные коэффициенты по осям X и Y), упакованные в двойное слово; младшее слово содержит компонент X, а старшее — компонент Y. Для получения этих компонент по отдельности можно воспользоваться макросами LOWORD (dw) и HIWORD (dw).

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

 

typedef struct tagSIZE { int cx; int cy; } SIZE; typedef struct tagPOINT { int x; int y; } POINT;

 

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

// Реализованы только в Windows API

DWORD GetWindowOrg (hDC); 0

DWORD GetViewportOrg (hDC); 0

DWORD SetWindowOrg (hDC, nX, nY); 0

DWORD SetViewportOrg (hDC, nX, nY); 0

// Реализованы в Windows API (начиная с Windows 3.1) и в Win32 API

BOOL GetWindowOrgEx (hDC, lpPoint);

BOOL GetViewportOrgEx (hDC, lpPoint);

BOOL SetWindowOrgEx (hDC, nX, nY, lpPrevPoint);

BOOL SetViewportOrgEx (hDC, nX, nY, lpPrevPoint);

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

// Реализованы только в Windows API

DWORD GetWindowExt (hDC); 0

DWORD GetViewportExt (hDC); 0

DWORD SetWindowExt (hDC, nX, nY); 0

DWORD SetViewportExt (hDC, nX, nY); 0

DWORD ScaleWindowExt (hDC, xMul, xDiv, yMul, yDiv); 0

DWORD ScaleViewportExt (hDC, xMul, xDiv, yMul, yDiv); 0

// Реализованы в Windows API (начиная с Windows 3.1) и в Win32 API

BOOL GetWindowExtEx (hDC, lpSize);

BOOL GetViewportExtEx (hDC, lpSize);

BOOL SetWindowExtEx (hDC, nX, nY, lpPrevSize);

BOOL SetViewportExtEx (hDC, nX, nY, lpPrevSize);

BOOL ScaleWindowExtEx (hDC, xMul, xDiv, yMul, yDiv, lpPrevSize);

BOOL ScaleViewportExtEx (hDC, xMul, xDiv, yMul, yDiv, lpPrevSize);

При использовании функций Scale...Ext... система осуществляет коррекцию масштабных коэффициентов с помощью следующих формул:


Xnew.ext = (Xold.ext * xMul) / xDiv

Ynew.ext = (Yold.ext * yMul) / yDix

 

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

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

Но можно сделать и проще — воспользоваться системой координат MM_ISOTROPIC. При установке атрибутов в такой системе координат GDI сам корректирует их значения, что бы обеспечить равную цену единиц. При этом важно устанавливать сначала масштабные коэффициенты логической системы координат (с помощью функции SetWindowExt или SetWindowExtEx) и только затем коэффициенты системы координат устройства (с помощью функции SetViewportExt или SetViewportExtEx).

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

 


Практические примеры

 

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

void Cls_OnPaint (HWND hwnd)

{PAINTSTRUCT ps;

RECT rc;

BeginPaint (hwnd, &ps);

// устанавливаем собственную систему координат

GetClientRect (hwnd, &rc); // rc.left и rc.top всегда равны 0

SetMapMode (ps.hdc, MM_ISOTROPIC);

// задаем масштабные коэффициенты

SetWindowExtEx (ps.hdc, 1000, 1000, (LPSIZE)0L);

SetViewportExtEx (ps.hdc, rc.right, -rc.bottom, (LPSIZE)0L);

// перемещаем начало отсчета в центр контекста

SetViewportOrgEx (ps.hdc, rc.right/2, rc.bottom/2, (LPPOINT)0L);

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

EndPaint (hwnd, &ps);}

В качестве другого примера обратим внимание на систему координат MM_TWIPS. В этой системе координат за единицу принята 1/1440 доля дюйма. Если при подготовке какого–либо полиграфического макета вы применяете эту систему координат для вывода на принтер, то может быть целесообразным при выводе на экран воспользоваться аналогичной системой, но базирующейся на логическом дюйме:

void Cls_OnPaint (HWND hwnd)

{PAINTSTRUCT ps;

BeginPaint (hwnd, &ps);

// устанавливаем собственную систему координат

SetMapMode (ps.hdc, MM_ANISOTROPIC);

SetWindowExtEx (ps.hdc, 1440, 1440, (LPSIZE)0L);

SetViewportExtEx (

ps.hdc,

GetDeviceCaps (ps.hdc, LOGPIXELSX),

GetDeviceCaps (ps.hdc, LOGPIXELSY),

 (LPSIZE)0L);

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

EndPaint (hwnd, &ps);}

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

void Cls_OnPaint (HWND hwnd)

{PAINTSTRUCT ps;

SIZE sz;

RECT rc;

BeginPaint (hwnd, &ps);

GetClientRect (hwnd, &rc);

// устанавливаем собственную систему координат

SetMapMode (ps.hdc, MM_HIMETRIC);

SetMapMode (ps.hdc, MM_ANISOTROPIC);

// рисовать будем автомобиль — масштаб 50:1

ScaleWindowExtEx (ps.hdc, 50,1, 50,1, &sz);

// перемещаем начало отсчета в нижний левый угол листа

SetViewportOrgEx (ps.hdc, 0, rc.bottom, (LPPOINT)0L);

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

EndPaint (hwnd, &ps);}

Этот–же прием может использоваться для «переворота» осей координат. Например, можно установить метрическую систему, но ось Y направить вниз, как в MM_TEXT.

 


Дата добавления: 2019-02-12; просмотров: 246; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!