Основы графического вывода

Тип работы:
Лекция
Предмет:
Программирование


Узнать стоимость

Детальная информация о работе

Выдержка из работы

Основы графического вывода

В предыдущих разделах мы вскользь касались темы, связанной с графическим выводом. При рассмотрении самого первого приложения (пример 1), были коротко отмечены основные правила работы с графическими устройствами, введено понятие контекст устройства (device context, DC), обсуждены некоторые основные правила работы с контекстом. При первом знакомстве мы ограничились только применением контекста устройства при обработке сообщения WM_PAINT.

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

Контекст устройства

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

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

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

получить хендл контекста этого устройства

осуществить собственно вывод на это устройство (рисование, вывод текста и пр.)

обязательно освободить контекст устройства.

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

Контекст устройства описывает так называемые атрибуты контекста и непосредственно характеристики устройства.

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

Информация об устройстве описывает непосредственно возможности самого графического устройства. Функции GDI взаимодействуют с устройством опосредованно -- через контекст и через драйвер этого устройства. Для обеспечения универсальности средств вывода от драйверов требуется поддерживать некоторый базовый минимум операций. При необходимости выполнить более сложные операции GDI будет привлекать специальные программные расширения, являющиеся частью самого GDI. В случае использования устройств, способных аппаратно реализовывать дополнительные функции, GDI будет направлять запросы непосредственно драйверу этого устройства, а не использовать собственные расширения.

Рисунок 1. Вывод изображений с использованием контекста устройства в Windows

Как правило вы можете не заботиться о характеристиках устройств, на которых реально будет работать приложение. Однако, при разработке сложных приложений, которые могут широко распространяться, вы должны все-таки позаботиться о некоторых вопросах совместимости -- например, при назначении цветов стоит их подбирать так, что бы при переходе на черно-белое оборудование изображение осталось бы различимым. Часто лучшим решением является возможность легкой настройки программы пользователем под его конкретную аппаратуру, либо использование только системных цветов (конечно, если для всех объектов, отображаемых приложением, предусмотрены системные цвета).

Обычно надо позаботиться о нормальном функционировании приложения в следующих случаях:

если приложение осуществляет вывод только в окно, то надо учитывать возможность работы:

с разным разрешением -- от 640×400, 640×480 и до часто встречающихся 1024×768, 1280×1024. Было бы очень желательно, что бы даже в режиме 640×400 все диалоги и окна помещались на экране.

с разным числом цветов -- от 16 и до более чем 16 миллионов цветов. При этом надо учитывать как количество цветов, которое поддерживается видеокартой, так и количество цветов, которое может воспроизводить дисплей. Чисто монохроматические дисплеи (черный и белый) уже практически не встречаются, а вот дисплеи дешевых переносных компьютеров часто дают только 8−16 градаций серого; причем различимость цветов может быть невелика. Сравнительно редкий случай, когда может встретиться монохроматический дисплей -- разработка приложений для работы на серверах.

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

если приложение способно выводить на принтер, то надо иметь в виду, что вместо принтера может оказаться плоттер, который хорошо рисует линии, но совершенно не может выводить растровых изображений, либо АЦПУ, которое способно только печатать текст.

Получение информации об устройстве

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

Для того, что бы получить информацию об устройстве в GDI предусмотрена функция int GetDeviceCaps (hDC, nIndex);

Эта функция возвращает целое число, являющееся значением указанного аргументом nIndex параметра устройства. В windows. h определено значительное количество символических имен, определяющих возвращаемые функцией GetDeviceCaps данные. Возвращаемое число может представлять собой как непосредственное значение запрашиваемого параметра (например, ширину устройства в миллиметрах), либо битовой последовательностью, в которой отдельные биты являются флагами (см., например, параметр RASTERCAPS). Полный список всех возможных характеристик устройства весьма обширен, поэтому приводить его здесь не будем; при необходимости можно обратиться к формальному описанию функции GetDeviceCaps в документации. Некоторые из них:

DRIVERVERSION

Версия драйвера. 0×0100 обозначает версию 1. 0

HORZSIZE, VERTSIZE

размер устройства вывода в миллиметрах

HORZRES, VERTRES

размер устройства вывода в единицах устройства вывода (пикселях)

LOGPIXELSX,

LOGPIXELSY

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

BITSPERPIXEL

число бит на 1 пиксель

PLANES

число битовых планов

TECHNOLOGY

тип устройства, может принимать следующие значения:

DT_PLOTTER векторный плоттер

DT_RASDISPLAY растровый дисплей

DT_RASPRINTER растровый принтер

DT_RASCAMERA растровая камера

DT_CHARSTREAM поток символов

DT_METAFILE метафайл

DT_DISPFILE дисплейный файл

NUMBRUSHES

Число встроенных кистей

NUMPENS

Число встроенных перьев

ASPECTX

Относительная ширина пикселя

ASPECTY

Относительная высота пикселя

ASPECTXY

Относительная диагональ пикселя

RASTERCAPS

Битовая маска, указывающая возможности устройства при работе с растровыми операциями

RC_BANDING поддерживает пополосный вывод

RC_BITBLT может передавать битмапы

RC_BITMAP64 битмапы могут быть больше 64К

RC_DI_BITMAP поддерживает независимые от устройства битмапы

RC_DIBTODEV поддерживает функцию SetDIBitsToDevice

RC_FLOODFILL может выполнять заливку замкнутых контуров

RC_GDI20_OUTPUT поддерживает расширения версии 2.0 GDI

RC_PALETTE устройство использует палитру

RC_SCALING устройство может масштабировать

RC_STRETCHBLT устройство поддерживает функцию StretchBlt

RC_STRETCHDIB устройство поддерживает функцию StretchDIBits…

Одной из идей разработки GDI было обеспечение единого программного интерфейса со всеми устройствами, однако реализовать ее в полной мере практически невозможно. Поэтому вам иногда придется определять характеристики устройства, на котором вы осуществляете вывод. Например, если вы собираетесь отобразить на принтере какой-либо битмап, то надо проверить бит RC_BITBLT в параметре RASTERCAPS, так как плоттеры и АЦПУ не могут работать с растровыми изображениями; или вам может понадобиться узнать, какое число цветов может быть отображено на дисплее или цветном принтере и т. д.

Атрибуты контекста устройства

Атрибуты контекста описывают уже не само устройство, а те «инструменты» и правила, которыми и по которым будет осуществляться вывод на это устройство. Атрибуты контекста являются независимыми от аппаратуры.

Контекст устройства содержит, помимо информации об устройстве, так называемые «атрибуты» контекста. Так, например, когда мы выводим текст, то применяем тот или иной шрифт. Текущий шрифт -- это один из атрибутов контекста устройства. Аналогично перья, кисти, цвета и др. тоже являются атрибутами контекста устройства. Приведем полную таблицу атрибутов:

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

Стандартное значение

Установить

Получить

Mapping mode

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

MM_TEXT

SetMapMode

GetMapMode

Window origin

Начало отсчета в логических координатах

0,0

SetWindowOrg 0

SetWindowOrgEx

OffsetWindowOrg 0

OffsetWindowOrgEx

GetWindowOrg 0

GetWindowOrgEx

Viewport origin

Начало отсчета в координатах устройства

0,0

SetViewportOrg 0

SetViewportOrgEx

OffsetViewportOrg 0

OffsetViewportOrgEx

GetViewportOrg 0

GetViewportOrgEx

Window extents

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

1,1

SetWindowExt 0

SetWindowExtEx

SetMapMode

ScaleWindowExt 0

ScaleWindowExtEx

GetWindowExt 0

GetWindowExtEx

Viewport extents

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

1,1

SetViewportExt 0

SetViewportExtEx

SetMapMode

ScaleViewportExt 0

ScaleViewportExtEx

GetViewportExt 0

GetViewportExtEx

Pen

Перо (карандаш)

BLACK_PEN

SelectObject

SelectPen 2

SelectObject

SelectPen 2

Current pen position

Текущая позиция пера

0,0

MoveTo 0

MoveToEx

LineTo

GetCurrentPosition 0

GetCurrentPositionEx

Brush

Кисть

WHITE_BRUSH

SelectObject

SelectBrush 2

SelectObject

SelectBrush 2

Brush origin

Начальная точка кисти

0,0 (screen)

SetBrushOrg 0

SetBrushOrgEx

GetBrushOrg 0

GetBrushOrgEx

Font

Шрифт

SYSTEM_FONT

SelectObject

SelectFont 2

SelectObjectSelectFont 2

Bitmap

Ассоциируемый битмап

отсутствует

SelectObject

SelectBitmap 2

SelectObject

SelectBitmap 2

Background mode

Режим заполнения фона

OPAQUE

SetBkMode

GetBkMode

Background color

Цвет фона

White

SetBkColor

GetBkColor

Text color

Цвет текста

BLACK

SetTextColor

GetTextColor

Drawing mode

Режим рисования

R2_COPYPEN

SetROP2

GetROP2

Stretching mode

Режим сжатия изображения

BLACKONWHITE

SetStretchBltMode

GetStretchBltMode

Polygon filling mode

Режим заполнения многоугольников

ALTERNATE

SetPolyFillMode

GetPolyFillMode

Text Alignment

Привязка текста

TA_LEFT|TA_TOP

SetTextAlign

GetTextAlign

Intercharacter spacing

Межсимвольный промежуток

0

SetTextCharacterExtra

GetTextCharacterExtra

Text Justification

Выравнивание строки

0,0

SetTextJustification

SetTextJustification

Clipping region

Область отображения

отсутствует

SelectObject

SelectClipRgn

IntersectClipRect

OffsetClipRect

ExcludeClipRect

SelectObject

GetClipBox

Arc direction

Направление рисования дуг

AD_COUNTERCLOCKWISE

SetArcDirection

GetArcDirection

В случае платформы Win32

Miter Limit

Величина спрямления сопрягаемых линий

10. 0

SetMiterLimit 1

GetMiterLimit 1

Graphics Mode

Режим задания координат

GM_COMPATIBLE

SetGraphicsMode 1

GetGraphicsMode 1

World Transformation Matrix

Матрица преобразования глобальных координат

1. 0,0. 0,0. 0

0. 0,1. 0,0. 0

SetWorldTransform 1

GetWorldTransform 1

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

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

Получение хендла контекста устройства

Как было отмечено выше, существует два метода получения контекста устройства -- создание и получение контекста устройства. Разница связана с тем, что создание и, позже, уничтожение контекста устройства занимает некоторое время. Если вы собираетесь осуществлять вывод на принтер, то эти затраты времени ничтожно малы, по сравнению со всем временем печати. Однако, если вы собираетесь только осуществлять рисование в окне (которое может обновляться очень часто), то даже сравнительно быстрая операция создания контекста, повторенная многократно, займет значительное время. Поэтому в Windows существует несколько заранее созданных контекстов, соответствующих дисплею. При выводе в окно контекст создавать не надо, надо воспользоваться одной из функций, возвращающих такой заранее заготовленный контекст устройства.

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

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

Существует 7 основных методов получения и освобождения контекста устройства; причем каждый метод создает специфичный контекст устройства, предназначенный для выполнения определенных действий. Надо хорошо представлять, в каком случае каким методом надо пользоваться. Функции BeginPaint, GetDC, GetWindowDC возвращают заранее заготовленный контекст устройства, а функции CreateDC, CreateIC, CreateCompatibleDC и CreateMetaFile создают новый контекст.

1) При обработке сообщения WM_PAINT рекомендуется применять следующий способ:

PAINTSTRUCT ps;

BeginPaint (hWnd, & ps);

EndPaint (hWnd, & ps);

Структура PAINTSTRUCT содержит следующие данные:

typedef struct tagPAINTSTRUCT {

HDC hdc; // хендл контекста, он же возвращается функцией BeginPaint

BOOL fErase; // TRUE, если фон неверного прямоугольника надо очищать

RECT rcPaint; // неверный прямоугольник, может быть пустым!

// остальные поля используются Windows:

BOOL fRestore;

BOOL fIncUpdate;

BYTE rgbReserved[ 16 ];

} PAINTSTRUCT;

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

Начиная с Windows 3. x для задания областей, нуждающихся в перерисовке используются не неверные прямоугольники, а неверные области (region), которые могут быть сложной формы. В этом случае прямоугольник rcPaint может быть указан пустым, в то время как неверная область реально существует.

Кроме этого, функция BeginPaint выполняет еще несколько операций:

если fErase равен TRUE, тогда функция BeginPaint вызывает обработку сообщения WM_ERASEBKGND с параметрами wParam= hDC, lParam= 0

неверный прямоугольник маркируется верным. Для этого BeginPaint вызывает функцию ValidateRect.

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

2) Иногда необходимо получить хендл контекста для всей внутренней области окна. Для этого вы можете воспользоваться следующим способом:

HDC hDC;

hDC = GetDC (hWnd);

ReleaseDC (hWnd, hDC);

Этот способ часто применяется для рисования во внутренней области окна при обработке иных сообщений, чем WM_PAINT. Если вы собираетесь использовать его при обработке сообщения WM_PAINT, то помните про:

восстановление фона окна (так как обычно это делает BeginPaint)

удалите неверные прямоугольники после рисования (так, например, вы можете вызвать ValidateRect (hWnd, NULL); для маркирования всего окна верным).

3) Еще реже приходится рисовать во внешней (неклиентной, non-client) части окна, тогда вы можете воспользоваться таким способом:

HDC hDC;

hDC = GetWindowDC (hWnd);

ReleaseDC (hWnd, hDC);

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

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

4) В некоторых случаях надо получить доступ ко всему устройству, например принтеру. Для этого вы должны использовать пару функций CreateDC… DeleteDC следующим образом:

HDC hDC;

hDC = CreateDC (lpszDriver, lpszDevice, lpszOutput, lpData);

DeleteDC (hDC);

Например, для рисования непосредственно на поверхности дисплея:

hDC = CreateDC («DISPLAY», NULL, NULL, NULL);

или принтера:

hDC = CreateDC («IBMGRX», «IBM Graphics», «LPT1: «, NULL);

параметры имеют следующие значения:

lpszDriver

-- имя драйвера (имя файла без расширения)

lpszDevice

-- имя устройства (если один драйвер поддерживает несколько устройств)

lpszOutput

-- имя устройства вывода

lpData

-- указатель на данные, передаваемые во время инициализации.

Функция CreateDC применяется сравнительно редко, так как первоначально она была ориентирована на работу с устройствами типа принтера или плоттера. Однако, для применения этой функции надо было анализировать информацию об используемом принтере (ах), содержащуюся в файле win. ini. Начиная с Windows 3. x появились специальная библиотека, реализующая стандартные диалоги и наиболее распространенные действия, включая процесс получения контекста принтера. См., например, функцию ChoosePrinter.

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

HDC hDC;

hDC = CreateIC (lpszDriver, lpszDevice, lpszOutput, lpData);

DeleteDC (hDC);

параметры функции такие же, как и в предыдущем случае.

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

6) Этот способ создает контекст так называемого совместимого устройства, оно реально не существует, но обладает характеристиками реально существующего.

HDC hCompatDC;

HDC hRealDC;

// для получения контекста реального устройства годится любой

// из способов 1. 5

hRealDC= GetDC (hWnd);

// по хендлу контекста реально существующего устройства создается

// контекст совместимого устройства.

hCompatDC= CreateCompatibleDC (hRealDC);

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

// размером в 1 пиксел. Для нормальной работы с этим устройством

// его надо ассоциировать с битмапом, об этом — позже

// если контекст реального устройства нам больше не нужен, мы можем

// его освободить

ReleaseDC (hWnd, hRealDC);

//. здесь мы можем использовать созданный совместимый контекст

// для освобождения совместимого контекста применяетсяфункция

DeleteDC (hCompatDC);

Совместимые контексты (compatible DC, memory DC) предназначены для работы с битмапами. Для этого созданный совместимый контекст ассоциируется с конкретным битмапом, после чего появляется возможность выполнять рисование на этом битмапе, либо осуществлять передачу изображения между битмапом и другим контекстом устройства. Подробнее о применении совместимых контекстов устройства см. в разделе «Работа с зависимым от устройства битмапом».

7) Последний способ создает так называемый метафайл. Это устройство только лишь запоминает команды GDI в специальном файле, а затем может воспроизвести заданное изображение на реальном устройстве, «проигрывая» запомненные команды. Контекст метафайла существенно отличается от других контекстов информацией об устройстве, так как ни к какому устройству он не привязан и не на кого не похож. Поэтому такие параметры, как число бит на пиксель, число встроенных шрифтов и пр. не имеет для него никакого смысла.

HDC hDC;

HANDLE hMF;

hDC = CreateMetaFile (lpszFilename);

hMF = CloseMetaFile (hDC);

DeleteMetaFile (hMF);

или

HDC hEnhDC;

HENHMETAFILE hEnhMF;

hEnhDC = CreateEnhMetaFile (lpszFilename);

hEnhMF = CloseEnhMetaFile (hDC);

DeleteEnhMetaFile (hEnhMF);

Как использовать полученный хендл метафайла мы рассмотрим позже.

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

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

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

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

Сохранение контекста может выполняться двумя существенно различающимися путями. Так, если вам надо для выполнения какого-то фрагмента рисунка изменить большое число атрибутов контекста, а для продолжения рисования их надо восстановить, то удобно воспользоваться парой функций 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″) По крайней мере так считает Microsoft. Вообще говоря, существует несколько различных полиграфических систем с применением разных единиц отсчета, в которых размер точки может несколько варьироваться.

MM_ISOTROPIC

x=y цена единицы определяется пользователем

MM_ANISOTROPIC

x≠y цена единицы определяется пользователем

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

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

Для того, что бы определить или изменить текущую систему координат можно воспользоваться функциями 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).

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

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

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

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.

Глобальные системы координат GDI (Win32 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;

ПоказатьСвернуть
Заполнить форму текущей работой