Основи графічного виводу

[ виправити ] текст може містити помилки, будь ласка перевіряйте перш ніж використовувати.

скачати

Основи графічного виводу

У попередніх розділах ми побіжно торкалися теми, пов'язаної з графічним виводом. При розгляді самого першого застосування (приклад 1), були коротко відзначені основні правила роботи з графічними пристроями, введено поняття контекст пристрою (device context, DC), обговорені деякі основні правила роботи з контекстом. При першому знайомстві ми обмежилися лише застосуванням контексту пристрою при обробці повідомлення WM_PAINT.

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

Контекст пристрою

Повторимо коротко основні положення, сформульовані при першому знайомстві:

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

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

отримати хендл контексту цього пристрою

здійснити власне висновок на цей пристрій (малювання, виведення тексту і пр.)

обов'язково звільнити контекст пристрою.

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

Контекст пристрою описує так звані атрибути контексту і безпосередньо характеристики пристрою.

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

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

Малюнок 18. Висновок зображень з використанням контексту пристрою в Windows

Як правило ви можете не піклуватися про характеристики пристроїв, на яких реально буде працювати додаток. Однак, при розробці складних додатків, які можуть широко розповсюджуватися, ви повинні все-таки подбати про деякі питання сумісності - наприклад, при призначенні квітів варто їх підбирати так, що б при переході на чорно-біле обладнання зображення залишилося б помітним. Часто кращим рішенням є можливість легкого налаштування програми користувачем під його конкретну апаратуру, або використання тільки системних кольорів (звичайно, якщо для всіх об'єктів, що відображаються додатком, передбачені системні кольори).

Звичайно треба подбати про нормальне функціонування програми в наступних випадках:

якщо додаток здійснює висновок тільки у вікно, то треба враховувати можливість роботи:

з різним дозволом - від 640x400, 640x480 і до часто можна зустріти 1024x768, 1280x1024. Було б дуже бажано, що б навіть у режимі 640x400 всі діалоги і вікна поміщалися на екрані.

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

з різними налаштуваннями системної палітри; включаючи контрастні та енергозберігаючі режими (іноді застосовуються для переносних комп'ютерів)

якщо додаток здатний виводити на принтер, то треба мати на увазі, що замість принтера може виявитися плоттер, який добре малює лінії, але зовсім не може виводити растрових зображень, або АЦПУ, яке здатне тільки друкувати текст.

Отримання інформації про пристрій

Контекст пристрою містить два види даних - інформація про пристрій і так звані атрибути контексту. Інформація про пристрій включає в себе опис безпосередньо того графічного пристрою, на якому буде здійснюватися висновок і можливості цього пристрою з виконання розширених функцій GDI. Ці дані специфічні для апаратури.

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

Ця функція повертає ціле число, що є значенням зазначеного аргументом nIndex параметра пристрою. У windows.h визначено значну кількість символічних імен, що визначають повертаються функцією GetDeviceCaps дані. Повертане число може являти собою як безпосереднє значення запитуваної параметра (наприклад, ширину пристрою в міліметрах), або бітової послідовністю, в якій окремі біти є прапорами (див., наприклад, параметр RASTERCAPS). Повний список всіх можливих характеристик пристрою досить великий, тому наводити його тут не будемо, а при необхідності можна звернутися до формального опису функції GetDeviceCaps в документації. Деякі з них:

DRIVERVERSION

Версія драйвера. 0x0100 позначає версію 1.0

HORZSIZE, VERTSIZE

розмір пристрою виводу в міліметрах

HORZRES, VERTRES

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

LOGPIXELSX,

LOGPIXELSY

число одиниць устаткування (пікселів), що доводиться на один логічний дюйм 1

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

SetWindowOrg Ex

OffsetWindowOrg 0

OffsetWindowOrgEx

GetWindowOrg 0

GetWindowOrg Ex

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

Scale ViewportExtEx

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

SetBrushOrg Ex

GetBrushOrg 0

GetBrushOrg Ex

Font

Шрифт

SYSTEM_FONT

SelectObject

SelectFont 2

SelectObject SelectFont 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

OffsetClip R ect

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

G etWorldTransform 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

- Покажчик на дані, передані під час ініціалізації.

Функція C r eateDC застосовується порівняно рідко, тому що спочатку вона була орієнтована на роботу з пристроями типу принтера або плоттера. Однак, для застосування цієї функції треба було аналізувати інформацію про використаний принтері (ах), що міститься у файлі 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 coordinate s, viewport coordinates). А саму систему координат GDI називають логічною системою координат (logical coordinates, window coordinates).

Зверніть увагу, що англійська термінологія в цій області дуже плутана, одне і те ж поняття може позначатися різними термінами навіть у межах одного абзацу. Так, терміни viewport і device відносяться до системи координат пристрою, а терміни logical і window описують логічну систему координат. Це дещо дивно, тому що при виведенні у вікно система координат вікна буде відповідати координатами пристрою, а логічні координати, використовувані GDI, чомусь будуть позначатися терміном window.

Коли система надає вам контекст пристрою, то його система координат збігається з координатами пристрою (вікна), але у вас є можливість цю систему координат самостійно змінити під час малювання на контексті.

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

У цих формулах використовуються нижні індекси у вигляді view і win, відповідно до прийнятих назвами атрибутів контексту пристрою. Індекс view відноситься до системи координат пристрою, а індекс win - до логічної системі координат. Тобто x view і y view - координати будь-якої точки в системі координат пристрою, X view.org і Y view.org - відносне зміщення почав відліку систем координат, виражене в одиницях пристрою (viewport origin), X win.org і Y win.org - те ж саме зміщення, але виражене в логічних одиницях (window origin), а X view.ext, Y view.ext і X win.ext, Y win.ext - масштабні коефіцієнти (viewport extents, window extents).

Природно, що в цих формулах зміщення початку відліку має задаватися лише одного разу - чи то для логічної системи координат, або для системи координат пристрою. У якому саме вигляді - залежить виключно від зручності. Наприклад, якщо ви хочете початок відліку логічної системи координат помістити точно в центрі вікна (аркуша паперу тощо), то фактично ви знаєте положення точки початку відліку в координатах пристрої - розміри контексту пристрою, поділені навпіл - тоді зручніше поставити величини X view.org і Y view.org, а X win.org і Y win.org залишити рівними нулю.

Малюнок 18. Система координат пристрою і логічна система координат

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

Малюнок 18. Через зсув початку відліку можливе виникнення помилок.

Що б уникнути подібної помилки краще брати не одну точку, а вектор потрібної довжини:

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

X win.org, Y win.org

Viewport origin

Початок відліку в координатах пристрої

0,0

X view.org, Y view.org

Window extents

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

1,1

X win.ext, Y win.ext

Viewport extents

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

1,1

X view.ext, Y view.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 (d w) і HIWORD (dw).

Так як в Win 32 API координати задаються цілим числом, тобто 32 х розрядним, то упакувати два компоненти в одне подвійне 32 х розрядне слово стало неможливо. У зв'язку з цим GDI надає додаткові функції, які повертають необхідну інформацію в структурі типу SIZE або POINT. На щастя, необхідні зміни були внесені в Windows API ще за часів Windows 3.1, так що використання більшості функцій, типових для Win 32 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) і в Win 32 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) і в Win 32 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 ... система здійснює корекцію масштабних коефіцієнтів за допомогою таких формул:

X new.ext = (X old.ext * xMul) / xDiv

Y new.ext = (Y old.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) 0 L);

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 (Win 32 API)

Увага! У даному розділі розглядаються додаткові можливості щодо перетворення систем координат, підтримувані 32 х розрядними підсистемами в Windows NT. Решта реалізації Win 32 API і всі реалізації Windows API не підтримують цих можливостей.

У Win32 API передбачено альтернативний, більш повільний, але істотно більш потужний механізм для визначення власних систем координат. На жаль, в документації при описі нових можливостей Win 32 API в черговий раз відбулася зміна термінології (англійською). При розгляді глобальних систем координат виділяють чотири поняття:

система координат фізичного пристрою (physical device coordinate space)

система координат пристрою (device coordinate space)

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

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

(Російськомовна термінологія наводиться з мінімальними змінами в порівнянні з попереднім розділом, англомовна - відповідно з документацією).

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

Всі ці системи координат 2 х мірні, різняться лише орієнтацією осей, ціною поділки і максимальним діапазоном зміни координат. Так координати фізичного пристрою обмежені, природно, розмірами самого пристрою (або вікна), координати пристрої можуть змінюватися в діапазоні 2 27 одиниць як по горизонталі, так і по вертикалі, а логічні і глобальні координати-в діапазоні ± 31 лютого одиниць.

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

x '= M 11 * x + M 21 * y + D x

y '= M 12 * x + M 22 * y + D y

Отримані в результаті такого перетворення координати x 'і y' розглядаються як логічні і потім піддаються перетворенню, що відповідає переходу від логічної системи координат до координат пристрою (див. функцію SetMapMode).

Перевірити, який режим використовується, або встановити потрібний ви можете за допомогою функцій

int GetGraphicsMode (hDC);

int SetGraphicsMode (hDC, nIndex);

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

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

GM_ADVANCED - розширений режим Win32 API. У цьому режимі ви можете визначати або змінювати матрицю перетворення глобальних координат. Точніше кажучи, ви зможете викликати для завдання або зміни матриці перетворення координат. Якщо така матриця вже задана і відрізняється від стандартної, то навіть при переході в GM_ COMPATIBLE вона буде використовуватися як і раніше. Для відключення перетворень ви повинні встановити стандартну матрицю перетворень (M 11 і M 22 рівні 1.0, інші коефіцієнти M 21, M 12, D x і D y рівні 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 (коефіцієнт D x) і y (коефіцієнт D y). При цьому коефіцієнти M 11 і M 12 повинні бути рівні 1, а M 12 і M 21 рівні 0. Формула в матричному вигляді розкривається наступним чином:

x '= x + D x

y '= y + D y

Масштабування (scaling) і дзеркальне відображення (reflection). Обидві ці операції виконуються одним способом, для їх завдання необхідно вказати масштабні коефіцієнти M 11 (масштаб по осі X) і M 22 (масштаб по осі Y).

Негативні значення коефіцієнтів відповідають перевернутого увазі. Для отримання дзеркального відображення задають коефіцієнти рівними -1.

x '= x * M 11

y '= y * M 22

`Поворот (rotation). Для завдання коефіцієнтів необхідно дізнатися кут повороту a. Якщо він відомий, то коефіцієнти M 11 і M 22 обидва будуть рівні cos a, коефіцієнт M 12 буде дорівнює sin a, а коефіцієнт M 21 = sin a. Тобто для завдання повороту необхідно обчислити коефіцієнти M 11 і M 12, а коефіцієнти M 22 і M 21 виходять з уже обчислених: M 22 = M 11 і M 21 = - M 12.

x '= x * M 11 - y * M 21 = x * cos a - y * sin a

y '= x * M 12 + y * M 22 = x * sin a + y * cos a

Зрушення (shear). Для завдання зсуву (опис неперпендикулярних осей координат) необхідно задати два коефіцієнти M 12 і M 21, що задають величину зсуву осей. При цьому коефіцієнти M 11 і M 22 обидва рівні 1.

x '= x + Y * M 21

y '= x * M 12 + 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 або GDI 32. EXE). Для цього модуля обмежений максимальний розмір локальної купи в 64К для 16 ти розрядних (Windows 3. X, Windows -95) і для 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 0 L;}

Досить часто, якщо об'єкт створюється на весь час життя вікна, замість статичної змінної використовуються пов'язані з вікном дані: або розміщені в структурі вікна (див. функції GetWindow Long, SetWindow Long), або списки властивостей вікна (Property) (про це див методи зв'язування даних з вікном).

Відображення основних графічних об'єктів

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

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

При малюванні ліній на результат починає впливати безліч інших атрибутів, що визначають вид відображається лінії і те, яким чином здійснюється це відображення.

При малюванні заповнених фігур сам процес малювання можна представити у вигляді двох фаз - малювання контуру і заповнення внутрішньої області. Якщо малювання контуру здійснюється також, як і малювання ліній, то на заповнення фону починають впливати додаткові атрибути.

Завдання кольору

При відображенні будь-яких графічних об'єктів необхідно задавати їх кольору. Складності завдання кольору починаються через те, що різна апаратура може відтворювати лише деякий кінцеве число кольорів. Одночасно вам може знадобитися здійснювати висновок відразу на кілька пристроїв, можливості яких по відображенню кольору суттєво різняться. Наприклад - будь-який текстової чи графічний редактор може взаємодіяти як з дисплеєм, здатним відтворювати від декількох квітів до мільйонів квітів і принтером, який часто є монохромним.

Для забезпечення універсальності в GDI прийнято, що колір позначається за допомогою 24 х бітового параметра типу COLORREF. Цей параметр реально відповідає одному подвійному слову, найстарший байт якого не використовується. Молодші три байти задають інтенсивності трьох основних компонент кольору - червоної, зеленої та синьої. Кожна компонента задається своїм байтом, так що граничний діапазон її зміни лежить від 0 до 255. Це дозволяє задавати до 16 777 216 різних кольорів.

Для зручного завдання параметра типу COLORRREF використовується спеціальний макрос RGB, який з трьох окремо зазначених компонент формує одне подвійне слово.

COLORREF RGB (byRed, byGreen, byBlue);

Звичайно, Windows не завжди може використовувати 24 х бітовий колір безпосередньо, так як апаратура може підтримувати від 2 кольорів (монохроматичні пристрою) до більш ніж 16 млн. кольорів (режими TrueColor і HighColor для сучасних відеоадаптерів). Замість відображення вказаного вами кольору, GDI буде підбирати найближчий колір, який в змозі відтворити дана апаратура в даному режимі; такий колір називається чистий (pure color).

Увага! На монохромних пристроях існує тільки два чисті кольори: чорний і білий. Тому всі кольори, які мають суму яркостей трьох складових менш 381, представляються чорними, а більше - білими. Це може призвести до нерозрізненості образу, якщо, наприклад, ви по чорному фону малюєте червону лінію, або по білому - світло-ціанові.

У деяких випадках GDI може використовувати замість чистого кольору суміш точок різних кольорів, найбільш точно передають бажаний колір. Такий колір називається змішаним (dithered color). Однак така операція виконується далеко не завжди. Так при виведенні тексту колір символів буде чистим кольором; аналогічно лінії (за невеликим винятком) малюються теж чистим кольором.

Робота з окремими пікселями

Малювання по окремих точках вкрай рідко використовується в Windows, так як швидкість здійснення подібної операції є виключно низькою. У всіх випадках рекомендується знайти який-небудь інший спосіб відображення, а не малювати по окремих точках.

Власне, для малювання точок існують всього дві функції:

З OLORREF GetPixel (hDC, nX, nY);

COLORREF SetPixel (hDC, nX, nY, crColor);

Координати nX і nY є логічними координатами, що визначають позицію пікселя, колір якого ми дізнаємося або змінюємо. Незалежно від прийнятої системи координат відображається один піксель, навіть якщо його розмір відповідає кількох логічним одиницям.

Крім того, задається колір пікселя автоматично перетвориться до найближчого чистого кольору, який може бути відтворений апаратурою.

Якщо використовуваний піксель знаходиться поза області малювання, то обидві функції повертають -1, а якщо піксель знаходиться всередині області малювання, то повертається значення відповідає поточному кольору пікселя. Для функції SetPixel воно може відрізнятися від заданого crColor, вказуючи застосований чистий колір (якщо ваша апаратура не може відобразити необхідний колір). Цей прийом іноді використовується для перевірки можливостей пристрою, хоча ефективніше аналізувати можливості пристрою за допомогою функції GetDeviceCaps.

Малювання ліній

У процесі малювання лінії бере участь відразу кілька атрибутів GDI (крім атрибутів, які обирають систему координат). Перед малюванням потрібної вам лінії ви повинні налаштувати контекст пристрою відповідно до виконуваної операцією, встановивши потрібні значення атрибутів. Коротко призначення цих атрибутів наступне:

Поточне перо (Pen).

Визначає вид проведеної лінії - товщину, колір, стиль (суцільна або переривчаста). Перо є об'єктом GDI, тому при його використанні необхідно дотримуватися правил роботи з об'єктами GDI.

Поточна позиція пера (Pen Position).

Визначає точку з якої почнеться малювання наступного відрізка лінії. Після малювання цього відрізка поточна точка переміщається в кінець відрізка.

Колір фону (Background Color).

Заповнює проміжки між штрихами переривчастої лінії (а також фон під символами виведеного тексту або проміжки між лініями штрихованих кистей - про це див в обговоренні малювання заповнених фігур та обговоренні виведення тексту).

Режим заповнення фону (Background Mode).

Визначає, чи потрібно зафарбовувати проміжки між штрихами переривчастої лінії кольором фону або залишити фон без змін. Аналогічно - при виведенні тексту.

Режим малювання (Drawing Mode).

На практиці малювання на контексті пристрою розглядається як складний процес перенесення підготовленого вами зображення (наприклад, образу рисуемой лінії) на вже існуюче в цьому місці зображення. У процесі переносу виконується деяка операція, в результаті якої виходить результуюче зображення. Зазвичай така операція - просте копіювання нового зображення на старе. Однак ви можете задати необхідну операцію, яку треба виконувати в процесі перенесення зображення.

Напрямок малювання дуг еліпсів (Arc Direction).

Впливає лише на результат малювання дуги еліпса (функції Arc, Pie і Chord). За замовчуванням дуги малюються проти годинникової стрілки, але ви можете змінити цей напрям. Користуватися цією можливістю, взагалі кажучи, не рекомендується, так як застосування цієї функції обмежена - в Win 32 API в розширеному режимі завдання координат (див. SetGraphicsMode, GM_ADVANCED) дуги малюються завжди проти годинникової стрілки і змінити цей напрям не можна.

Власне для малювання прямих ліній необхідно всього дві функції:

DWORD MoveTo (hDC, nX, nY); 0

BOOL MoveToEx (hDC, nX, nY, lpPoint);

BOOL LineTo (hDC, nX, nY);

Функція MoveToEx переміщує поточну точку пера у вказане місце, не виконуючи малювання. Подальший виклик функції LineTo здійснить малювання від поточної точки до вказаної кінцевої відрізка. Намальований відрізок лінії не включає в себе останню крапку! Це зроблено для зручності малювання ламаних ліній з декількох відрізків - в цьому випадку будь-яка точка ламаної буде намальована тільки один раз. Якщо вам необхідно намалювати відрізок обов'язково включаючи останню крапку - продовжите відрізок на один піксель далі (не забувайте, що ви задаєте логічні координати, а подовжити відрізок треба на одну одиницю пристрою!). Якщо це зробити важко (наприклад відрізок нахилений під якимось кутом) то можна додатково намалювати крихітний відрізок довжиною 1 піксель в будь-яку сторону.

У випадку Windows API часто застосовувалася функція MoveTo, а не MoveToEx; Функція MoveTo повертала інформацію про колишньому положенні поточної точки, упаковану в подвійне слово. У Win 32 API така упаковка неможлива - кожна координата вже є подвійне слово - тому функція MoveTo більше не підтримується.

Можливий випадок, коли треба намалювати ламану лінію, що складається з декількох відрізків. Звичайно, це легко зробити за допомогою серії викликів функції LineTo, але іноді може бути простіше скористатися іншою функцією:

BOOL Polyline (hDC, lpPoints, nCount);

Ця функція малює ламану лінію, початкова, кінцева і всі точки перегину якої задані в масиві структур типу POINT (параметр lpPoints), що містить nCount точок. Лінія малюється, починаючи від першої точки і далі, з'єднуючи послідовно відрізками всі крапки, аж до останньої. Ламана лінія може бути незамкненою, GDI не проводить замикаючого відрізка від останньої точки до першої.

Ще одна функція GDI призначена для малювання дуг:

BOOL Arc (hDC, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd);

Для завдання рисуемой дуги еліпса ви повинні задати спочатку сам еліпс, дуга якого буде зображуватися, а потім задати початкову і кінцеву точки дуги. Еліпс задається описує його прямокутником (причому нижня та права кордону не включаються) - параметри xLeft, yTop, xRight і yBottom, а початкова та кінцева точки задаються параметрами xStart, yStart і xEnd, yEnd.

Малюнок 18. Малювання дуг еліпсів

Початкова і кінцева точки можуть не лежати на еліпсі. У цьому випадку GDI обчислює точку перетину еліпса з відрізком, що з'єднує центр еліпса з вказаної вами точкою. Дуга буде малюватися між точками перетину відрізків, направлених від центру еліпса до початкової і кінцевої точок дуги в напрямку проти ходу годинникової стрілки (див. розділ «Напрямок малювання дуг»).

У разі застосування Win32 API ви можете використовувати ще кілька функцій для малювання ліній. Так дві нових функції призначені для малювання дуг:

BOOL ArcTo (hDC, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd); 1

BOOL AngleArc (hDC, nX, nY, dwRadius, eStartAngle, eSweepAngle); 1

Функція ArcTo аналогічна функції Arc, за винятком того, що вона спочатку малює відрізок від поточної точки до початкової точки дуги, потім малює саму дугу і переміщує поточну крапку в кінцеву точку намальованої дуги. Функція AngleArc малює дугу кола, які не еліпса. Для неї треба задати центр кола (параметри nX, nY), радіус (dwRadius), початковий кут (eStartAngle) і кутову величину рисуемой дуги (eSweepAngle), в градусах. GDI не перевіряє кутову величину дуги, вона може перевищувати 360 0.

Ще пара нових функцій призначена для малювання ламаних ліній:

BOOL PolylineTo (hDC, lpPoints, nCount); 1

BOOL PolyPolyline (hDC, lpPoints, lpuCounts, nPolyCount); 1

Функція PolylineTo відрізняється від Polyline тим, що починає малювання з відрізка від поточної точки до першої точки, зазначеної в масиві, а після промальовування всіх відрізків переміщує поточну крапку в останню крапку масиву. Функція PolyPolyline може за одну операцію відобразити кілька ламаних ліній. Координати всіх точок всіх ліній задаються масивом структур POINT (параметр lpPoints), кількість точок в кожній ламаної лінії задається масивом цілих чисел lpuCounts, а число ламаних, змальованих цією функцією - параметром nPolyCount.

Крім того в Win32 API існують функції для малювання кривих Безьє:

BOOL PolyBezier (hDC, lpPoints, cPoints); 1

BOOL PolyBezierTo (hDC, lpPoints, cPoints); 1

Функція PolyBezier малює лінію складається із сегментів кривих Безьє. Для завдання кожного сегменту потрібно вказати чотири точки: початкову, дві напрямні і кінцева. Так як малюється лінія з декількох сегментів, то кінцева точка одного сегмента є в свою чергу початковою точкою іншого сегмента. Таким чином крива буде визначатися набором з N pt = 1 + N segments * 3; тут N pt - число точок для завдання кривої, N segments - число сегментів в кривій.

Використовуючи функцію PolyBezier ви повинні задати масив точок, що визначають початкову точку, дві напрямні та кінцеву точку для першого сегмента кривої, потім по дві напрямних і однієї кінцевої для кожного наступного сегменту. Поточна точка при цьому не використовується.

Функція PolyBezierTo відрізняється тим, що поточна точка використовується в якості початкової точки першого сегменту. У цьому випадку в масиві має міститися на одну точку менше - тільки по дві напрямних і однієї кінцевої для кожного сегмента кривої. І, крім того, після малювання кривої поточна точка буде переміщена в кінцеву точку останнього намальованого сегмента.

Остання розглянута функція призначена для малювання цілого набору з прямих відрізків і кривих Безьє за одну операцію:

BOOL PolyDraw (hDC, lpPoints, lpbyTypes, cCount); 1

Масиви структур POINT (lpPoints) і байтів (lpbyTypes) мають однакову кількість елементів, кожен елемент масиву lpbyTypes визначає тип рисуемой лінії з поточної точки до точки, що задається відповідним елементом масиву lpPoints. Можливі наступні значення для типів ліній:

PT_MOVETO

лінія не малюється, поточна точка переміщується у вказану позицію

PT_LINETO

малюється відрізок від поточної точки, до зазначеної; поточна точка переміщується в кінцеву точку відрізка.

PT_BEZIERTO

малюється крива Безьє; для завдання кривої треба визначити підряд три точки типу PT_LINETO: перші дві точки - напрямні, остання - кінцева; поточна точка буде початкова точка кривої, а після малювання поточна точка переміститься в кінець намальованого сегмента.

PT_CLOSEFIGURE

цей прапор може бути об'єднаний із значеннями PT_LINETO або PT_BEZIERTO; при його вказівці фігура буде замкнута проведенням відрізка від останньої точки намальованого сегмента до першої точки фігури (точки типу PT_MOVETO або точки, заданої функцією MoveTo перед викликом PolyDraw.

Перо

Для проведення ліній використовується спеціальний об'єкт GDI, який визначає вид проведеної лінії, її колір і товщину. За аналогією зі звичайним малюванням на папері такий об'єкт отримав назву перо (pen). Перо є об'єктом GDI, отже до нього застосовуються всі правила роботи з об'єктами GDI, розглянуті раніше.

GDI надає можливість використовувати одне з трьох стандартних пір'я або створювати власні пір'я, мають потрібні властивості. Функція GetStockObject дозволяє одержати стандартний перо; вони відрізняються тільки кольором, що проводиться ними лінія завжди суцільна, шириною 1 одиницю устаткування (піксель). Замість функції GetStockObject можна використовувати макрос GetStockPen з windowsx.h. Як і всі стандартні об'єкти GDI це пір'я не можна знищувати.

HANDLE GetStockObject (nIndex);

HPEN GetStockPen (nIndex), 2

BLACK_PEN

- Чорне перо

WHITE_PEN

- Біле перо

NULL_PEN

- Прозоре перо

Куди більше можливостей надають функції, що створюють пір'я. Дві з них - CreatePen і CreatePenIndirect відрізняються лише способом передачі параметрів. Функція CreatePen отримує всі характеристики створюваного пера у вигляді окремих параметрів, а функція CreatePenIndirect використовує структуру LOGPEN, що описує створюване перо. Функціонально обидві функції тотожні. Ця ж структура використовується функцією GetObject для отримання інформації про пере.

HPEN CreatePen (nPenStyle, nWidth, crColor);

HPEN CreatePenIndirect (lpLogPen);

typedef struct tagLOGPEN {

WORD lopnStyle; / / стиль пера

POINT lopnWidth; / / ширина лінії

COLORREF lopnColor; / / колір пера

} LOGPEN;

Стиль пера може бути:

PS_SOLID

суцільна тонка або товста лінія

PS_DASH

штрихова тонка лінія

PS_DOT

пунктирна тонка лінія

PS_DASHDOT

штрих-пунктирна тонка лінія

PS_DASHDOTDOT

штрих-точка-точка тонка лінія

PS_NULL

(Прозорий)


PS_INSIDEFRAME

суцільна тонка або товста лінія

Ширина пера задається в логічних одиницях, причому у випадку функції CreatePenIndirect для завдання товщини використовується структура типу POINT, в якій використовується тільки поле x, а поле y - ні. Ширина пера задається в логічних одиницях. Так як логічна одиниця в загальному випадку може не збігатися з фізичної, то для створення тонких пір'я (ширина яких дорівнює 1 пікселю або одному рядку растра) треба вказати необхідну ширину рівною 0 - тоді буде створено перо шириною 1 піксель. Всі переривчасті лінії (PS_DOT, PS_DASH, PS_DASHDOT, PS_DASHDOTDOT), шириною більше фізичної 1 відтворюються як PS_SOLID.

Для створеного пера буде призначений найближчий чистий колір, який може бути відтворений даної апаратурою. У реальних умовах він може відрізнятися від кольору, заданого вами.

Широка лінія проводиться таким чином, що б її центр з'єднував зазначені точки. Перо застосовується не тільки для малювання ліній, але і для малювання контуру фігур. Якщо при цьому використовується широка лінія, то може бути зручно, що б вся лінія лежала усередині контуру фігури. Для таких цілей призначений стиль лінії PS_INSIDEFRAME. Лінії стилю PS_INSIDEFRAME відрізняються ще однією особливістю: якщо її ширина більше або дорівнює 2 одиницям пристрою, то ця лінія може малюватися не чистим кольором, а змішаним.

При використанні Win32 API ви можете скористатися ще однією функцією для створення пір'я:

HPEN ExtCreatePen (dwPenStyle, dwWidth, lpLogBrush, dwStyleCount, lpdwStyle); 1

typedef struct tagLOGBRUSH {

UINT lbStyle;

COLORREF lbColor;

LONG lbHatch;

} LOGBRUSH;

Пір'я, створювані цією функцією діляться на два типи: косметичні (cosmetic) і геометричні (geometric). Для опису характеристик пера використовується структура LOGBRUSH, зазвичай застосовується для опису кистей (див. розділ "Пензель").

Косметичні пір'я створюються, якщо в параметрі dwPenStyle встановлений стиль PS_COSMETIC. Вони відповідають тонким суцільним або переривчастим лініях (штриховим, штрих-пунктирним і пунктирним); завжди мають ширину 1 піксель (параметр dwWidth повинен бути заданий 1) і малюються чистим кольором (колір задається полем lbColor структури LOGBRUSH; поле lbStyle обов'язково повинно бути BS_SOLID, а полі lbHatch не використовується). Крім стилю PS_COSMETIC треба задати один з додаткових стилів:

Стилі PS_SOLID, PS_DASH, PS_DOT, PS_DASHDOT, PS_DASHDOTDOT, PS_NULL, PS_INSIDEFARME в основному відповідають уже розглянутих стилів звичайних пір'я. (Для косметичних ширина завжди дорівнює 1 пікселю).

Стиль PS_ALTERNATE означає, що точки лінії будуть малюватися через одну.

Якщо використовується стиль PS_USERSTYLE, то два останніх параметри функції ExtCreatePen задають стиль переривчастої лінії: параметр lpdwStyle вказує на масив цілих чисел, які задають довжину штрихів і проміжків між ними; параметр dwStyleCount кількість записів в цьому масиві. Увага! Довжина штрихів і проміжків задається не в логічних одиницях, а в одиницях пристрою, безпосередньо в процесі малювання лінії.

Інший вид пір'я - геометричні - створюються, якщо в параметрі dwPenStyle вказаний стиль PS_GEOMETRIC. Такі пір'я можуть бути довільної товщини і кольору, включаючи візерункові пір'я (малюнок на яких задається зазначеної кистю). Крім того, при створенні геометричних кистей є можливість поставити додаткові стилі. Так, в параметрі dwPenStyle ви повинні визначити стиль PS_GEOMETRIC, стиль лінії, стиль оформлення закінчень і стиль сполучення ліній.

Стиль лінії може бути PS_SOLID, PS_DASH, PS_DOT, PS_DASHDOT, PS_DASHDOTDOT, PS_NULL, PS_INSIDEFARME або PS_USERSTYLE. Стиль PS_ALTERNATE для геометричних пір'я не підтримується.

Стиль оформлення закінчень грає роль для широких ліній і може бути PS_ENDCAP_ROUND (кінець лінії напівкруглий), PS_ENDCAP_SQUARE (у вигляді кута) і PS_ENDCAP_FLAT (прямий).

Стиль сполучення ліній впливає, якщо кілька послідовно проведених відрізків сполучаються під невеликим кутом. Він може бути PS_JOIN_BEVEL (лінії стикуються під кутом), PS_JOIN_ROUND (спряження скругляются) і PS_JOIN_MITER (спряження випрямляється). Якщо використовується перо зі стилем PS_JOIN_MITER, то додатково можна задати граничну величину випрямлення сполучних ліній за допомогою функції

BOOL SetMiterLimit (hDC, eNewLimit, lpeOldLimit); 1

BOOL GetMiterLimit (hDC, lpeOldLimit); 1

Величина випрямлення задається щодо ширини лінії.

Ширина геометричній лінії може бути будь-який, вона задається параметром dwWidth в логічних одиницях. Параметр lpLogBrush описує кисть, яка задає колір і візерунок лінії; кисть може бути однотонної, змішаної, штрихованої або навіть створеної за зразком - залежному від пристрою бітмапами. Використання незалежних від пристрою бітмапами в якості зразків при описі пера неприпустимо. Про кистях і бітмапами див. розділи "Пензель" та "".

Як і у випадку косметичних пір'я, параметри dwStyleCount і lpdwStyle визначають стиль штрихування, якщо заданий стиль лінії PS_USERSTYLE, інакше вони не використовуються. Однак на відміну від косметичного пера, ширина штрихів і проміжків задається в логічних одиницях.

Після використання створеного пера ви повинні його знищити, причому перед цим він повинен бути звільнений з контексту пристрою (або весь контекст повинен бути звільнений). Перо, як і всі об'єкти GDI, знищується за допомогою функції:

BOOL DeleteObject (hGDIobj);

BOOL DeletePen (hPen);

Поточна позиція пера

Наступний атрибут контексту пристрою, який використовується при малюванні лінії, називається поточною позицією пера. Цей атрибут використовується дуже невеликим числом функцій, тому ми коротко розглянемо їх усіх, навіть якщо вони не пов'язані з малюванням ліній.

З першими трьома функціями ми вже познайомилися - вони-то і використовуються для малювання ліній.

DWORD MoveTo (hDC, nX, nY); 0

BOOL MoveToEx (hDC, nX, nY, lpPoint);

BOOL LineTo (hDC, nX, nY);

Крім них з поточною позицією пера мають справу функції GetCurrentPosition і GetCurrentPositionEx а також, в деяких випадках, функції TextOut і ExtTextOut.

DWORD GetCurrentPosition (hDC); 0

BOOL GetCurrentPositionEx (hDC, lpPoint);

Ці функції повертають позицію пера. Як завжди - GetCurrentPosition повертає її в подвійному слові й застосовуватись лише в 16 ти розрядному Windows API, тоді як функція GetCurrentPosition Ex повертає результат у структурі типу POINT і може застосовуватися як в Windows API, так і в Win 32 API.

Дві інші функції - TextOut і ExtTextOut зазвичай не використовують поточну позицію пера, ви повинні ним окремо вказувати позицію для виведення тексту. Однак, іноді може бути зручно перейти в спеціальний режим прив'язки тексту TA_UPDATECP (див. функцію SetTextAlign), при якому текст буде виводитися від поточної позиції, а після виведення тексту поточна позиція переміститься в кінець виведеного фрагмента.

Колір фону і режим заповнення фону

Ще один використовуваний атрибут контексту пристрою - колір фону. Це не той колір, яким зафарбовується фон вікна, хоча він може і збігатися з ним. Фон вікна зафарбовується не поточним кольором фону, а кистю, призначеної вікна при реєстрації класу. Колір фону і режим заповнення фону використовуються:

функціями для малювання ліній - для заповнення проміжків між штрихами переривчастої лінії

функціями виведення тексту - для заповнення простору під символами тексту.

функціями, що застосовують кисть - для заповнення фону між лініями штрихованих кистей.

Малюнок 18. Малювання переривчастих ліній в різних режимах заповнення фону.

Для завдання кольору фону і для з'ясування поточного кольору ви можете скористатися функціями

COLORREF SetBkColor (hDC, crColor);

COLORREF GetBkColor (hDC);

Функції повертають використовуваний раніше колір фону. Аналогічно перу, GDI буде використовувати найближчий чистий колір як колір фону.

У деяких випадках буває зручно проводити пунктирні та штрих-пунктирні лінії так, що б у проміжках було видно колишній фон (колишнє зображення), а не зафарбовувати їх кольором фону. Для цього ви повинні змінити режим заповнення фону. Для це призначені функції:

int SetBkMode (hDC, nBkMode);

int GetBkMode (hDC);

GDI підтримує два різних режиму заповнення фону; один з них називається OPAQUE - це режим за замовчуванням. У режимі OPAQUE проміжки заповнюються поточним кольором фону, а в іншому режимі, званому TRANSPARENT, фон в проміжках не змінюється.

Режим малювання

Атрибут контексту пристрою, званий режим малювання, впливає на сам процес малювання. Справа в тому, що малювання на пристрої легко може бути представлено як операції над бітовими послідовностями, що містять ті або інші дані. Поки ми говорили про малювання лінії, маючи на увазі найпростіший перенесення бітового образу малюється об'єкта (лінії) на інший бітовий образ (вже наявне зображення).

Атрибут режим малювання задає правила, за якими GDI переносить інформацію з однієї бітової послідовності на іншу. Так, крім самого очевидного копіювання можливі операції інверсії як вже наявного зображення, так і малюється, об'єднання з допомогою різних бітових операцій І (and), АБО (or), ВИКЛЮЧАЄ АБО (xor).

При розгляді процесу перенесення зображення ми будемо виходити з припущення монохромного пристрої виведення, оскільки аналіз процесу малювання на кольоровому пристрої виглядає громіздко. Говорячи про монохромному пристрої ми будемо позначати світлу точку будемо бітом зі значенням 1, а для темну крапку - 0.

При малюванні ми можемо умовно розглядати три різні бітові послідовності:

вихідне зображення, що містить мальованої об'єкт, в документації називається pen (чомусь вважається, що малюється саме лінія поточним олівцем - звідси назва - але, взагалі кажучи, режим малювання застосовується для всіх операцій виведення графічних примітивів - ліній, еліпсів, багатокутників, тексту і пр.);

контекст пристрою, що містить намальований раніше образ (хоча б просто зафарбований фон), в документації називається destination;

результат - то зображення, яке буде знаходитися на контексті пристрою після малювання;

Стосовно до цих трьох двійкового послідовностей говорять про двійковій растрової операції (binary raster operation, ROP2), так як у формуванні результату беруть участь дві вихідних бітових послідовності.

Режим малювання задає операцію над бітами обох послідовностей, виконувану в процесі перенесення зображення. Всього можливо 16 різних режимів малювання:

Перо (Pen) P

1 1 0 0

Виконувана

Режим малювання

Наявне зображення (Destination) D

1 0 1 0

операція



0 0 0 0

0

0 R2_BLACK


0 0 0 1

~ (P | D)

1 R2_NOTMERGEPEN


0 0 1 0

(~ P) & D

2 R2_MASKNOTPEN


0 0 1 1

~ P

3 R2_NOTCOPYPEN


0 1 0 0

P & (~ D)

4 R2_MASKPENNOT


0 1 0 1

~ D

5 R2_NOT

Повторне малювання відновлює фон

0 1 1 0

P ^ D

6 R2_XORPEN


0 1 1 1

~ (P & D)

7 R2_NOTMASKPEN


1 0 0 0

P & D

8 R2_MASKPEN


1 0 0 1

~ (P ^ D)

9 R2_NOTXORPEN

Колишнє зображення не змінюється

1 0 1 0

D

10 R2_NOP


1 0 1 1

(~ P) | D

11 R2_MERGENOTPEN

Режим малювання за замовчуванням

1 1 0 0

P

12 R2_COPYPEN


1 1 0 1

P | (~ D)

13 R2_MERGEPENNOT


1 1 1 0

P | D

14 R2_MERGEPEN


1 1 1 1

1

15 R2_WHITE

Так, наприклад, за замовчуванням використовується операція копіювання вихідного зображення на контекст пристрою (звана R2_COPYPEN), досить часто застосовується операція виключає або (R2_XORPEN) між пером і існуючим зображенням на контексті.

Якщо монохроматичні переноситься зображення і вже наявне розглядати як бітові послідовності, то можливі 4 випадки:

обидва зображення, переносний (pen) і наявне (destination), містять світлі крапки

переносний зображення (pen) містить світлу крапку, а наявне (destination) - темну

переносний зображення (pen) містить темну точку, а наявне (destination) - світлу

обидва зображення, переносний (pen) і наявне (destination), містять темні точки

Говорячи в термінах бітів може знадобитися комбінувати 1 з 1, 1 з 0, 0 з 1 і 0 з 0. Ці чотири варіанти перераховані у другому стовпці таблиці у заголовку. У рядках нижче дається очікуваний результат у всіх чотирьох випадках.

Наприклад, якщо в результаті комбінування світлої зі світлою повинна вийти світла точка (1 і 1 дає 1), а в інших випадках - темна (1 і 0, 0 і 1, а також 0 і 0 дають 0), то в таблиці ця операція буде позначена як 1 0 0 0 - R2_MASKPEN.

Ви можете встановити бажаний режим малювання, або дізнатися поточний за допомогою функцій

int SetROP 2 (hDC, nIndex);

int GetROP 2 (hDC);

В описі цих функцій можна також знайти символічні імена всіх 16 режимів і їх короткий опис. Проте, слідуючи приводиться в документації опису, знайти потрібну операцію може бути важко (зазвичай там не наводяться результати виконання операцій, тільки назви і позначення). У цьому випадку ви можете отримувати потрібний номер растрової операції з цієї таблички, навіть ви взагалі можете обійтися без символічних імен, просто розглядаючи бітову послідовність у табличці як номер режиму. Цим правилом можна користуватися, якщо порядок чотирьох можливих комбінацій відповідає наведеному в таблиці і в прикладі.

Наприклад, ви хочете вибрати такий режим, що б зображення отримувало світлу точку тільки в тому випадку, коли і мальованої точка і точка наявного фону мають темний колір, або коли точка лінії - світла, а фону - темна; у всіх інших випадках повинна вийти темна крапка.

Малюнок 18. Отримання номера растрової операції.

Напрямок малювання дуг

Для завдання дуги, яку ви хочете намалювати необхідно визначити еліпс, дугу якого ви збираєтеся намалювати, а також початкову та кінцеву точки дуги. Але от проблема - від початкової до кінцевої точки можна провести дугу в двох різних напрямках, так що доводиться додатково визначати, в якому саме напрямку дуга буде намальована. Зазвичай прийнято, що дуга малюється проти годинникової стрілки.

Ви можете при необхідності дізнатися або змінити напрямок малювання дуг за допомогою функцій:

int GetArcDirection (hDC);

int SetArcDirection (hDC, nIndex);

Припустимими є AD_COUNTERCLOCKWISE - малювання проти ходу годинникової стрілки (прийнято за замовчуванням) і AD_CLOCKWISE - по ходу годинникової стрілки.

Увага! Ці дві функції не працюють у разі розширеного режиму завдання координат (див. функцію SetGraphicsMode 1, GM_ADVANCED) - у цьому випадку дуги завжди малюються проти годинникової стрілки.

Додаткові кошти для малювання ліній

Windows містить ще одну функцію, яка застосовується для малювання ліній. Вона призначена для послідовного обчислення координат точок, що належать лінії, і виконання над цими точками будь-якої операції, визначеної користувачем. Фактично ця функція для кожної точки рисуемой лінії викликає зазначену користувачем процедуру. Цікаво, що сама ця функція ніяк не взаємодіє з контекстом пристрою, вона виконує тільки математичні операції, обчислюючи всі проміжні точки лінії.

void LineDDA (nXStart, nYStart, nXEnd, nYEnd, lpfnD da P rc, lParam);

nXStart, nYStart - визначають першу точку лінії

nXEnd, nYEnd - визначають останню точку лінії

lpfnDda P rc - покажчик на процедуру типу LINEDDAPROC

lParam - дані, що передаються користувачем процедурі LINEDDAPROC

Викликається процедура LINEDDAPROC має наступний вигляд:

void CALLBACK LineDDAproc (nX, nY, lParam) {

/ / ...}

Процедура LineDDAproc отримує координати точки, яку треба намалювати і дані, передані користувачем.

Координати першої та останньої точок задаються в довільних одиницях, так як їх використання визначається не процедурою LineDDA, а процедурою LineDDAproc, що розробляється вами. Які координати вам зручніше - такі і використовуйте. Адреса процедури lpfnDdaPrc у разі Windows API є адресою, повернутому функцією MakeProcInstance, але не адресою самої процедури (про це докладніше - у розділі «диспетчер пам'яті»). Дані, що передаються користувачем, (lParam) є подвійним словом. У документації стверджується, що це далека покажчик на дані, хоча це некоректне твердження. Часто параметр у вигляді подвійного слова використовується для завдання адреси яких-небудь даних (особливо, якщо дані займають більше двох слів), проте реально там може утримуватися довільне 32 х розрядне число. Більш того, в прототипі функції він описаний саме як long.

Малювання заповнених фігур

Наступна група функцій призначена для відображення заповнених фігур. До таких фігур відносяться прямокутники, прямокутники з округленими краями, еліпси, сектору, дуги, стягнуті хордою і багатокутники. Умовно можна уявити собі малювання заповнених фігур як процес, що складається з двох етапів - малювання контуру поточним пером і заповнення тла. При малюванні контуру справедливі всі зауваження, зроблені при обговоренні малювання ліній. Якщо вам не треба малювати контур, то виберіть в контекст пристрою прозорий олівець (функція GetStockObject або макрос GetStockPen, NULL_PEN). При заповненні фону рисуемой фігури використовуються як вже розглянуті атрибути GDI, так і кілька нових. Природно, що перед малюванням потрібної вам фігури ви повинні встановити необхідні значення атрибутів контексту пристрою.

Кисть (B rush).

Кисть використовується як для зафарбовування внутрішньої області замкнутих фігур, так і для зафарбовування внутрішньої області вікна. Якщо фон фігури заповнювати не треба, то встановіть прозору кисть. Фактично кисть являє собою маленький, 8x8 піксель бітмапами, який багато разів повторюється при заповненні зазначеної області. Кисть може бути як однотонною (всі крапки кисті мають однаковий колір), так і штрихованої або гаптівника. Для штрихованих кистей визначається тільки колір штрихів; колір проміжків між штрихами визначається безпосередньо при закраске області.

Колір фону (Background Color).

При закраске областей штрихований китицями, кисть визначає тільки лише колір штрихів, а проміжки між штрихами заповнюються поточним кольором фону.

Режим заповнення фону (Background Mode).

Визначає, чи потрібно зафарбовувати проміжки між лініями штрихованих кистей кольором фону або залишити фон без змін.

Режим малювання (Drawing Mode).

Визначає операцію, виконувану в процесі перенесення формованого зображення на контекст пристрою.

Напрямок малювання еліпсів (A rc D irection).

Впливає на спосіб завдання початкової і кінцевої точок дуг, що утворюють сектора або дуги, стягнуті хордою.

Режим заповнення багатокутників (Polygon F illing M ode).

При малюванні заповнених багатокутників ви можете задати багатокутник такої форми, що він буде містити накладаються один на одного елементи. GDI пропонує два різних алгоритму, що обчислює конфігурацію закрашиваемой області. За допомогою даного атрибуту ви можете вибрати більш підходящий вам алгоритм.

Більшість перерахованих тут атрибутів GDI вже розглянуто у розділі «», так що повторно обговорюватися вони не будуть. Нижче окремо винесено обговорення двох нових атрибутів: кисть і режим заповнення багатокутників.

Функції для малювання заповнених фігур

Серед всіх функцій, які змальовують заповнені об'єкти, можна умовно виділити групу функцій, для виконання яких ви повинні задати описує мальованої об'єкт прямокутник. До таких функцій відносяться функції з малювання прямокутника (Rectangle), прямокутника з округленими краями (RoundRect), еліпса (Ellipse), а також функції для малювання сектора (Pie) і дуги, стягнутої хордою (Chord). Всі ці функції вимагають завдання або безпосередньо малюється прямокутника, або прямокутника, в який буде вписано мальованої об'єкт (прямокутник з округленими кутами, весь мальованої еліпс або еліпс, дуга якого використовується для малювання сектора або стягується хордою).

При завданні описує прямокутника необхідно враховувати кілька нюансів:

По-перше, за замовчуванням нижня та права кордону описує прямокутника не включаються до мальованої об'єкт, проте у разі Win 32 API і встановленого розширеного режиму кордону описує прямокутника повністю включаються до мальованої об'єкт (функція SetGraphicsMode, GM_ADVANCED, додатково див. розділ «»).

По-друге, мальованої об'єкт може виходити за рамки описує прямокутника, якщо для обведення контуру використовується перо, ширина якого перевищує одну одиницю пристрою і це перо не стилю PS_INSIDEFRAME. Лінія стилю PS_INSIDEFRAME завжди буде знаходитися всередині описує прямокутника.

По-третє, орієнтація сторін описує прямокутника завжди паралельна осях координат і, відповідно, передбачені функції не можуть малювати похилих прямокутників і еліпсів, що є істотним незручністю. Єдине можливість - при використанні Win 32 API перейти в розширений режим і нахилити самі осі координат (див. розділ «»).

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

BOOL Rectangle (hDC, xLeft, yTop, xRight, yBottom);

BOOL Ellipse (hDC, xLeft, yTop, xRight, yBottom);

BOOL RoundRect (hDC, xLeft, yTop, xRight, yBottom, xRound, yRound);

Функція Rectangle малює прямокутник з прямими кутами, а функція RoundRect скругляют ці кути, проводячи невеликі дуги. Ці функції використовують прямокутник, координати якого задаються в якості параметрів, причому для функцій Rectangle і RoundRect цей прямокутник задає безпосередньо мальованої об'єкт, а для функції Ellipse він задає прямокутник. Функція RoundRect додатково вимагає завдання величини еліпсів, якими вона буде скруглять кути. При цьому треба враховувати, що задаються розміри описує скругляющим еліпс прямокутника, а не його радіуси. Тобто в кожному кутку скругляются дуга буде займати тільки половину від зазначених вами значень.

Малюнок 18. Завдання величини скругляющим еліпсів для функції RoundRect.

Тепер залишилося розглянути пару функція для малювання сектора (Pie), тобто дуги і області, обмеженої двома радіусами і для малювання дуги, стягнутої хордою (Chord).

Малюнок 18. Результат виконання функцій Pie (ліворуч) і Chord (праворуч).

BOOL Pie (hDC, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd);

BOOL Chord (hDC, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd);

При використанні цих функцій треба пам'ятати про направлення малювання дуги (в Windows API ви можете задавати цей напрямок, а в Win 32 API в розширеному графічному режимі воно завжди проти годинникової стрілки), про завдання початкової і кінцевої точок дуги і про особливості завдання би прямокутника.

Ще дві функції, що використовуються для малювання обмежених фігур, дозволяють малювати багатокутники.

BOOL Polygon (hDC, lpPoints, nCount);

BOOL PolyPolygon (hDC, lpPoints, lpCounts, nPolyCount);

Функція Polygon малює поточним олівцем ламану лінію, що має вказане параметром nCount кількість точок з координатами, заданими масивом структур типу POINT, який задається параметром lpPoints (аналогічно функції PolyLine - див розділ «»). На відміну від PolyLine, функція Polygon замикає багатокутник і зафарбовує його внутрішню область поточної пензлем. Так як багатокутник може бути досить складним, і деякі його області можуть перекриватися, то GDI передбачає два різних алгоритму обчислення внутрішньої, закрашиваемой області. Застосовуваний алгоритм визначається атрибутом контексту пристрою, званим режим заповнення багатокутників (polygon filling mode) - див нижче.

Функція PolyPolygon дозволяє намалювати кілька багатокутників за одну операцію. При цьому вказується масив, що містить координати всіх точок всіх багатокутників послідовно один за одним, масив, що містить числа точок у кожному з багатокутників і число змальованих багатокутників.

Кисть

У Windows існує спеціальний об'єкт GDI, використовуваний для зафарбовування внутрішніх областей фігур; за аналогією з малюванням на папері цей об'єкт отримав назву кисть (brush). Практично кисть являє собою невелику (8x8 пікселів) картинку, яка багато разів відтворюється для зафарбовування зазначеної області.

Кисть є об'єктом GDI і, відповідно, при роботі з нею треба дотримуватися загальних правил роботи з об'єктами GDI.

Windows містить кілька стандартних пензлів, для вибору яких можна скористатися функцією:

HANDLE GetStockObject (nIndex);

HBRUSH GetStockBrush (nIndex), 2

де параметр nIndex може бути:

BLACK_BRUSH

- Кисть чорного кольору

DKGRAY_BRUSH

- Темно-сіра

GRAY_BRUSH

- Сіра

LTGRAY_BRUSH

- Світло-сіра

WHITE_BRUSH

- Біла

HOLLOW_BRUSH

- Прозора кисть

NULL_BRUSH

- Прозора кисть (синонім символу HOLLOW_BRUSH)

Біла кисть (WHITE_BRUSH) зазвичай використовується для зафарбовування фону вікна (при описі класу вікна поле WNDCLASS.hbrBackground задається зазвичай рівним хендла білої кисті).

У принципі, ви можете задати будь-яку іншу кисть для зафарбовування внутрішньої області вікна, або змінивши реєстрацію класу вікна, або, вже після реєстрації та створення вікна (вікон) цього класу, скориставшись функцією:

UINT SetClassWord (hWnd, GCW_HBRBACKGROUND, hbrNewBrush); / / Windows 3.x

DWORD SetClassLong (hWnd, GCL_HBRBACKGROUND, hbrNewBrush); / / Win32 API

яка змінить кисть, яка застосовується для зафарбовування фону вікна, на іншу. При цьому треба враховувати, що змінитися кисть для всіх вікон, що належать цьому класу, так що вам треба вжити спеціальних заходів для перемальовування всіх вікон цього класу після зміни кисті.

Кисті HOLLOW_BRUSH і NULL_BRUSH представляють один і той самий об'єкт - прозору кисть. Ви можете використовувати дану кисть для зафарбовування фону замкнутих фігур, якщо вони повинні бути представлені тільки контуром, без заповнення внутрішньої області.

Якщо ви хочете створити власну кисть, то можна скористатися однією з наступних функцій:

HBRUSH CreateSolidBrush (crColor);

HBRUSH CreateHatchBrush (nStyle, crColor);

HBRUSH CreatePatternBrush (hBitmap);

HBRUSH CreateDIBPatternBrush (hGlobDIB, nColorSpec);

HBRUSH CreateBrushIndirect (lpLogBrush);

Функція CreateSolidBrush дозволяє створити однотонну кисть. В якості параметра вказується колір створюваної кисті. У тому випадку, якщо система не може відтворити вказаний колір чистим, то GDI буде використовувати змішування різнокольорових пікселів для одержання найбільш точного наближення до заданого кольору.

Так, наприклад, в 16 ти кольоровому режимі стандартні кисті LTGRAY_BRUSH, GRAY_BRUSH і BLACK_BRUSH можуть бути представлені відтінками сірого кольору, а кисть DKGRAY_BRUSH буде представлена ​​сумішшю точок сірого і чорного кольорів.

Функція CreateHatchBrush створює штрихованої кисть. Ви вказуєте два параметри - тип штриховки (nStyle) і колір штриха (crColor), для зафарбовування фону між штрихами в якості фону використовуються атрибути контексту пристрою поточний колір фону та режим заповнення фону.

Малюнок 18. Стилі штрихованих кистей

Параметр crColor задає колір штрихування. GDI буде застосовувати найближчий чистий колір для малювання ліній штрихування. Для завдання кольору фону треба скористатися функціями

COLORREF SetBkColor (hDC, crColor);

int SetBkMode (hDC, nMode);

За допомогою функцій CreatePatternBrush і CreateDIBPatternBrush ви можете описати кисть, яка визначається зразком (pattern) - малюнком розміром 8x8 пікселів. Для цього треба попередньо отримати бітмапами розміром 8x8 пікселів (або більше) і передати його відповідної функції. Кисть буде створена виходячи із зображення розміром 8x8 пікселів, що знаходиться у верхньому-лівому кутку бітмапами. Докладніше про отримання бітмапами - дивися в розділі "". Весь час, поки існує кисть, ви повинні зберігати вихідний бітмапами, за яким ця кисть побудована. Один бітмапами може застосовуватися для створення багатьох кистей.

Різниця між двома функціями створення кисті за зразком пов'язана із застосуванням різних видів бітмапами - залежних від пристрою (DDB - d evice d epended b itmap) і незалежних від пристрою (DIB - d evice i ndepended b itmap). Незалежний від пристрою бітмапами містить дані про зображення, так і дані про застосовувані кольорах (палітрі). При створенні кисті на основі DIB потрібно два параметри, один з яких вказує зразок (незалежний від пристрою бітмапами), а інший вказує правила інтерпретації логічних квітів даного бітмапами.

До цих двох функцій створення кисті за зразком треба зробити ще одне зауваження: якщо бітмапами є монохромним, то тоді GDI буде представляти його не у вигляді чорно-білого зображення, а використовувати поточний колір тексту замість кольору (1) і поточний колір тла замість кольору ( 0). Таким чином картинка виявляється як-би негативною - точки, кодовані 1, за замовчуванням представляються чорними (колір тексту), а 0 - білими (колір фону).

Функція CreateBrushIndirect є об'єднанням всіх розглянутих функцій: як параметр їй передається покажчик на структуру типу LOGBRUSH, яка описує кисть будь-якого типу.

typedef struct tagLOGBRUSH {

UINT lbStyle;

COLORREF lbColor;

int lbHatch;

} LOGBRUSH;

Поле lbStyle визначає стиль кисті. Воно може приймати одне з наступних значень: BS_SOLID, BS_HATCHED, BS_HOLLOW, BS_NULL, BS_PATTERN і BS_DIBPATTERN (що в якійсь мірі відповідає функції, яка застосовується для створення кисті). Використання інших полів структури LOGBRUSH залежить від стилю кисті:

Стиль

Еквівалентна функція, яку застосовують для створення кисті

Використання параметра lbColor

Використання параметра lbHatch

BS_HOLLOW, BS_NULL


не використовується

не використовується

BS_SOLID

CreateSolidBrush

колір кисті

не використовується

BS_HATCHED

CreateHatchBrush

колір штрихування

стиль штрихування

BS_PATTERN

CreatePatternBrush

не використовується

хендл DDB бітмапами (HBITMAP)

BS_DIBPATTERN

CreateDIBPatternBrush

спосіб інтерпретації логічних квітів

хендл блоку пам'яті з DIB бітмапами (HGLOBAL)

Структура LOGBRUSH може використовуватися також для з'ясування властивостей кисті за допомогою функції GetObject.

Прив'язка кисті (brush alignment)

Існує цілком особливий прийом при роботі з китицями - так звана прив'язка кисті. При закраске внутрішній області будь-якої фігури за допомогою кисті GDI багаторазово відтворює її зображення. Однак при цьому виникає питання: з якого місця починає відтворюватися зображення кисті? Вважається, що GDI за замовчуванням повторює кисть починаючи з верхнього лівого кута екрана - тобто від точки з координатами пристрою (0,0). Це так звана початкова точка кисті (brush origin).

Малюнок 18. Пояснення до атрибуту початкова точка кисті (brush origin)

Якщо кисть представлена ​​яким-небудь зображенням або штрихуванням, то все закрашуєте фрагменти потрапляють в одну "фазу". У деяких випадках треба змінювати початок відліку пензлі для того, що б малюнок або штрихування був узгоджений із зафарбовуваною поверхнею. Для цього застосовується прийом, званий вирівнювання кисті (brush alignment):

POINT pt;

/ / Brush origin встановлюється в координатах пристрої,

/ / А точка з якої ми будемо вирівнювати зазвичай визначена

/ / У логічних координатах, тому потрібне перетворення.

pt.x = 0; pt.y = 0; / / вибираємо логічні координати нової точки

LPtoDP (hDC, & pt, 1); / / переводимо їх у координати вікна

ClientToScreen (hWnd, & pt); / / а тепер в координати екрана

/ /-Якщо ми застосовуємо систему координат MM_TEXT (за замовчуванням), то

/ / Ми можемо не використовувати функцію LPtoDP;

/ / - Якщо контекст відповідає іншому влаштуванню, ніж дисплей,

/ / То ми не використовуємо функцію ClientToScreen.

/ / Кисть має розмір 8 x 8 пікселів, тому координати початкової точки

/ / Краще задавати в діапазоні 0 .. 7, тобто залишок від ділення на 8

pt. x% = 8; pt. y% = 8;

/ / Тепер нам відомі координати пристрої нового brush origin

UnrealizeObject (hNewBrush);

SetBrushOrg (hDC, pt.x, pt.y);

/ / Функція UnrealizeObject дозволяє призначити для кисті нову початкову

/ / Крапку; це призначення відбудеться при виборі кисті в контекст пристрою,

/ / Причому початкова точка призначається саме контексту, а не кисті.

SelectObject (hDC, hNewBrush);

При вирівнюванні кисті треба дотримуватися кількох обмежень:

заборонено застосовувати функцію UnrealizeObject до всіх стандартних пензлям;

вирівнювати можна тільки кисть, не обрану в контекст пристрою;

Режим заповнення багатокутників

При малюванні багатокутників виникає необхідність у вирішенні досить складного завдання - визначенні внутрішньої області багатокутника, яку необхідно зафарбувати. Завдання може істотно ускладнитися, якщо врахувати можливість малювання декількох перекриваються багатокутників за одну операцію і можливість завдання пересічних областей одного багатокутника. Типовими прикладами є малюнки п'ятикутною зірочки або будиночка:

Малюнок 18. Приклади багатокутників з перекриваються поверхнями.

У такій ситуації поведінка GDI буде визначатися поточним режимом заповнення багатокутників (polygon filling mode). Ви можете його змінити або дізнатися за допомогою функцій

UINT GetPolyFillMode (hDC);

UINT SetPolyFillMode (hDC, nIndex);

допустимі два значення параметра nIndex: ALTERNATE і WINDING. У режимі ALTERNATE GDI зафарбовує на кожному рядку розгортки відрізок між сторонами з непарних і парних послідовними номерами. Дуже спрощено - область, яка повторно зафарбовується збереже первинний вигляд. У режимі WINDING застосовується більш складний алгоритм, який дозволяє обчислити і зафарбувати й внутрішню область багатокутника. Наприклад, заповнення багатокутника у вигляді п'ятикутної зірочки в різних режимах заповнення багатокутників виглядає так:

Малюнок 18. Заповнення п'ятикутною зірочки в різних режимах заповнення багатокутників.

Прямокутники та регіони

Історично склалося так, що прямокутник є базовою фігурою при роботі з графічними пристроями. Значна частина примітивів GDI вимагає завдання описує прямокутника, вікна знову-таки мають форму прямокутника (не рахуючи можливості використовувати еліптичні вікна в Windows -95), області вікон, які потребують перемальовуванні - невірні області - у ранніх версіях Windows описувалися прямокутником, растрові зображення - бітмапами - мають форму прямокутника і так далі. Природно, що в Windows були включені спеціальні засоби для виконання математичних операцій над прямокутниками і деякий допоміжний набір функцій, який здійснює операції зафарбовування, обведення контуру, інверсії кольору та іншого у зазначеній вами прямокутної області.

У міру розвитку Windows багато функцій прямокутників були передані більше складних об'єктах - регіонам (region), які можуть описувати області складної форми. При цьому розвинувся паралельний набір функцій, орієнтованих на застосування регіонів замість прямокутників.

Функції, орієнтовані на роботу з прямокутниками і з регіонами досить різнорідні, відносяться до самих різних підсистем Windows, не тільки до GDI, залежно від того, як буде застосовуватися вказаний прямокутник або регіон. Велика частина цих функцій розглядається в цьому розділі, хоча в інших розділах ті ж самі функції будуть розглядатися додатково, з поглибленим обговоренням їх використання.

Прямокутники

Розгляд операції над прямокутниками ми почнемо з математичних функцій. Ці функції розглядають прямокутник як якусь математичну абстракцію, описувану структурою типу RECT.

typedef struct tagRECT {

int left;

int top;

int right;

int bottom;

} RECT;

Над цією структурою можна виробляти деякі операції, як-то ініціалізувати нулем, задати її поля, додати до всіх полів певні величини і так далі. Використання цих функцій не завжди доцільно, тому що того ж ефекту можна досягти більш простими засобами. Так, функція

void SetRect (lpRect, xLeft, yTop, xRight, yBottom);

дозволяє заповнити структуру типу RECT зазначеними значеннями, функція

void SetRectEmpty (lpRect);

обнуляє поля структури RECT, а функція

void CopyRect (lpRectDst, lpRectSrc);

копіює одну структуру RECT в іншу. Розглянуті функції замінюються на більш прості конструкції самим тривіальним чином, причому одержуваний код виявляється компактніше й істотно швидше. Ще дві функції здійснюють переміщення прямокутника по координатної площини (OffsetRect) і зміна його розмірів (InflateRect):

void OffsetRect (lpRect, nDeltaX, nDeltaY);

void InflateRect (lpRect, nDeltaWidth, nDeltaHeight);

void InsetRect (lpRect, nDeltaWidth, nDelatHeight), 2

Макрос InsetRect відповідає викликом функції:

InflateRect (lpRect, - (nDeltaWidth), - (nDeltaHeight)).

Деякі функції для роботи з прямокутниками, що надаються Windows, все-таки досить зручні, що б їх не замінювати власними:

BOOL IsRectEmpty (lpRect);

перевіряє, чи є даний прямокутник порожнім, чи ні; Функція

BOOL EqualRect (lpRect, lpRect);

перевіряє збіг прямокутників (чи мають вони однакові розміри і положення); а функція

BOOL PtInRect (lpRect, l pPoint);

перевіряє, чи потрапляє зазначена точка в заданий прямокутник. Ще три функції дозволяють виконати найпростіші математичні операції над прямокутниками: обчислити перетин, об'єднання та виключення.

BOOL IntersectRect (lpRectDst, lpRectSrc1, lpRectSrc2);

BOOL UnionRect (lpRectDst, lpRectSrc1, lpRectSrc2);

BOOL SubtractRect (lpRectDst, lpRectSrc1, lpRectSrc2);

Малюнок 18. Перетин, об'єднання і два варіанти виключення прямокутників.

При використанні функції SubtractRect для обчислення області прямокутника 1, що не входить в прямокутник 2 треба бути впевненим, що прямокутник 2 повністю перекриває прямокутник 1 по одній зі сторін.

Крім чисто математичних операцій над прямокутниками існують деякі функції, призначені для малювання прямокутників. До них відносяться функції для інверсії кольору у прямокутній області, для зафарбовування прямокутника зазначеної пензлем і для проведення облямівки навколо прямокутника.

void InvertRect (hDC, lpRect);

інвертує колір зазначеного прямокутника, виконуючи операцію BITWISE NOT над усіма пікселями прямокутника. Ця функція зручна для виділення будь-яких прямокутника, тому що повторне виконання цієї операції відновлює первинний вигляд прямокутника.

У деяких випадках буває зручно просто зафарбувати зазначеної пензлем необхідну область. Звичайно, це можна зробити за допомогою функції Rectangle. Однак цей спосіб не завжди хороший, тому що при малюванні прямокутника він оточується лінією, намальованою поточним олівцем. Цей олівець, по-перше завжди представлений чистим кольором, а, по-друге, прямокутник не завжди треба обмежувати лінією (використання прозорого олівця призводить до частої зміни олівців). Для цього Windows містить дві додаткові функції:

int FillRect (hDC, lpRect, hBrush);

int FrameRect (hDC, lpRect, hBrush);

Функція FillRect зафарбовує вказаний прямокутник необхідної пензлем, а функція FrameRect проводить навколо зазначеного прямокутника облямівку знову ж пензлем (не олівцем). Ширина проведеної облямівки 1 піксель як по горизонталі, так і по вертикалі.

Замість функції FillRect можна іноді застосовувати функцію PatBlt, яка дозволяє зафарбувати фон поточної пензлем. Ця функція підтримується безпосередньо драйверами пристроїв, так що її застосування дає найбільш швидкий виконуваний код. Докладніше про функції PatBlt див. у розділі «»

Окремий випадок - зафарбування прямокутної області не пензлем, а конкретним кольором. Очевидний спосіб - створення однотонної кисті і зафарбування прямокутника з допомогою функції FillRect або Rectangle - по-перше досить громіздкий і, по-друге, не гарантує зафарбовування саме чистим кольором - кисть може виявитися змішаної з точок різних кольорів. Найбільш швидкий спосіб - використовувати функцію ExtTextOut, вказавши їй порожню рядок, що обмежує прямокутник і необхідність зафарбовування прямокутника кольором фону (прапор ETO_OPAQUE).

Прямокутники часто використовуються для оголошення будь-яких областей внутрішньої області вікна невірними, тобто такими, що потребують перемальовуванні або, навпаки, вірними - після промальовування зазначеної області. Для цього призначені дві функції

BOOL InvalidateRect (hWnd, lpRect, fEraseBkgnd);

BOOL ValidateRect (hWnd, lpRect);

Якщо замість адреси структури RECT вказати NULL, то система буде мати на увазі прямокутник, що співпадає з усією внутрішньою областю вікна.

Регіони

Така кількість функцій, призначених для роботи з прямокутниками пов'язано з тим, що прямокутник можна назвати основним примітивом Windows, так як він використовується практично повсюдно. Однак застосування прямокутників не завжди ефективно. Наприклад, якщо прямокутники використовуються для оголошення невірної області вікна, то об'єднання, скажімо, двох невеликих невірних прямокутників у протилежних кутках вікна призведе до оголошення всій внутрішній області потребує перемальовуванні. Часто замість прямокутників ефективніше використовувати області складної форми і, відповідно, регіони (region) як об'єкти, що описують області складної форми.

Регіон є об'єктом GDI, на нього поширюються всі правила застосування об'єктів GDI. У Windows описаний набір функцій, призначений для створення регіонів, форма яких відповідає основним примітивам GDI, і, крім того, функцію CombineRgn, яка дозволяє з декількох регіонів простої форми побудувати один регіон складної форми.

Ми можемо створювати прямокутні регіони, прямокутні з округленими кутами, еліптичні та регіони у вигляді багатокутників. Для цього призначені наступні функції:

HRGN CreateRectRgn (xLeft, yTop, xRight, yBottom);

HRGN CreateRectRgnIndirect (lpRect);

HRGN CreateRoundRectRgn (xLeft, yTop, xRight, yBottom, xRound, yRound);

HRGN CreateEllipticRgn (xLeft, yTop, xRight, yBottom);

HRGN CreateEllipticRgnIndirect (lpRect);

HRGN CreatePolygonRgn (lpPoints, nCount, nPolyFillMode);

HRGN CreatePolyPolygonRgn (lpPoints, lpCounts, nPolyCount, nPolyFillMode);

Аргументи цих функцій подібні аргументів функцій, що здійснюють малювання аналогічних фігур, тому розглядати їх тут не будемо. Параметр nPolyFillMode аналогічний відповідного атрибуту контексту пристрою - режиму заповнення багатокутників.

У результаті виклику однією з функцій Create ... Rgn створюється спеціальний об'єкт, що описує регіон, а нам повертається хендл цього об'єкта.

Як і будь-який об'єкт GDI регіон віддаляється за допомогою функції DeleteObject.

Однією з найцікавіших особливостей регіонів є можливість комбінування декількох регіонів в один, більш складний. Це робиться за допомогою функції:

int CombineRgn (hrgnDest, hrgnSrc1, hrgnSrc2, nMode);

Ця функція дозволяє виконати певну параметром nMode операцію над двома (або одним) вихідними регіонами і результат записати в третій регіон. При цьому новий регіон не створюється, ви повинні заздалегідь створити якийсь регіон і його хендл передати в якості hrgnDest. У цьому регіоні буде розміщений результат виконання операції. Таке, на перший погляд дивне, правило дозволяє дещо зменшити кількість створюваних об'єктів.

Отже, за допомогою функції CombineRgn, ми можемо виконувати різні операції, задаючи номер потрібної операції в параметрі nMode:

RGN_AND

- Отримати перетин двох регіонів (точки, що входять в обидва регіону одночасно)

RGN_OR

- Отримати об'єднання регіонів (точки, що входять хоча б в один із двох регіонів)

RGN_XOR

- Отримати об'єднання без перекриваються областей

RGN_DIFF

- Отримати частину першого регіону, що не входить у другій регіон

RGN_COPY

- Скопіювати перший регіон (другий регіон не використовується)

При цьому функція повертає інформацію про те, який регіон отримано:

SIMPLEREGION

- Якщо підсумковий регіон складається з не перекриваються примітивів

COMPLEXREGION

- Якщо примітиви, що входять у підсумковий регіон, перекриваються

NULLREGION

- Підсумковий регіон порожній (не має спільних точок)

ERROR

- Виникла помилка (наприклад, недостатньо пам'яті)

У заголовному файлі windowsx.h включено кілька макросів, заснованих на функції CombineRgn:

int CopyRgn (hrgnDest, hrgnSrc), 2

int IntersectRgn (hrgnDest, hrgnSrc 1, hrgnSrc 2), 2

int SubtractRgn (hrgnDest, hrgnSrc 1, hrgnSrc 2), 2

int UnionRgn (hrgnDest, hrgnSrc 1, hrgnSrc 2), 2

int XorRgn (hrgnDest, hrgnSrc 1, hrgnSrc 2), 2

Існує ще одна функція, яка може змінити тип регіону, вона дозволяє замінити вказаний вами будь-який регіон на регіон прямокутної форми:

void SetRectRgn (hrgnSrc, lpRect);

Таким чином, застосовуючи функції створення регіонів і їх комбінуючи ми можемо описати області дуже складної форми. Тепер нам треба розібратися з основними способами застосування регіонів.

По-перше, ми можемо застосовувати регіони як абстрактні об'єкти, і виконувати над ними будь-які операції, наприклад переміщення, аналогічно операціям над прямокутниками:

int OffsetRgn (hrgnSrc, nDeltaX, nDeltaY);

або перевіряти збіг регіонів:

BOOL EqualRgn (hrgnSrc1, hrgnSrc2);

Крім того ми можемо перевірити приналежність точки або прямокутника регіону:

BOOL PtInRegion (hrgnSrc, nX, nY);

BOOL RectInRegion (hrgnSrc, lpRect);

І ще одна функція дозволяє отримати прямокутник, описаний навколо зазначеного регіону:

int GetRgnBox (hrgnSrc, lpRect);

По-друге, регіони можуть відображатися на контексті пристрою, наприклад для зафарбовування областей або обведення контуру області складної форми:

BOOL InvertRgn (hDC, hrgnSrc);

BOOL PaintRgn (hDC, hrgnSrc);

BOOL FillRgn (hDC, hrgnSrc, hbrBrush);

BOOL FrameRgn (hDC, hrgnSrc, hbrBrush, nFrameWidth, nFrameHeight);

Функція InvertRgn здійснює операцію BITWISE NOT над всіма точками, що входять в зазначений регіон; вона аналогічна функції InvertRect. Функція PaintRgn зафарбовує регіон поточної пензлем. Вона подібна до функції FillRgn, яка зафарбовує регіон зазначеної вами, а не поточної, пензлем. Найцікавіша функція - FrameRgn, яка проводить навколо регіону облямівку зазначеної ширини і зазначеної пензлем. Тобто ця функція аналогічна функції FrameRect, за винятком того, що область може бути складної форми і ви можете задати ширину облямівки, причому як по горизонталі, так і по вертикалі.

Малюнок 18. Застосування регіонів для зафарбовування і областей та обведення області контуром.

По-третє, ще один зі способів застосування регіонів пов'язаний з обробкою повідомлення WM_PAINT. Раніше ми говорили про те, що повідомлення WM_PAINT генерується, коли біля вікна з'являється невірний прямокутник.

Це не зовсім точно - замість невірного прямокутника зазвичай використовується регіон. Аналогічно прямокутниках, у вас є дві функції, одна з яких оголошує невірний регіон, а інша вказує, що регіон став вірним:

void InvalidateRgn (hWnd, hrgnSrc, fEraseBkgnd);

void ValidateRgn (hWnd, hrgnSrc);

Якщо повідомлення WM_PAINT генерується в результаті появи невірного регіону, то отриманий за допомогою функції BeginPaint контекст пристрою може застосовуватися тільки для малювання всередині невірного регіону.

По-четверте, регіон, будучи об'єктом GDI, може бути обраний у контекст пристрою. Регіон, обраний в контекст пристрою, визначає область цього контексту, на якій можливо малювання. При цьому він є як би "маскою" через яку видно рисуемой зображення.

Малюнок 18. Вихідне зображення (ліворуч), регіон (в центрі) і намальоване зображення (Праворуч). Світло-сірим кольором показаний незмінний даними малюнком фон.

Для того, що б вибрати регіон в контекст пристрою, ви повинні скористатися функцією

int SelectClipRgn (hDC, hrgnSrc);

Ця функція повертає ціле число, яке вказує тип обраного регіону (SIMPLEREGION, COMPLEXREGION, NULLREGION). При виборі регіону в контекст пристрою він копіюється, тому ви можете видалити його або використовувати іншим чином одразу після вибору в контекст пристрою.

Крім функції SelectClipRgn ви можете скористатися функцією SelectObject з тією ж метою. При цьому функція SelectObject буде використовуватися точно також, як і функція SelectClipRgn, і поверне не хендл попереднього регіону, а тип обраного вами.

Растрові зображення і метафайли

У Windows існує можливість зберігати зображення у вигляді картинок, які зберігали малюнок у вигляді інформації про квіти окремих точок. Такі зображення іноді називаються растровими, тому що інформація про колір точок групується по рядках растра зображення, або бітмапами (bitmap), іноді термін bitmap навіть переводять дослівно - бітова карта. У ранніх версіях Windows бітмапами точно відповідали образу в графічній пам'яті пристрою, на який здійснювався висновок. При цьому інформація про передачу кольорів відповідала в точності формату колірної пам'яті пристрою. Такі бітмапами отримали назву залежних від пристрою бітмапами (device-depended bitmap, DDB)

Так, наприклад, для чотириколірних відеоадаптерів CGA кожен піксель кодувався двома послідовними бітами в відеопам'яті - такий-же була організація бітмапами, що відображаються на дисплеї. А якщо використовувався 16 ти-кольоровий адаптер EGA, в якому для завдання кожного пікселя потрібно поставити 4 біта лежать в різних колірних площинах (planes), то й бітмапами створювався аналогічно - кожен рядок растра була представлена ​​4 рази, для кожної колірної площині по разу. Безсумнівним достоїнством таких зображень була простота їх відображення на кінцевому пристрої і висока швидкість виведення.

Але були й недоліки - головний з них - колір конкретної точки визначається безпосередньо налаштуваннями апаратури і залишається незалежний від самого растрового зображення. Тобто спроба відобразити одне і те ж зображення при різних настройках (допустимо, при різних використовуваних палітрах), приводила до спотворення кольорів вихідного зображення. Крім цього при спробі відобразити одне і теж зображення на різних пристроях було потрібно перетворювати інформацію про кодування кольорів - що знову ж вимагало додаткових даних про призначення квітів на обох пристроях.

Все це призвело до того, що був розроблений новий стандарт зберігання растрових зображень - так звані незалежні від пристрою бітмапами (device-independed bitmap, DIB). Цей бітмапами відрізняється від DDB як фіксованим способом кодування кольору кожної точки - послідовної групою біт - так і наявністю інформації про призначення квітів - так званої палітри (palette) - чи іншої інформації, що дозволяє визначити точне призначення квітів кожної точки.

Починаючи з Windows 3. X всі бітмапами, представлені у вигляді файлів або ресурсів програми, є незалежними від пристрою бітмапами (DIB), в той час як після завантаження в пам'ять програми ці бітмапами можуть бути представлені як у вигляді незалежних від пристрою, так і в вигляді залежних - дивлячись за способом завантаження та використання.

Говорячи про бітмапами треба виділити кілька обговорюваних аспектів, що вирішуються для кожного виду бітмапами своїм способом;

отримання бітмапами

формування або корекція зображення

відображення бітмапами

збереження незалежних від пристрою бітмапами у файлах

Для залежних і для незалежних від пристрою бітмапами ці завдання вирішуються різними методами з залученням різних функцій і інструментів. Використовувати один вид бітмапами у функціях, призначених для роботи з іншим видом бітмапами, неможливо. У наступних розділах ці завдання будуть обговорені більш докладно, тут же буде намічена загальна схема розв'язання цих завдань для кожного виду бітмапами. У деяких випадках бітмапами можна застосовувати іншими способами - наприклад для створення кистей, передачі зображення через буфер обміну (clipboard) і так далі.

Крім растрових зображень (залежних і незалежних від пристрою бітмапами) в Windows передбачений ще один спосіб збереження зображень - збереження малюнка в метафайлах.

Огляд залежних від пристрою бітмапами (DDB)

Залежний від пристрою бітмапами (DDB) є об'єктом GDI і робота з ним здійснюється також, як і звичайними об'єктами GDI - пір'ям, кистями і іншим. Говорячи про ідентифікацію залежного від пристрою бітмапами говорять про його хендла - хендла об'єкта GDI. Більше того, так як незалежні від пристрою бітмапами не є об'єктами GDI, то вони також не мають специфічних хендлов. Як тільки в тексті згадується деякий хендл бітмапами (HBITMAP), то можна однозначно стверджувати, що мається на увазі залежний від пристрою бітмапами, DDB.

Більш того, залежні від пристрою бітмапами в реальному житті представлені саме як об'єкти GDI, тому що у всіх сучасних версіях Windows зображення зберігаються у вигляді незалежних від пристрою бітмапами. Тобто будь-який зберігається на диску (у вигляді файлу або ресурсів програми) бітмапами - завжди незалежний від пристрою, і тільки після його завантаження в пам'ять у вигляді об'єкта GDI він стане залежним від пристрою бітмапами.

Звичайно, незалежні від пристрою бітмапами можуть бути завантажені в пам'ять безпосередньо, однак часто для подання в пам'яті використовуються саме DDB, так як його відображення виконується швидше і він як правило займає менше ресурсів.

Отримання бітмапами;

Для отримання хендла бітмапами ви можете або створити новий об'єкт і отримати його хендл, або завантажити вже наявне зображення. Так як у всіх сучасних версіях Windows бітмапами реально зберігаються у вигляді незалежних від пристрою, то для завантаження зображення у вигляді DDB треба здійснити перетворення незалежного бітмапами в залежний. Це робиться або автоматично - при завантаженні бітмапами з ресурсів програми, або це треба здійснити безпосередньо у вашому додатку.

Формування чи корекція бітмапами;

Для малювання на бітмапами створюється контекст пристрою, асоційований з даним бітмапами, після чого всі функції GDI, що застосовуються до цього контексту, реально взаємодіють з бітмапами і формують його зображення. Для виконання цього завдання призначений сумісний контекст пристрою (compatible DC, memory DC), який призначений саме для цього.

Відображення бітмапами на контексті пристрою;

Врешті-решт все і затівається заради можливості відобразити бітмапами у віконці або на аркуші паперу. GDI не містить спеціальних функцій для відображення DDB. Замість цього ви повинні асоціювати бітмапами з контекстом пристрою (як і для малювання), а потім здійснити передачу зображення з одного контексту на інший - для цього в GDI міститься спеціальний набір функцій, які називаються функціями передачі растрових зображень або, дослівно, функціями для передачі блоків біт (Bit Block Transfer, BLT - вимовляється «Бліт»)

Огляд незалежних від пристрою бітмапами (DIB)

Незалежний від пристрою бітмапами (DIB) об'єктом GDI не є. Він завантажується у вигляді одного або декількох блоків даних (детальніше про роботу з блоками пам'яті див у розділі "Диспетчер пам'яті»), його ідентифікують або покажчики на ці дані, або хендл блоку пам'яті, в якому цей бітмапами розташовується (це залежить від застосовуваних функцій ). Якщо бітмапами зберігається на диску (у вигляді окремого файлу або у вигляді ресурсу додатка), то це неодмінно DIB.

Після завантаження в пам'ять DIB може бути представлений як безпосередньо у вигляді незалежного від пристрою бітмапами, так і він може бути перетворений в DDB. У тому випадку, якщо його подання відповідає DIB, то він реалізований у вигляді декількох взаємопов'язаних структур даних, що описують інформацію про бітмапами (заголовок і палітра) і безпосередньо зображення. Іноді ці структури розміщуються в різних областях даних (тоді бітмапами ідентифікується покажчиками на ці дані), а іноді в одній - в цьому випадку структура цієї області даних приблизно відповідає вмісту файлу з DIB, але без заголовка файлу - таке уявлення називається упакований DIB (packed DIB ). В останньому випадку для ідентифікації незалежного від пристрою бітмапами може вистачити одного хендла блоку, що містить упакований DIB, однак цей хендл не є хендлов бітмапами (HBITMAP).

При роботі з DIB слід вибрати один з трьох шляхів:

1) Працювати з DIB безпосередньо. Для цього призначений досить великий набір функцій, реалізований і в Windows API і в Win 32 API.

Отримання бітмапами з файлу або ресурсу програми;

Якщо бітмапами представлений у вигляді файлу, то його завантаження необхідно виконати самостійно; на щастя ця процедура може бути зведена всього до кількох рядках вихідного коду. Можна, звичайно, скористатися функціями LoadImage 1 або LoadBitmap, але це призведе до отримання DDB, причому з організацією, що відповідає дисплею і відповідного поточної вибраної палітрі. Це може бути незручно, особливо при необхідності цей бітмапами пізніше вивести на друк - для цього краще скористатися або самим DIB, або DDB, створеним під характеристики і під палітру, застосовувану на пристрої відображення. Таку операцію можна виконати, використовуючи замість функції LoadBitmap пару функцій FindResource і LoadResource, що дозволить отримати безпосередньо сам DIB. (Замість LoadImage треба прочитати файл з бітмапами).

Збереження бітмапами у файлі;

Цю операцію треба виконати самостійно, розібравшись зі структурами, що описують DIB, і записавши їх у файл у необхідному порядку.

Відображення бітмапами на контексті пристрою;

На відміну від DDB для DIB передбачені спеціальні функції, що передають зображення бітмапами на вказаний контекст пристрою, близькі до функцій передачі растрових зображень між контекстами пристроїв. На відміну від DDB для DIB не потрібно попередньо пов'язувати бітмапами з контекстом пристрою, ці функції здійснюють перенесення зображення безпосередньо із завантаженого в пам'ять DIB на контекст пристрою.

2) Працювати зі створюваним проміжним DDB. При цьому DIB зчитується і записується також, як і у випадку (1), проте далі він перетвориться в DDB з яким і здійснюється все необхідна робота. Цей варіант є одним з найбільш ефективних, хоча і громіздких - він дозволяє отримати звичайний бітмапами з організацією, найбільш точно відповідній пристрою відображення, що дозволяє як заощадити ресурси, так і прискорити процес виведення.

3) Створити асоціацію DIB з контекстом пристрою. Цей шлях схожий на вибір звичайного бітмапами в контекст пристрою за допомогою функції SelectObject, однак здійснюється іншими засобами і різним чином в різних API (Windows або Win 32).

У разі 16 ти розрядної платформи Windows 3. X ви можете використовувати спеціальний DIB драйвер, що поставляється в складі багатьох компіляторів

У разі Win 32 ви можете використовувати так звану DIB-секцію, яка є своєрідним гібридом звичайного бітмапами (для неї повертається HBITMAP, що дозволяє застосовувати її як об'єкт GDI) і незалежного від нього - в пам'яті вона представлена ​​як нормальний DIB.

Огляд метафайлів

Альтернативний метод збереження зображень представлений у вигляді метафайлів (metafile). Метафайл в строгому сенсі малюнка не зберігає, він зберігає тільки послідовність команд (викликів функцій GDI) формують зображення.

Для роботи з метафайлах в Windows міститься достатньо повний набір функцій, практично виключає необхідність робити що-небудь самостійно. Спільні операції над метафайлах можна розділити на операції по створенню метафайлу, тобто його записи та операції з відтворення метафайлу на потрібному контексті пристрою.

Створення метафайлів;

Для створення метафайлу створюється спеціальний контекст пристрою; всі подальші операції з малювання на цьому контексті запам'ятовуються і зберігаються у вигляді метафайлу. При видаленні контексту пристрою, пов'язаного з метафайл, створюється об'єкт GDI, що представляє даний метафайл. Цей метафайл може бути представлений або в оперативній пам'яті, або у вигляді файлу - залежно від того, як ви створювали контекст пристрою метафайлу. Ніяких спеціальних кроків для запису метафайлу на диск робити не треба.

Відтворення метафайлу;

Для відтворення метафайлу призначена спеціальна функція, яка послідовно виконує всі збережені в метафайлу команди на зазначеному контексті пристрою. Для її застосування необхідно вказати хендл метафайлу (об'єкта GDI), який повертається або при видаленні контексту пристрою, або при виклику спеціальної функції, завантажування метафайл з диска в оперативну пам'ять.

У якійсь мірі можна порівняти метафайл з мікропрограмою, що здійснює виведення зображення.

Залежні від пристрою бітмапами

Звичайний, залежний від пристрою бітмапами є простим чином відеопам'яті. Його організація відображає особливості апаратури, на якій він повинен відтворюватися. DDB у додатку представляється як об'єкт GDI; аналогічно опису пір'я або кистей DDB описується за допомогою спеціальної структури (BITMAP) і доступний за допомогою хендла цього об'єкта типу (HBITMAP).

typedef struct tagBITMAP {

int bmType;

int bmWidth;

int bmHeight;

int bmWidthBytes;

BYTE bmPlanes;

BYTE bmBitsPixel;

LPSTR bmBits;

} BITMAP;

Поле bmType має бути 0, поля bmWidth і bmHeight визначають розміри зображення, bmPlanes і bmBitsPixel використовуються для вказівки способу кодування інформації про колір точки і для вказівки максимальної кількості кольорів.

Використання двох полів bmPlanes і bmBitsPixel пов'язано з особливостями зберігання кольорового зображення різними відеоадаптерами. Наприклад, CGA, IBM 8514 або SVGA в деяких режимах для завдання кольору пікселя відводять кілька послідовних біт пам'яті (CGA - 2 біти, IBM - 8 біт, SVGA - до 32 біт на кожен піксель). А адаптери типу EGA або VGA (і, звичайно, в деяких режимах SVGA) містять декілька так званих бітових площин, або планів (planes). У кожному плані одному пікселю відповідає тільки один біт, а колір задається комбінацією біт в різних планах. Поле bmPlanes структури BITMAP визначає кількість колірних планів, а поле bmBitsPixel - кількість послідовних біт, відведених для завдання кольору пікселя а одному плані. Принаймні один з цих параметрів дорівнює 1 (або обидва - для монохромних бітмапами).

Зображення в бітмапами зберігається розділеним на рядки растра (scan line). Довжина кожного рядка округлюється в більшу сторону до найближчої парної кордону (кратна 2 байтам) і задається полем bmWidthBytes. Для обчислення довжини рядка треба твір bmWidth * bmBitsPixel розділити на 8 і округлити в бік завищення до найближчого парного числа.

Якщо бітмапами містить кілька колірних планів, то рядки різних колірних планів одного рядка растру розміщуються послідовно один за одним, потім для іншої рядки і так далі.

Поле bmBits теоретично має вказувати на власне дані бітмапами (масив рядків растра). Однак воно використовується далеко не завжди - ви його можете задавати, але коли інформацію про бітмапами вам повертає система (функція GetObject), то це поле не ініціалізується - для отримання даних бітмапами існує спеціальна функція - GetBitmapBits.

Створення залежного від пристрою бітмапами

Створення залежних від пристрою бітмапами - випадок порівняно рідкісний; зазвичай бітмапами готуються спеціальним графічним редактором і пізніше завантажуються в пам'ять або з файлу, або з ресурсів програми. У зв'язку з цим запропонований тут матеріал потрібен, в основному, для більш близького «фамільярного» знайомства з DDB; на практиці використовуватися цей матеріал буде рідко. Припустимо, що ми хочемо створити бітмапами для використання в якості пензля. Кисть завжди має розмір 8x8 пікселів. Крім того, для спрощення, ми будемо припускати, що використовується монохромний бітмапами. Монохромний - так як зображення цього бітмапами ми опишемо самі, а опис кольорового бітмапами безпосередньо в додатку - вкрай неефективне рішення. Крім того, монохромні бітмапами обробляються кілька особливим чином, так що є привід звернути увагу на їх особливості. Отже, нехай ми хочемо отримати наступну картинку:

Малюнок 18. Підготовлюваний малюнок монохромного бітмапами (при використанні такого бітмапами в якості пензля ми отримаємо "цегляну стіну").

Зверніть увагу на нумерацію байтів і біт у байті - байти (не слова) нумеруються зліва-направо, а біти в байті - справа-наліво. Підготуємо для цієї картинки дані бітмапами: оскільки ширина бітмапами 8, кількість біт на піксель 1, то в одному рядку растра має бути 8 біт, значить її довжина 2 байти (парне число) або одне слово.

static WORD wBits [] = {

0x00FF, 0x00C0, 0x00C0, 0x00C0, 0x00FF, 0x000C, 0x000C, 0x000C};

Далі ми можемо створити бітмапами, що містить цю картинку. Для цього можна скористатися однією з наступних функцій:

HBITMAP CreateBitmap (cxWidth, cyHeight, nPlanes, nBitsPixel, lpBits);

HBITMAP CreateBitmapIndirect (lpBitmap);

HBITMAP CreateCompatibleBitmap (hDC, cxWidth, cyHeight);

HBITMAP CreateDiscardableBitmap (hDC, cxWidth, cyHeight);

Функція CreateBitmap дозволяє створити бітмапами із заданими характеристиками. У нашому випадку це буде виглядати так:

HBITMAP hBmp = CreateBitmap (8, 8, 1, 1, wBits);

Функція CreateBitmapIndirect дозволяє спочатку описати структуру типу BITMAP, а потім створити бітмапами по цій структурі. У нашому прикладі цю функцію можна використовувати, наприклад, таким чином:

static BITMAP stBmp = {

0, / / bmType

8,8, / / bmWidth, bmHeight

2, / / bmWidthBytes

1,1, / / bmPlanes, bmBitsPixel

NULL / / bmBits};

HBITMAP hBmp = CreateBitmapIndirect (& stBmp);

SetBitmapBits (hBmp, sizeof (wBits), wBits);

Звичайно, ми могли встановити адресу образу (wBits) у полі bmBits і обійтися без функції SetBitmapBits але так ми розглянемо на одну функцію більше.

У деяких випадках ми будемо створювати бітмапами, які повинні бути за своїми характеристиками сумісні з конкретним контекстом пристрою. Для цього призначена функція CreateCompatibleBitmap, яка створює бітмапами зазначеного розміру і такий же організації, як зазначений контекст пристрою. Зображення при цьому не задається - бітмапами містить набір випадкових даних. Пізніше ви можете скористатися, наприклад, функцією SetBitmapBits для завдання даних бітмапами.

Увага! При створенні сумісного бітмапами треба вказувати контекст реального пристрою, сумісність з яким потрібно. Якщо вказати сумісний контекст пристрою в якості прототипу, то буде створений монохромний бітмапами.

Функція CreateDiscardableBitmap використовується вкрай рідко, так як створюється за її допомогою бітмапами може бути видалений з пам'яті диспетчером при необхідності.

Незалежно від способу створення, існує одна особливість у застосуванні монохромних бітмапами: при його відображенні на кольоровому пристрої для представлення точок бітмапами будуть використовуватися не чорний і білий кольори, а поточний колір фону (для всіх біт, рівних 0, - зазвичай це білий колір) і поточний колір тексту (для всіх біт, рівних 1, зазвичай це чорний колір). Таким чином ви можете в певній мірі керувати відображенням монохромного бітмапами за допомогою функцій SetTextColor і SetBkColor.

При необхідності ви можете дізнатися характеристики бітмапами, використовуючи функцію GetObject:

BITMAP stBmp;

GetObject (hBmp, sizeof (stBmp), & stBmp);

Однак ця функція не встановлює поле bmBits структури BITMAP. Для отримання даних бітмапами треба скористатися функцією:

LPSTR GetBitmapBits (hBitmap, dwMaxSize, lpBuffer);

Ще кілька функцій можуть використовуватися абсолютно специфічним способом:

DWORD GetBitmapDimension (hBmp);

BOOL GetBitmapDimensionEx (hBmp, lpSize);

DWORD SetBitmapDimension (hBmp, nWidth, nHeight);

BOOL SetBitmapDimensionEx (hBmp, nX, nY, lpSize);

Ці процедури використовуються для завдання / отримання довідкового розміру бітмапами, в одиницях по 0.1 мм. Ніякі інші функції GDI не використовують цю інформацію при роботі з бітмапами. Практично ви можете використовувати ці розміри самі при необхідності передачі бітмапами між пристроями з різною роздільною здатністю.

Отримання залежних від пристрою бітмапами як ресурсів програми

Створення бітмапами безпосередньо в додатку - випадок порівняно рідкісний. Зазвичай бітмапами малюються за допомогою будь-якого графічного редактора і потім завантажуються з файлу (в цьому випадку завантажується не DDB, а DIB), або бітмапами додається в ресурси програми і потім завантажується з ресурсів за допомогою функції LoadBitmap. У цьому випадку виконується завантаження саме у вигляді залежного від пристрою бітмапами, хоча в ресурсах додатка розміщується DIB.

Цікавий нюанс - так як бітмапами з ресурсів програми завантажується як залежний від пристрою, то якого пристрою будуть відповідати його характеристики? Microsoft вважає, що такі бітмапами будуть відображатися переважно на дисплеї, і, отже, DDB буде будуватися під характеристики дисплея. Вказати, для якого пристрою бітмапами треба оптимізувати неможливо. У більшості випадків цей підхід цілком прийнятний, однак при необхідності здійснювати вивід бітмапами не тільки у вікні, а й на інших пристроях (наприклад на принтері або включати його в метафайл), краще отримати доступ безпосередньо до самого DIB, записаному в ресурсах додатки (про це - у розділі «»).

Детальніше про застосування та описі ресурсів програми - див розділ «Ресурси додатки», тут же будуть представлені основні відомості про опис бітмапами в якості ресурсів програми. Для опису бітмапами у файлі опису ресурсів прийнята наступна форма:

nameId

унікальне ім'я або номер ресурсу

load-opt 0

режим завантаження ресурсу: PRELOAD або LOADONCALL (за замовчуванням)

mem-opt 0

тип виділюваної пам'яті: FIXED, MOVEABLE (за замовчуванням) або DISCARDABLE

filename.bmp

ім'я файлу, що містить бітмапами

nameId BITMAP [load - opt] [mem - opt] filename. Bmp (деякі редактори і компілятори ресурсів, як, скажімо, Borland WorkShop, дозволяють описувати бітмапами безпосередньо у файлі опису ресурсів у текстовому вигляді. Тоді замість filename.bmp використовуються структурні дужки BEGIN. .. END або {...} до укладеного між ними даними бітмапами у вигляді списку шістнадцяткових чисел. Цей спосіб не гарантує переносимість ресурсів між різними середовищами розробки додатків.)

Часто режим завантаження ресурсу і тип виділюваної пам'яті при описі бітмапами не вказується - пропоновані за умовчанням значення (завантаження на вимогу і переміщуваний блок пам'яті) як правило є оптимальними. Приклади опису бітмапами:

red_brick BITMAP rbrick.bmp

1 BITMAP firm.bmp

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

Для завантаження бітмапами з ресурсів програми використовуються функції:

HBITMAP LoadBitmap (hInstance, lpszName);

HANDLE LoadImage (hInstance, lpszName, uType, cxDesired, cyDesired, fuLoad); 1

де hInstance - хендл копії додатку, що містить даний бітмапами, а lpszName - ім'я ресурсу бітмапами. Ім'я ресурсу може бути або текстом - тоді lpszName це звичайна рядок, що закінчується символом '\ 0', або номером - тоді замість lpszName може стояти або «# number», або MAKEINTRESOURCE (number). Наприклад, для завантаження бітмапами «red_brick» і «1» можна скористатися такими викликами функцій:

HBITMAP hbmpRedBrick = LoadBitmap (hInstance, "red_brick");

HBITMAP hbmp1a = LoadBitmap (hInstance, "# 1");

HBITMAP hbmp1b = LoadBitmap (hInstance, MAKEINTRESOURCE (1));

Причому останній варіант є найшвидшим і компактним.

Функція LoadImage здійснює завантаження бітмапами, піктограм і курсорів. Теоретично вона дозволяє завантажувати необхідний ресурс з файлу (для цього в fuLoad треба встановити прапор LR_LOADFROMFILE і вказати hInstance рівним NULL). Однак така операція підтримується тільки у разі Windows -95, Windows NT 4.0 і більш пізніх. Попередні реалізації Win 32 API не підтримують завантаження зображень з файлів.

Ви можете використовувати стандартні бітмапами, що надаються Windows. Їх символічні імена починаються на OBM_ .... Для того, що б ви могли скористатися цими ідентифікаторами, необхідно перед директивою # include <windows.h> визначити символ OEMRESOURCE, тобто:

# Define OEMRESOURCE

# Include <windows.h>

У таблиці наведені зображення стандартних бітмапами та їх ідентифікатори, у відповідності до їх реалізації в Windows 3.x (Windows API) і Windows NT 3.x (Win32 API); в більш пізніх версіях (як, наприклад, Windows-95, Windows NT 4.0) зовнішній вигляд стандартних бітмапами дещо змінений. У таблиці заповнені не всі клітини просто з міркувань порядкової угруповання схожих бітмапами.

OBM_UPARROW

OBM_UPARROWI

OBM_UPARROWD

OBM_OLD_UPARROW

OBM_DNARROW

OBM_DNARROWI


OBM_DNARROWD

OBM_OLD_DNARROW

OBM_RGARROW

OBM_RGARROWI

OBM_RGARROWD

OBM_OLD_RGARROW

OBM_LFARROW

OBM_LFARROWI

OBM_LFARROWD

OBM_OLD_LFARROW

OBM_REDUCE



OBM_REDUCED

OBM_OLD_REDUCE

OBM_ZOOM



OBM_ZOOMD

OBM_OLD_ZOOM

OBM_RESTORE



OBM_RESTORED

OBM_OLD_RESTORE

OBM_CLOSE





OBM_OLD_CLOSE


OBM_MNARROW

OBM_COMBO

OBM_SIZE

OBM_BTSIZE

OBM_CHECK

OBM_BTNCORNERS



OBM_CHECKBOX

Робота з залежним від пристрою бітмапами

Невелике зауваження: так як бітмапами є об'єктом GDI, то ви зобов'язані видалити його, як тільки він стане непотрібним. Це відноситься до всіх бітмапами, як створеним за допомогою функцій CreateBitmap, CreateBitmapIndirect, CreateCompatibleBitmap, CreateDiscardableBitmap, так і до завантаженим за допомогою функції LoadBitmap. Звільнення невикористовуваних бітмапами особливо важливо, тому що це навряд-чи не найбільші об'єкти GDI, що займають значні ресурси.

У GDI практично не міститься функцій, що використовують залежні від пристрою бітмапами безпосередньо. Виняток, хіба що, функції для створення кисті за зразком, для завдання графічного образу пункту меню або для передачі зображення на незалежний від пристрою бітмапами (докладніше див у відповідних розділах):

HBRUSH hbrBrush = CreatePatternBrush (hBmp);

DeleteBitmap (hBmp); / / 2

Після того, як ми створили пензлик, бітмапами можна видаляти, тому що його образ скопійований в кисть і більше не використовується. Якщо бітмапами більше, ніж 8x8 пікселів, то для створення кисті буде використаний його верхній-лівий куточок, розміром 8x8.

Всі інші операції по роботі з бітмапами здійснюються за допомогою спеціально створюваного контексту пристрою, асоційованого з цим бітмапами. Для цього був розроблений спеціальний вид контекстів пристрої - сумісний контекст (compatible device context, compatible DC, частіше званий memory device context, memory DC). Такий різнобій у назвах контексту пов'язаний, з одного боку, з назвою функції, його створює - CreateCompatibleDC - створює контекст пристрою, сумісного з іншим, реально існуючим пристроєм (див. розділ «»). А, з іншого боку, створений таким чином контекст пристрою не відповідає ніякому фізичному пристрою, його область відображення - деякий растрове зображення, збережене в пам'яті. Звідси друга назва - memory DC.

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

Тільки що створений сумісний контекст пристрою має монохромну область відображення розміром 1 піксель. Так як намалювати що-небудь осмислене в такій області нереально, ви повинні вжити заходів до того, що б область відображення цього контексту була асоційована з збереженим у пам'яті растровим зображенням - залежним від пристрою бітмапами. Це робиться тривіально - бітмапами вибирається в сумісний контекст пристрою за допомогою звичайної функції SelectObject. Після цього область відображення сумісного контексту буде збігатися з вказаним бітмапами - як за розмірами, так і за кольоровою організації.

Увага! GDI припускає, що бітмапами може бути вибраний тільки в сумісний контекст пристрою. Якщо його вибрати в який-небудь контекст реально існуючого устрою, то швидше за все така спроба буде просто проігноровано, хоча залежно від платформи і використовуваних драйверів пристроїв, реакція системи може бути і іншою.

Загальна схема при цьому виглядає наступним способом:

HDC hCompatDC;

HBITMAP hBmp;

hCompatDC = CreateCompatibleDC (hDC);

/ / Функція CreateCompatibleDC () створює сумісний

/ / Контекст пристрою, що відповідає одному монохромного пікселу

hBmp = LoadBitmap (hInstance, lpszName);

/ / Для отримання хендла бітмапами ми могли скористатися будь-яким

/ / Способом - його завантаженням з ресурсів або створенням

SelectObject (hCompatDC, hBmp);

/ / Тепер сумісний контекст пристрою відповідає нашому бітмапами.

/ / ... тут ми можемо виконувати будь-які операції з малювання на нашому бітмапами

/ / ... або передавати зображення між різними контекстами пристроїв.

DeleteDC (hCompatDC);

/ / Після того, як ми виконали всі потрібні операції над контекстом

/ / Пристрої, ми можемо його видалити.

/ / ... При цьому бітмапами як об'єкт GDI залишається і ми можемо вільно

/ / ... застосовувати його хендл. Наприклад, для створення кисті, або для

/ / ... відображення пункту меню.

DeleteObject (hBmp);

/ / Після того, як бітмапами став нам не потрібний, ми можемо його знищити

Дуже часто зустрічається окремий випадок цієї схеми: при створенні нового зображення бітмапами робиться сумісним за колірною організації з тим пристроєм, на якому він буде відображатися. У цьому випадку створення бітмапами виглядає так:

HDC hCompatDC;

HBITMAP hBmp;

hCompatDC = CreateCompatibleDC (hDC);

hBmp = CreateCompatibleBitmap (hDC, 500, 300)

SelectObject (hCompatDC, hBmp);

PatBlt (hCompatDC, 0,0, 500,300, PATCOPY);

/ / ... тут ми можемо виконувати будь-які операції з малювання на нашому бітмапами

/ / ... або передавати зображення між різними контекстами пристроїв.

DeleteDC (hCompatDC);

/ / ... Працюємо з бітмапами як з об'єктом GDI

DeleteObject (hBmp);

У цьому прикладі треба відзначити два моменти: По-перше, при створенні бітмапами як прототип задається обов'язково контекст реального пристрою (із заданою колірної організацією), а не сумісного (який відповідає одному монохромного пікселю). Бітмапами, сумісний з сумісним контекстом пристрою буде монохромним! По-друге, створений сумісний бітмапами містить довільні дані, тому перед його використанням зображення треба очистити. У цьому прикладі функція PatBlt зафарбовує бітмапами поточної пензлем (операція PATCOPY), іноді для початкової зафарбовування використовують не поточну кисть (за замовчуванням - WHITE_BRUSH може бути не білою), а білий або чорний кольори (операції WHITENESS, BLACKNESS). Це залежить від подальшого використання: фон бітмапами повинен збігатися з фоном вікна або повинен бути конкретного кольору.

Ця схема дійсно зручна, якщо бажано, що б колірна організація бітмапами відповідала колірної організації пристрою, на якому він буде відображатися. Як правило це так і є, крім порівняно рідкісних випадків застосування монохромних бітмапами.

До тих пір, поки бітмапами залишається обраний в сумісний контекст пристрою, ви можете застосовувати всі функції GDI для малювання і корекції зображення. Але одна з найпотрібніших завдань - відображення бітмапами на потрібному пристрої - залишається невирішеною. Спеціальних функцій для відображення залежних від пристрою бітмапами на контексті пристрою в GDI немає, проте передбачено більш універсальний і потужний механізм, що забезпечує виконання цього завдання - механізм передачі растрових зображень між контекстами пристроїв.

Операції передачі образів

Розглядаючи застосування бітмапами ми звернули увагу на спеціальний механізм, що здійснюють передачу растрових зображень між різними контекстами пристроїв. Цей механізм називається операції з обміну блоками біт (b it b l ock t ransfer, BLT) або тернарних растровими операціями (ternary raster operation).

Основна ідея растрових операцій (слово тернарние часто опускають, на відміну від слова бінарні - див розділ «», стор 61) полягає в організації обміну даними між двома контекстами пристроїв. Ці операції універсальні - вони працюють з будь-якими контекстами пристроїв, що підтримують обмін растровими зображеннями (наприклад, пристрої типу плоттера такими можливостями, природно, не володіють). Таким чином ви можете здійснити передачу зображення і між бітмапами, обраним в сумісний контекст пристрою і реальним пристроєм на якому хочете це зображення показати, між двома бітмапами або передати наявне зображення з реального пристрою в бітмапами або на інший пристрій.

GDI містить 3 функції, здійснюють таку передачу зображень - PatBlt, BitBlt і StretchBlt (зауважте, що абревіатура BLT вимовляється як Бліт):

BOOL PatBlt (

hDC, nX, nY, nWidth, nHeight, dwROP);

BOOL BitBlt (

hDestDC, nDestX, nDestY, nDestWidth, nDestHeight,

hSrcDC, nSrcX, nSrcY, dwROP);

BOOL StretchBlt (

hDestDC, nDestX, nDestY, nDestWidth, nDestHeight,

hSrcDC, nSrcX, nSrcY, nSrcWidth, nSrcHeight, dwROP);

Всі три функції виконують подібні операції - вони будують результуюче зображення на контексті-приймачі, використовуючи в якості вихідних даних:

зображення, що створюється на приймачі при закраске фону поточної пензлем, обраної в контекст-приймач (це називається зразком, pattern).

зображення, що існує на контексті-джерелі (вихідне зображення, source).

зображення, що існує в даний момент на контексті-приймачі (наявне зображення, destination).

У процесі виконання растрової операції ці три вихідних зображення (бітових послідовності) комбінуються і виходить результуюче зображення. Так як в операції беруть участь три вихідних послідовності, то операція отримала назву тернарних (ternary).

Код виконуваної операції задається параметром dwROP - індексом тернарних растрової операції.

У документації по SDK можна знайти таблицю, перераховують індекси 256 можливих растрових операцій, їх імена і коротке пояснення до кожної операції. Причому імена присвоєні лише 15 найбільш уживаним операціями. Таблиця, подана у документації має наступний вигляд:

Number

Hex ROP

Boolean function

Common Name

0

00000042

0

BLACKNESS


...



0D

000D0B25

PDSnaon



...







Поле «Hex ROP» містить індекс тернарних растрової операції, який ви повинні використовувати як параметр dwROP. Поле «Boolean function» містить пояснення до виконуваної операції, а поле «Common name» - ім'я растрової операції, якщо її призначено. Однак розібратися в тому, яка конкретно операція виконується в процесі перенесення зображення не так-то просто.

Спробуємо пояснити це на прикладі: операція з індексом 000D0B25 позначає операцію PDSnaon. Це позначення містить у зворотній польської запису логічні операції над бітами, що їх у процесі растрової операції. Спочатку вказані великими літерами використовуються компоненти:

P: зразок, (кисть, pattern)

D: існуюче зображення (destination)

S: вихідне зображення (source),

в загальному випадку для позначення компонент операції використовуються ці три великі літери, але порядок їх перерахування залежить від виконуваної операції. Після перерахування компонентів слідують маленькі літери, що вказують виконувані операції:

n: інверсія, not; операція використовує 1 аргумент

a: перетин, and; операція використовує 2 аргументу

o: об'єднання, or; операція використовує 2 аргументу

x: виключає АБО, xor; операція використовує 2 аргументу

Для того, щоб зрозуміти як виконуються ці операції уявімо, що у нас є стек, і кожна літера в записі вказує операцію: велика літера виконує запис у стек, маленька - операцію над нижніми даними в стеці, причому вони з стека витягуються, а результат операції розміщується в стеку. Подивимося на прикладі:

Малюнок 18. Приклад розшифровки позначення растрової операції.

Таким чином, отримуємо послідовність операцій для формування результату. Однак використовувати таку табличку зі списком тернарних растрових операцій може бути зручно тільки при отриманні довідок. А от якщо ми можемо словами описати потрібну послідовність дій, а для неї треба визначити індекс растрової операції, то така таблиця зовсім незручна (одного результату можна досягти, виконуючи операції різним чином; навіть якщо ви запишіть необхідні дії в розглянутій формі, немає ніякої гарантії, що в таблиці такий запис знайдеться).

Спробуємо навчитися якось інакше отримувати індекс тернарних растрової операції.

Ми вже зустрічалися з бінарними растровими операціями (ROP2) коли розглядали малювання ліній. Зараз ми скористаємося приблизно таким же підходом - ми будемо виходити з припущення монохромних контекстів пристроїв (для простоти) і спробуємо скласти табличку, аналогічну тій, що застосовувалася для бінарних растрових операцій:

Зразок, кисть (pattern)

1 1 1 1 0 0 0 0

Вихідне зображення (source)

1 1 0 0 1 1 0 0

Існуюче зображення (destination)

1 0 1 0 1 0 1 0

Така табличка дозволяє описати всі 256 тернарних операцій, тому наводити її цілком не має сенсу. Однак нам буде зручно використовувати подібну запис для визначення індексу тернарних операції.

Спробуємо, наприклад, знайти індекс растрової операції, в результаті якої ми отримаємо світлу крапку, якщо:

а) контекст-джерело має світлу точку

б) контекст-джерело і контекст-приймач мають темні точки

в) тільки в тому випадку, коли зразок містить темну точку

Мається на увазі операція ((а) або (б)) і (в). Складемо табличку:

Зразок, кисть (pattern)

1 1 1 1 0 0 0 0

Вихідне зображення (source)

1 1 0 0 1 1 0 0

Існуюче зображення (destination)

1 0 1 0 1 0 1 0

Бажаний результат

0 0 0 0 1 1 0 1

Як і у випадку бінарних растрових операцій ми можемо використовувати цей результат як номер операції (і заразом як старше слово індексу). Цей номер дорівнює 0b00001101 = 0x0D. Це вже розглянута нами операція з індексом 0x000D0B25 (PDSnaon).

Розібравшись з растровими операціями, саме час розібратися з функціями, які виконують ці операції. Найпростіша з трьох розглянутих - функція PatBlt. Вона не використовує контекст-джерело і виконує операцію тільки над контекстом-приймачем і зразком (тлом, отриманими в результаті зафарбовування поточної кистю).

BOOL PatBlt (hDC, nX, nY, nWidth, nHeight, dwROP);

Ця функція може використовуватися з усіма растровими операціями, які не застосовують контекст-джерело. З іменованих растрових операцій це:

BLACKNESS

- Зафарбувати все чорним

DSTINVERT

- Інвертувати зображення (зробити "негатив")

PATCOPY

- Зафарбувати пензлем

PATINVERT

- Зафарбувати інвертованою пензлем

WHITENESS

- Зафарбувати все білим

Ця функція часто використовується для початкової зафарбовування областей (операції BLACKNESS, WHITENESS, PATCOPY) і для виділення фрагментів (DSTINVERT).

Наступна функція, яку ми розглянемо:

BOOL BitBlt (

hDestDC, nDestX, nDestY, nDestWidth, nDestHeight,

hSrcDC, nSrcX, nSrcY, dwROP);

Вона здійснює передачу зображень між двома контекстами пристроїв, при цьому передається прямокутний фрагмент, який на контексті-приймачі і на контексті-джерелі має однакові розміри. При використанні цієї функції треба бути досить обережним - для завдання координат і розмірів використовується логічна система координат, і логічний розмір зображення в обох системах може бути різним.

Окремо треба розглянути випадок, коли один із контекстів є кольоровим, а інший чорно-білим - при цьому особливим чином здійснюється перетворення кольорів:

при переході від монохромного до кольорового колір, закодований 1, відповідає кольору фону (задається функцією SetBkColor), а колір 0 - кольором тексту (функція SetTextColor).

при переході від кольорового до монохромного вважається, що якщо колір точки збігається з кольором фону, то ця точка кодується кольором 1, інакше 0.

Найпотужніша функція, що виконує растрові операції:

BOOL StretchBlt (

hDestDC, nDestX, nDestY, nDestWidth, nDestHeight,

hSrcDC, nSrcX, nSrcY, nSrcWidth, nSrcHeight, dwROP);

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

зображення збільшується, то деякі рядки (стовпці) будуть дублюватися;

зображення зменшується, то деякі рядки (стовпці) будуть комбінуватися в один рядок (стовпчик).

Об'єднання рядків (стовпців) при стисненні може здійснюватися різними способами, які вибираються за допомогою функції

UINT SetStretchBltMode (hDC, nMode);

параметр nMode задає режим об'єднання рядків:

BLACKONWHITE

виконується операція І (AND). У результаті виходить, що чорний колір має "пріоритет" над білим - поєднання чорного з білим розглядається як чорний

WHITEONBLACK

виконується операція АБО (OR). При цьому "пріоритет" належить білому над чорним - поєднання чорного з білим дає білий

COLORONCOLOR

при цьому відбувається просте виключення рядків (стовпців).

HALFTONE 1

тільки в Win32 API; відбувається усереднення кольору об'єднуються точок.

Незалежні від пристрою бітмапами

У вже розглянутих залежних від пристрою бітмапами є один дуже серйозний недолік - їх організація відображає організацію відеопам'яті того графічного пристрою, для якого вони були спроектовані. При цьому виникають великі складнощі з перенесенням бітмапами з одного пристрою на інший, особливо при створенні бітмапами, які будуть зберігатися у вигляді файлів і пізніше переноситься на інші комп'ютери. Досить універсальним є тільки лише монохромний бітмапами, який легко може бути відображений на будь-якому кольоровому пристрої, проте таке обмеження є вкрай незручним для кінцевого користувача.

Для кольорових бітмапами складності виникають навіть при їх відображенні на однотипних пристроях. Наприклад, SVGA адаптери часто використовують логічну палітру, задану набір відтворних кольорів. При відображенні одного і того ж кольорового бітмапами на пристроях, що використовують різні палітри, результат буде різним.

Наявність цих складнощів призвело до появи нових видів бітмапами, так званих незалежних від пристрою бітмапами (D evice I ndepended B itmap, DIB). Такий бітмапами відрізняється від звичайного тим, що додатково містить дані, що визначають відповідність кольорів, які використовуються бітмапами, реальним кольорам. Завдяки цьому незалежний від пристрою бітмапами може бути відображений практично на будь-якому графічному пристрої, що підтримує операції з обміну бітовими образами, з мінімальними спотвореннями кольору.

На практиці, починаючи з версій Windows 3. X для зберігання зображень (у вигляді. Bmp файлів або ресурсів програми) використовуються тільки незалежні від пристрою бітмапами.

Формат незалежного від пристрою бітмапами

Зазвичай доводиться мати справу з DIB, коли вони представлені або у вигляді файлів або ресурсів програми. Тому знайомство з DIB ми почнемо з формату файлу, який містить DIB.

Деякі складності пов'язані з наявністю декількох різних видів DIB-файлів. Спочатку (у ранніх версіях Windows і OS / 2 використовувався бітмапами в його найпростішому вигляді, званому у документації форматом OS / 2 5. Надалі, у міру розвитку GDI з'явився формат Windows, який для додатків Windows довгий час був фактично стандартом. Цей вид бітмапами дожив до платформи Win 32, коли до нього було додано кілька нових можливостей, правда без зміни заголовка. Надалі розвиток Windows бітмапами пішло стрімко - практично у кожній новій версії Windows додається щось нове і в заголовках бітмапами з'являються нові поля. Так з'явилися бітмапами 4 ї версії (для Windows -95 і Windows NT 4.0) і навіть 5 ою (для Windows NT 5.0). Швидше за все цей процес так скоро не зупиниться.

Втішає в цьому два міркування:

Перше: всі старі формати бітмапами підтримуються. Таким чином, якщо ваш додаток сама створює бітмапами, то він буде коректно оброблятися і в наступних версіях Windows.

Друге: при завантаженні бітмапами (а він може бути створений в системі, розробленої пізніше вашого застосування), можна так побудувати алгоритм, що аналізувати заголовок не буде потрібно. У цьому випадку ваш додаток знов-таки може використовувати нові формати бітмапами (принаймні до тих пір, поки ви не збираєтеся самостійно аналізувати зображення).

Малюнок 18. Структура незалежного від пристрою бітмапами.

Власне незалежний від пристрою бітмапами містить кілька структур даних, що описують його характеристики. Ці структури слідують один за одним безупинно, без проміжків. Якщо говорити про структуру DIB загалом, не вдаючись в подробиці опису цих структур даних, то його формат зберігається у всіх існуючих версіях Windows.

Тема бітмапами містить дані про його розмір (розмірі всього бітмапами в байтах) та відстань від початку файлу до закладеного в ньому зображення. У такому вигляді бітмапами зберігається або у файлі, або у вигляді ресурсів програми. Завантаження бітмапами може виконуватися двома різними способами:

У найпростішому випадку все, крім заголовка файлу, поміщається в одну область даних (у разі 16 ти розрядних платформ треба враховувати, що розмір може бути істотно більше 64К). Бітмапами, завантажений таким чином, називається упакованим (packed DIB).

В іншому випадку бітмапами розташовується в двох областях - в першій знаходиться заголовок бітмапами і дані про відповідність кольорів (палітра, або замінюють її дані), а в другій - власне зображення.

Перший спосіб зручний при зчитуванні бітмапами з диска або з ресурсів програми, другий - при створенні нового бітмапами у додатку, коли розмір області даних для зберігання зображення може бути заздалегідь невідомий (його можна дізнатися з заголовка бітмапами). Багато функцій GDI, що працюють з незалежними від пристрою бітмапами, вимагають завдання двох покажчиків: на інформацію про бітмапами і на дані зображення. Однак деякі функції орієнтовані на використання упакованого бітмапами, і тоді вимагають завдання хендла глобального блоку пам'яті, що містить упакований DIB. З цієї точки зору перший спосіб (з використанням упакованого бітмапами) універсальний - ви можете легко вирахувати покажчик на дані зображення усередині єдиного блоку (наприклад, виходячи з даних в заголовку файлу).

Завантаження незалежних від пристрою бітмапами

Формат заголовка файлу однаковий для всіх версій бітмапами; він описується структурою BITMAPFILEHEADER:

typedef struct tagBITMAPFILEHEADER {

WORD bfType;

DWORD bfSize;

WORD bfReserved1;

WORD bfReserved2;

DWORD bfOffBits;

} BITMAPFILEHEADER;

Поле bfType, повинно бути містити два літери "BM" (значення 0x4D42).

Поле bfSize вказує повний розмір файлу, включаючи цей заголовок. Зверніть увагу на те, що розмір задається подвійним словом, тому що може істотно перевищувати 64K. Наприклад бітмапами 1280x1024, 24 біта / піксель має розмір більш 3M. Взагалі кажучи, це поле може бути не заповнене; хоча і вкрай рідко, але може навіть виявитися, що там вказана некоректна величина, замість правильного розміру або 0. Принаймні для бітмапами OS / 2 на полі bfSize може виявитися величина, що дорівнює розміру заголовка файлу плюс заголовок бітмапами (26). У всіх випадках краще виходити не з цієї величини, а з реального розміру файлу.

Поля bfReserved1 і bfReserved2 обидва містять 0. Принаймні так вважає Microsoft. У бітмапами OS / 2 часто ці поля містять ненульові дані.

Поле bfOffBits вказує адресу, з якого в даному файлі розміщуються власне дані зображення. Цим полем зручно користуватися для отримання розміру заголовка бітмапами і даних про його кольорах, а заодно для обчислення адреси початку даних зображення.

Так, завдяки наявності поля bfOffBits, можна сформулювати універсальний алгоритм завантаження бітмапами в пам'ять, що не залежить від версії бітмапами та його характеристик. У цьому прикладі ми будемо орієнтуватися на роботу з функціями Windows API, що дозволяє зробити більш компактний, стерпний код, крім цього введемо додаткову структуру, яка описує DIB. Вона буде зручна з двох причин - по-перше, після завантаження DIB зручно повертати два покажчики, які можуть знадобитися в подальшому, плюс хендл блоку пам'яті, що містить бітмапами; все це простіше зберігати в одній структурі. По-друге, цю ж структуру ми зможемо використовувати ще раз, коли розглянемо завантаження бітмапами з ресурсів програми. Детальніше про всі покажчиках та їх типах - див у розділі "".

# Define STRICT

# Include <windows.h>

# Include <windowsx. H>

/ / Описуємо структуру, що містить інформацію про бітмапами

typedef struct _ DIB {

HGLOBAL hglbDib; / / хендл блоку пам'яті або ресурсу

LPBITMAPINFOHEADER lpDibHdr; / / покажчик на заголовок бітмапами

LPSTR lpImage; / / покажчик на зображення

UINT uDibFlags; / / прапор 1-завантажений з файлу, 2-з ресурсу

} FAR * LP_DIB;

# Define DIB_FILE 1

# Define DIB_RESOURCE 2

# Define DIB_SIGNATURE 0x4D42

# Ifdef __NT__

# Define _memcpy_ (to, from, sz) CopyMemory ((LPVOID) (to), (LPVOID) (from), (sz))

# Else

# Define _memcpy_ (to, from, sz) hmemcpy ((void huge *) (to), (void huge *) (from), (sz))

# Endif

BOOL LoadDIBfromFile (LP_DIB lpDib, LPSTR lpszFileName)

{HFILE hFile;

DWORD dwSize;

BITMAPFILEHEADER bmfh;

/ / Инициализируем повертаються дані:

lpDib -> hglbDib = NULL;

lpDib -> lpDibHdr = (LPBITMAPINFOHEADER) NULL;

lpDib -> lpImage = (LPSTR) NULL;

lpDib -> uDibFlags = 0;

/ / Відкриваємо файл з бітмапами для читання

hFile = _lopen (lpszFileName, READ);

if (hFile == HFILE_ERROR) return FALSE;

/ / Визначаємо розмір упакованого бітмапами

dwSize = _llseek (hFile, 0L, 2); _llseek (hFile, 0L, 0);

if (dwSize> = sizeof (bmhf)) dwSize -= sizeof (bmhf);

/ / Виділяємо блок для зберігання упакованого бітмапами

lpDib-> lpDibHdr = (LPBITMAPINFOHEADER) GlobalAllocPtr (GHND, dwSize);

if (lpDib-> lpDibHdr! = (LPBITMAPINFOHEADER) NULL) {

/ / Зчитуємо заголовок файлу

if ((_lread (hFile, & bmhf, sizeof (bmhf)) == sizeof (bmhf)) & &

(Bmhf. BfType == DIB _ SIGNATURE)) {

/ / Якщо заголовок успішно лічений, зчитуємо сам бітмапами

if (_hread (hFile, lpDib-> lpDibHdr, dwSize) == dwSize) {

/ / І встановлюємо потрібні поля структури _ DIB:

lpDib-> hglbDib = GlobalPtrHandle (lpDib-> lpDibHdr);

lpDib-> lpImage = (LPSTR) (

(Char huge *) (lpDib-> lpDibHdr) + bmhf.bfOffBits - sizeof (bmhf));

lpDib -> uDibFlags = DIB _ FILE;}}

/ / Якщо десь виникла помилка - звільняємо пам'ять

if (lpDib -> uDibFlags == 0) {

GlobalFreePtr (lpDib -> lpDibHdr);

lpDib-> lpDibHdr = (LPBITMAPINFOHEADER) NULL;}}

_lclose (hFile);

return lpDib-> uDibFlags? TRUE: FALSE;}

Слід звернути увагу на те, що у цій процедурі основна частина коду виконує перевірки або пов'язана з дещо надмірною описом структури _DIB; в окремих випадках вся процедура може звестися до виконання 3 х - 4 х функцій.

По суті близький до цього випадок може бути пов'язаний із завантаженням незалежних від пристрою бітмапами з ресурсів програми. При розгляді залежних від пристрою бітмапами було зазначено, що функція LoadBitmap, завантажуються бітмапами з ресурсів програми, повертає залежний від пристрою бітмапами, призначений для відтворення на дисплеї. Це може бути незручно, якщо бітмапами повинен відображатися, скажімо, на принтері. На щастя в ресурси програми включається безпосередньо незалежний від пристрою бітмапами, що дозволяє отримати до нього доступ з допомогою функцій FindResource і LoadResource. У результаті ви отримаєте покажчик на блок пам'яті, із цілою образ файлу бітмапами, включаючи структуру BITMAPFILEHEADER. Залишиться тільки обчислити адресу початку даних зображення і адресу інформації про бітмапами:

/ / Включаються заголовки і опис структури _DIB - див у попередньому прикладі

BOOL LoadDIBfromResources (LP_DIB lpDib, HINSTANCE hInstance, LPSTR lpszResName)

{LPBITMAPFILEHEADER lpbmfh;

HRSRC hresDib;

/ / Инициализируем повертаються дані:

lpDib-> hglbDib = NULL;

lpDib-> lpDibHdr = (LPBITMAPINFOHEADER) NULL;

lpDib-> lpImage = (LPSTR) NULL;

lpDib-> uDibFlags = 0;

/ / Шукаємо потрібний ресурс

hresDib = FindResource (hInstance, lpszResName, RT_BITMAP);

if (! hresDib) return FALSE;

/ / Ресурс знайдений, отримуємо його хендл

lpDib-> hglbDib = LoadResource (hInstance, hresDib);

if (! (lpDib-> hglbDib)) return FALSE;

/ / Отримуємо покажчик на завантажений ресурс

lpbmfh = (LPBITMAPFILEHEADER) LockResource (lpDib-> hglbDib);

if (lpbmfh! = (LPBITMAPFILEHEADER) NULL) {

/ / Заповнюємо інші поля структури _DIB:

lpDib-> lpDibHdr = (LPBITMAPINFOHEADER) (lpbmfh + 1);

lpDib-> lpImage = (char FAR *) (lpbmfh) + bmhf.bfOffBits;

lpDib-> uDibFlags = DIB_RESOURCE;}

if (lpDib-> uDibFlags == 0) {

# Ifndef __NT__

FreeResource (lpDib-> hglbDib);

# Endif

lpDib-> hglbDib = NULL;}

return lpDib-> uDibFlags? TRUE: FALSE;}

Закінчуючи розгляд функцій для завантаження незалежних від пристрою бітмапами з файлу або з ресурсу програми, наведемо ще одну функцію, яка звільняє виділені ресурси. Необхідність у цій функції виникає, як тільки вводиться власна структура _DIB, в якій містяться хендла і покажчики на виділювані ресурси різного типу (блок пам'яті або ресурс додатка).

BOOL FreeDIB (LP_DIB lpDib)

{BOOL fResult = FALSE;

switch (lpDib-> uDibFlags) {

case DIB_FILE:

if (lpDib-> lpDibHdr) GlobalFreePtr (lpDib-> lpDibHdr);

fResult = TRUE;

break;

case DIB_RESOURCE:

# Ifndef __NT__

if (lpDib-> hglbDib) {

UnlockResource (lpDib-> hglbDib); / / для NT не потрібно

FreeResource (lpDib-> hglbDib);

/ / Для NT не потрібно}

# Endif

fResult = TRUE;

break;

default:

break;}

/ / Инициализируем структуру _DIB:

lpDib-> hglbDib = NULL;

lpDib-> lpDibHdr = (LPBITMAPINFOHEADER) NULL;

lpDib-> lpImage = (LPSTR) NULL;

lpDib -> uDibFlags = 0;

return fResult;}

Тема незалежного від пристрою бітмапами

Безпосередньо після заголовка файлу бітмапами слід інформація, що описує характеристики бітмапами - його розміри, кількість кольорів, використовувану палітру, режим стиснення зображення та багато іншого. Як вже було зазначено, інформація про незалежних від пристрою бітмапами умовно ділиться на дві структури даних: 1) опис самого бітмапами і 2) опис використовуваних бітмапами квітів.

У різних версіях Windows були передбачені різні формати опису бітмапами, на щастя сумісні знизу-вверх. Має сенс оглядово ознайомитися з можливостями бітмапами різних версій і змінами, що відбулися в їх описі.

Формат OS / 2

У ранніх версіях GDI для опису бітмапами застосовувалися структури, сумісні з раннім форматом OS / 2. Для опису інформації про бітмапами застосовувалася структура BITMAPCOREHEADER, а для опису використовуваних кольорів - палітри - масив структур RGBTRIPLE (він необов'язковий):

typedef struct tagBITMAPCOREHEADER {

DWORD bcSize;

short bcWidth;

short bcHeight;

WORD bcPlanes;

WORD bcBitCount;

} BITMAPCOREHEADER;

typedef struct tagRGBTRIPLE {

BYTE rgbtBlue;

BYTE rgbtGreen;

BYTE rgbtRed;

} RGBTRIPLE;

Спочатку розглянемо структуру BITMAPCOREHEADER, що описує бітмапами:

Поле bcSize містить розмір цієї структури (sizeof (BITMAPCOREHEADER)), його значення має дорівнювати 12. Поля bcWidth і bcHeight задають розміри даного бітмапами. Так як для завдання розмірів використовується ціле число зі знаком, то максимальний розмір бітмапами в цього формату дорівнює 32767x32767 пікселів.

Поле bcPlanes вказує кількість колірних планів (площин), використовуваних бітмапами. Його значення для незалежного від пристрою бітмапами завжди має дорівнювати 1. Поле bcBitCount вказує кількість біт, що використовуються для завдання кольору пікселя. Можливо одне з наступних значень:

1 - монохромний бітмапами

4 - 16 ти кольоровий бітмапами

8 - 256 ти кольоровий бітмапами

24 - бітмапами в істинних кольорах (TrueColor).

Всі інші значення для полів bcPlanes і bcBitCount є неприпустимими. Якщо бітмапами має 2, 16 або 256 кольорів, то безпосередньо після структури BITMAPCOREHEADER слід палітра (palette) - таблиця визначення кольорів у вигляді масиву з 2, 16 або 256 записів типу RGBTRIPLE. Вважається, що зображення такого бітмапами містить логічні номера кольорів для кожного пікселя, а відповідність логічного номери істинного кольору задається відповідним записом у палітрі. Кожна запис RGBTRIPLE задає інтенсивності червоною (r ed), зеленої (g reen) і синій (b lue) компонент кольору пікселя, у вигляді числа від 0 до 255. Таким чином можливо опис 16   777   216 можливих кольорів з яких будується палітра, використовувана бітмапами.

Останній варіант, коли бітмапами має 24 біта на піксель, припускає, що 24 х бітовий номер кольору пікселя відповідає дійсному кольору, то є записи з трьох компонент основних кольорів RGB (структура RGBTRIPLE сама має розмір 24 біта). Зрозуміло, що в цьому випадку палітра стає не потрібна і в заголовок бітмапами вона не поміщається зовсім.

Часто для зручності замість структур BITMAPCOREHEADER і масиву записів RGBTRIPLE використовують об'єднану структуру BITMAPCOREINFO, яка просто описує як поля структуру BITMAPCOREHEADER і масив з одного запису RGBTRIPLE.

typedef struct _BITMAPCOREINFO {

BITMAPCOREHEADER bmciHeader;

RGBTRIPLE bmciColors [1];

} BITMAPCOREINFO;

Така структура дещо спрощує доступ до опису бітмапами за вказівником: при використанні BITMAPCOREHEADER і RGBTRIPLE необхідно маніпулювати з двома покажчиками, а при використанні BITMAPCOREINFO достатньо тільки одного - вказує на початок заголовка. Наприклад, замість такого фрагмента коду:

LPBITMAPCOREHEADER lpbmch = ...; / / вважаємо, що покажчик на заголовок нам дано

LPRGBTRIPLE lprgbt;

lprgbt = (LPRGBTRIPLE) (lpbmch + 1); / / отримуємо покажчик на палітру

/ / Для доступу до полів заголовка використовуємо, наприклад lpbmch-> bcWidth

/ / Для доступу до палітри використовуємо, наприклад lprgbt [i]. RgbtRed;

Можна використовувати трохи більше простий фрагмент, в якому застосовується тільки один покажчик:

LPBITMAPCORE INFO lpbmci = ...; / / вважаємо, що покажчик на заголовок нам дано

/ / Для доступу до полів заголовка lpbmc i -> bmciHeader.bcWidth

/ / Для доступу до палітри lpbmci-> bmciColors [i]. RgbtRed;

Однак використовувати структуру BITMAPCOREINFO при завантаженні бітмапами не дуже зручно, так як її повний розмір може бути різним, залежно від кількості квітів бітмапами (причому він може бути або менше, або більше, ніж sizeof (BITMAPCOREINFO) і свідомо не дорівнює йому). Його можна обчислити як розмір структури BITMAPCOREHEADER (або значення поля bcSize) плюс розмір таблиці визначення кольорів: нуль, якщо поле bcBitCount дорівнює 24, або число квітів, помножене на розмір структури RGBTRIPLE:

UINT uSizeCoreInfo;

LPBITMAPCOREHEADER lpbmch;

uSizeCoreInfo = lpbmch -> bcSize + (

lpbmch-> bcBitCount == 24? 0: (1 <<lpbmch-> bcBitCount) * sizeof (RGBTRIPLE));

Безпосередньо слідом за структурою BITMAPCOREINFO слідують власне дані зображення. Їх можна знайти в DIB-файлі як за значенням поля bfOffBits заголовка файлу BITMAPFILEHEADER, так і зчитуючи їх безпосередньо після таблиці визначення кольорів. Аналізуючи заголовок бітмапами можна визначити і необхідний розмір області для зберігання зображення. Зображення зберігається по рядках розгортки, в кожному рядку для завдання кольору пікселя відводиться bcBitCount послідовних біт. Повна довжина рядка вирівнюється в бік завищення до найближчої кордону, кратній подвійному слову (в залежних від пристрою бітмапами рядок вирівнювалася до парного розміру, а у разі DIB - кратного чотирьом). Рядки розгортки перераховуються знизу-вверх. Для обчислення розміру зображення можна скористатися таким фрагментом:

DWORD dwSizeImage;

LPBITMAPCOREHEADER lpbmch; / / вважаємо, що покажчик на заголовок нам дано

dwSizeImage = ((lpbmch -> bcWidth * lpbmch -> bcBitCount + 31)>> 3) & ~ 3 L;

dwSizeImage *= lpbmch-> bcHeight;

У цьому фрагменті виконуються наступні дії: спочатку обчислюється довжина рядки розгорнення в бітах (lpbmch-> bcWidth * lpbmch-> bcBitCount), далі нам треба отримати цю довжину в подвійних словах (тобто поділену на 32) та округленої в більшу сторону; потім перерахувати з подвійних слів в байти - помножити на 4. Цей процес можна дещо прискорити - перерахунок у число подвійних слів з ​​округленням у більшу сторону легко виконати за формулою (x + 31) / 32, або, використовуючи більш швидкі операції, (x +31)>> 5, так як 32 це 2 Травня . Далі треба помножити на 4, тобто ((x +31)>> 5) * 4 = ((x +31)>> 5) <<2), або, в остаточному варіанті, ((x +31)>> 3) & (~ 3): так як при множенні на 4 (зсуві вліво на 2 біти), молодші 2 розряду будуть обнулені, то замінюючи поділ з множенням на зсув вправо, ми повинні скинути два молодших біта в 0.

Формат Windows

Досить швидко Microsoft вирішив розширити можливості бітмапами, у зв'язку з чим з'явилася нові версії структур, що описують бітмапами: для опису заголовка BITMAPINFOHEADER і для опису палітри RGBQUAD:

typedef struct tagBITMAPINFOHEADER {

DWORD biSize;

LONG biWidth;

LONG biHeight;

WORD biPlanes;

WORD biBitCount;

DWORD biCompression;

DWORD biSizeImage;

LONG biXPelsPerMeter;

LONG biYPelsPerMeter;

DWORD biClrUsed;

DWORD biClrImportant;

} BITMAPINFOHEADER;

typedef struct tagRGBQUAD {

BYTE rgbBlue;

BYTE rgbGreen;

BYTE rgbRed;

BYTE rgbReserved;

} RGBQUAD;

Перше поле структури BITMAPINFOHEADER - biSize збігається за призначенням і розміром з полем bcSize структури BITMAPCOREHEADER. Це поле містить розмір структури, яка описує даний заголовок. Таким чином, аналізуючи це поле, можна легко визначити, яка версія заголовка використовується. Але тут є один підводний камінь - в деяких ранніх джерелах часів Windows 3. X затверджується, що всі поля цієї структури, починаючи з поля bi Compression, можуть бути пропущені. Власне в документації, що супроводжує компілятори є тільки одне непряме згадування про це: там суворо попереджається, що для визначення розміру заголовка бітмапами треба обов'язково використовувати поле biSize, а не sizeof (BITMAPINFOHEADER). Таким чином розмір структури BITMAPINFOHEADER може змінюватися від 16 до 40 байт, але в будь-якому випадку він перевищує розмір структури BITMAPCOIREHEADER (12 байт), що дозволяє розрізняти заголовки в різних форматах.

На практиці мені тільки один раз зустрівся бітмапами з неповним заголовком. Слід зауважити також, що в результаті перевірки виявилося, що всі графічні пакети, з якими я мав справу, відмовляються сприймати такий бітмапами і повідомляють про неправильному форматі файлу; аналогічно реагують на подібні бітмапами і сучасні системи (перевірено для Windows -95, Windows -98 , Windows NT 4.0). Фактично можна з достатньою надійністю припускати, що заголовок буде завжди повним. Таке припущення не принесе скільки-небудь помітних обмежень у використанні бітмапами, створених іншими додатками. Однак у деяких випадках можна врахувати цю особливість практично без ускладнення вихідного тексту; наприклад, читання заголовка можна представити таким чином:

/ / Нехай файл з бітмапами вже відкритий і його хендл = hFile

union {

BIMAPCOREHEADER bmch;

BITMAPINFOHEADER bmih;

} Bmh;

DWORD dwSizeHeader;

memset (& bmh, 0, sizeof (bmh));

if (_lread (hFile, & bmh, sizeof (DWORD)) == sizeof (DWORD)) {

dwSizeHeader = bmh.bmih.biSize - sizeof (DWORD);

if (_lread (hFile, & bmh.bmih.biWidth, dwSizeHeader) == dwSizeHeader) {

/ / Заголовок успішно прочитаний, всі невизначені поля обнулені if (bmh.bmih.biSize == sizeof (BITMAPCOREHEADER)) {

/ / OS / 2 бітмапами, аналізуємо структуру bmh.bmch

} Else {

/ / Windows бітмапами, аналізуємо структуру bmh.bmih}}}

Такий прийом дозволяє зчитувати бітмапами як формату OS / 2, так і формату Windows. З деяким ускладненням він може бути в подальшому поширений і на більш нові формати бітмапами, що з'явилися в Windows -95 і Windows NT 4.0.

Коротко познайомимося з іншими полями структури BITMAPINFOHEADER: Поля biWidth і biHeight задають розміри бітмапами. Схоже, що максимальний розмір у 32   767   x   32   767 пікселів здався розробникам Windows занадто скромним, тому для завдання розмірів використовуються подвійні слова зі знаком (до 2   147   483   647 x 2   147   483   647 пікселів). Мені, наприклад, бітмапами, що перевищує 30 тисяч пікселів в ширину або висоту, поки ще не зустрічався.

Поля biPlanes і biBitCount використовуються так само, як і в заголовку бітмапами OS / 2, і мають такі ж значення: biPlanes завжди 1, а biBitCount може бути 1, 4, 8 або 24. Аналогічно OS / 2, якщо поле biBitCount має значення 24, то таблиця визначення кольорів (палітра) пропущена.

Поле biCompression використовується, якщо бітмапами представлений у стислому вигляді, і в цьому випадку поле biSizeImage вказує реальний розмір зображення в байтах. Якщо використовується незжатий формат бітмапами, то допустимо вказівку 0. Замість чисел, природно, використовуються символи BI_RGB (0), BI_RLE4 (1) або BI_RLE8 (2), в залежності від використовуваного алгоритму стиснення (RLE -4 або RLE -8), або незжатий бітмапами (BI_RGB). Детальніше про алгоритми стиснення та аналізі стислих бітмапами можна дізнатися зі стандартної документації, наприклад, з супроводжуючою компілятори системи допомоги.

Поля biXPelsPerMeter і biYPelsPerMeter вказують на рекомендовані характеристики пристрою, на якому буде відображатися бітмапами. Вони можуть використовуватися, наприклад, для вибору найбільш адекватного бітмапами, якщо передбачено кілька варіантів для різних дозволів. Зазвичай ці поля задають рівними 0. Однак, якщо Ви бітмапами буде відображатися на будь-якому відмінному від дисплея пристрої, то ці поля доцільно задати відповідними характеристиками пристрою, так само як і розмір самого бітмапами визначати виходячи з роздільної здатності пристрою. Далі такий бітмапами може легко оброблятися програмами верстки, які, виявивши ненульове значення цих полів, включать його в макет відразу з такими розмірами, як потрібно.

При цьому виникає невеликий нюанс, пов'язаний з тим, що здатність пристрою повертається функцією GetDeviceCaps у точках на дюйм, а нам потрібно задавати у вигляді числа крапок на метр. Виникає необхідність визначити співвідношення дюйма і метра. Коли я спробував мати справу з величиною 25.4 мм / дюйм, то з подивом виявив, що бітмапами в макеті відображається з деякою похибкою. Довелося експериментально обчислювати значення дюйма, прийняте в Microsoft (?!); виявилося, що найбільш точний результат дає величина 25.397 мм / дюйм. Фрагмент програми виглядає приблизно так:

LPBITMAPINFOHEADER lpbmih = ...; / / отримуємо покажчик на BITMAPINFOHEADER

HDC hDC = ...; / / контекст пристрою виводу

/ / При обчисленнях можна обійтися довгими цілими замість плаваючою комою,

/ / Поки роздільна здатність пристрою не перевищує 4294 точок на дюйм,

/ / А в найближчому майбутньому так і буде.

lpbmih -> biXPelsPerMeter = (LONG) (

(GetDeviceCaps (hDC, LOGPIXELSX) * 1 млн UL) / 25 397 UL);

У принципі можна вирахувати ці величини та іншим способом, наприклад так:

lpbmih -> biXPelsPerMeter = (LONG) (

(GetDeviceCaps (hDC, HORZRES) * 1000 UL) / GetDeviceCaps (hDC, HORZSIZE));

Який із способів дасть більш точний результат і яким краще користуватися - на розсуд розробника. Перший спосіб використовує так званий «логічний дюйм», який дасть на пристроях з низьким дозволом кілька завішені результат, зате помітне зображення (особливо це стосується тексту); окрім цього для багатьох пристроїв часто можна виконати спеціальну настройку (за допомогою панелі керування Windows), яка дозволить прецизійно встановити точні значення. Другий спосіб відштовхується від фізичних характеристик пристрою і, якщо вони задані не зовсім точно, результат також буде неточним, зате менш залежним від налаштування операційної системи. Наприклад для різних дисплеїв часто застосовуються одні і ті-ж драйвера, що призводить до того, що різні дисплеї з різними електронно-променевими трубками і різними фізичними розмірами вважаються абсолютно однаковими. Може бути перший спосіб краще для дисплеїв, а другий - для принтерів, розмір паперу для яких стандартизовано куди жорсткіше.

Поле biClrUsed задає кількість квітів, що задаються таблицею визначення кольорів. Це число може бути менше, ніж число можливих кольорів. Якщо цього поля немає, або його значення 0, то таблиця містить 2, 16 або 256 записів, дивлячись за кількістю біт, відведених на один піксель (biBitCount).

Поле biClrImportant визначає кількість кольорів, які повинні бути по можливості точно передані при відображенні бітмапами. Значення 0 припускає, що всі кольори повинні передаватися як можна точніше. Цим полем можна скористатися, якщо ви самі розробляєте палітру бітмапами - тоді ви можете деякі кольори (наприклад, кольору, що покривають більшу частину зображення) оголосити важливими, перерахувати їх у палітрі першими і цим дещо скоротити колірні спотворення при відображенні бітмапами на пристроях, що використовують палітру.

Інформація про використовувані бітмапами кольорах розміщується відразу після заголовка бітмапами у вигляді масиву від 2 до 256 записів типу RGBQUAD або пропущена зовсім, якщо бітмапами представлений в істинних кольорах. Структура RGBQUAD відрізняється від RGBTRIPLE тільки тим, що вона доповнена невживаним байтом до кордону подвійного слова 6.

Аналогічно формату OS / 2 вводиться додаткова об'єднує структура BITMAPINFO, за змістом еквівалентна структурі BITMAPCOREINFO.

typedef struct tagBITMAPINFO {

BITMAPINFOHEADER bmiHeader;

RGBQUAD bmiColors [1];

} BITMAPINFO;

Повний розмір структури BITMAPINFO можна визначити виходячи з розміру заголовка (обов'язково треба брати значення поля biSize, а не sizeof (BITMAPIFOHEADER)) і розміру палітри, обчислюваного з урахуванням поля biClrUsed:

UINT uSizeDibInfo;

LPBITMAPINFOHEADER lpbmih;

uSizeDibInfo = lpbmih-> biSize + (

lpbmih-> biClrUsed? lpbmih-> biClrUsed: (

lpbmih-> biBitCount> 8? 0: (1 <<lpbmih-> biBitCount))

) * Sizeof (RGBQUAD);

Теоретично, цей фрагмент коду лише щодо коректний - поле biClrUsed може відсутні в структурі BITMAPINFOHEADER. По ідеї треба спочатку перевірити значення поля biSize, і тільки якщо поле biClrUsed присутній у структурі, використовувати його значення. Однак цей фрагмент може виявитися зовсім коректним, якщо здійснювати завантаження заголовка бітмапами в спеціально виділену для цього структуру BITMAPINFOHEADER, з попереднім обнуленням всіх полів (приблизно так, як у прикладі на сторінці 130).

Слід ще раз нагадати, що бітмапами з неповним заголовком - сучасними системами не підтримуються, так що в принципі не буде помилки, якщо порахувати заголовок присутнім повністю. У той же час бітмапами з неповною палітрою - майже типовий випадок; наприклад шпалери Windows -95 часто представлені саме в такому вигляді, тому враховувати можливість завдання поля biClrUsed необхідно.

Іноді буває зручно скористатися власним замінником структури BITMAPINFO:

struct {

BITMAPINFOHEADER bmiHeader;

RGBQUAD bmiColors [256 + 3];

} Bitmapheader;

У цій структурі резервується достатній простір для утримання заголовка бітмапами і палітри плюс ще деяка інформація (3 додаткові записи RGBQUAD = 12 байт), про яку йтиметься нижче, у розділі «».

Визначення розміру області даних для зберігання зображення здійснюється точно також, як і у випадку OS / 2, за невеликим застереженням - отриманий розмір є максимальним. Якщо використовуються стислі бітмапами (biCompression одно BI_RLE4 або BI_RLE8), то реальне зображення може виявитися істотно меншим. Взагалі кажучи, визначення розміру стисненого зображення можливо тільки після того, як це зображення повністю побудовано - так як можливість стиснення даних і ступінь стиснення дуже сильно залежать від характеру самих даних. Таким чином, при виділенні простору під знову створювані бітмапами варто виділяти максимально необхідний обсяг простору, а при збереженні в стислому вигляді цей розмір вам поверне GDI, так як власне стиснення здійснюється саме їм.

DWORD dwSizeImage;

LPBITMAPCOREHEADER lpbmih; / / вважаємо, що покажчик на заголовок нам дано

dwSizeImage = ((lpbmih -> biWidth * lpbmih -> biBitCount + 31)>> 3) & ~ 3 L;

dwSizeImage *= lpbmih-> biHeight;

Формат Win32 (Windows NT 3.x)

При розширенні можливостей бітмапами, реалізованих в Win 32 API (ранні версії Windows -95, Windows NT 3. X) вдалося обійтися без зміни розміру заголовка бітмапами; зміни торкнулися тільки способів завдання деяких полів і опису кольорів. Всього можна перелічити кілька нововведень:

перерахування рядків розгортки як знизу-вверх, так і зверху-вниз;

додавання двох нових колірних форматів: 16 і 32 біта на піксель (так звані HiColor);

у разі форматів 16 і 32 біта на піксель новий спосіб опису кольорів - замість палітри задаються маски квітів.

Розглянемо ці нововведення докладніше.

По-перше, для того, щоб розібратися в тому, який порядок перерахування рядків розгортки використовується, треба звернути увагу на поле biHeight. Якщо рядки розгортки перераховуються зверху-вниз, то це поле буде представлено негативною величиною. У зв'язку з цим при визначенні розмірів зображення необхідно використовувати абсолютну величину поля biHeight.

DWORD dwSizeImage;

LPBITMAPCOREHEADER lpbmih; / / вважаємо, що покажчик на заголовок нам дано

dwSizeImage = ((lpbmih -> biWidth * lpbmih -> biBitCount + 31)>> 3) & ~ 3 L;

dwSizeImage *= abs (lpbmih -> biHeight); / / 1

По-друге, нові колірні формати (16 і 32 біта на піксель) спочатку (Windows NT 3. X) вимагали декількох одночасних змін до бітмапами:

палітра відсутня, так як зображення зберігається практично в істинних кольорах (навіть 16 біт на піксель дає можливість описати 65   536 різних кольорів);

замість палітри записуються три подвійні слова, що представляє відповідно маски червоною, зеленою і синій компонент (ці маски дозволяють GDI розібратися, які біти в 16 ти або 32 х бітовому номері кольору передають відповідну компоненту кольору);

полі biCompression задається рівним BI_BITFIELDS, що б підкреслити відсутність палітри і наявність замість неї масок квітів. Це значення дозволяє старим додаткам розпізнати непідтримуваний формат бітмапами до того, як виникне помилка, пов'язана зі спробою прочитати палітру.

Всі три зміни здійснювалися одночасно і були обов'язкові для HiColor бітмапами. Однак у міру розвитку в цей формат були внесені деякі зміни. Так, істотно спрощений GDI в Windows -95 зажадав завдання фіксованих масок квітів, працювати як в Windows NT з довільними масками було надто складно 7.

У Windows -95 дозволено застосовувати наступні маски квітів:

Формат

Червоний

Зелений

Синій

16 біт / піксель, 5-5-5 (32768 кольорів):

0x00007C00L

0x000003E0L

0x0000001FL

16 біт / піксель, 5-6-5 (65 536 кольорів) 8:

0x0000F800L

0x000007E0L

0x0000001FL

32 біт / піксель, 8-8-8 (16 777 216 кольорів):

0x00FF0000L

0x0000FF00L

0x000000FFL

Таким чином для 16 ти і 32 х бітових бітмапами з'явилися стандартні маски квітів, які будуть використовуватися за замовчуванням, якщо в самому бітмапами ці маски не визначені; в цьому випадку поле biCompression задається рівним BI_RGB, а не BI_BITFIELDS. Тепер режим BI_BITFIELDS не обов'язково повинен встановлюватися для 16 ти і 32 х бітових бітмапами, він використовується тільки в тому випадку, якщо заголовок бітмапами містить маски.

Якщо маски присутні, то вони перераховуються відразу за заголовком бітмапами (BITMAPINFOHEADER) у наведеному у таблиці порядку - червоний, зелений і синій кольори.

Крім того, у разі 16 ти, 24 х або 32 х біт на піксель і режиму BI_RGB з'явилася можливість задавати палітру (поле biClrUsed повинно бути ненульовим - палітра для максимально допустимого числа квітів у цих форматах надто громіздка). Сенс включення палітри тепер пов'язаний не з необхідністю задавати відповідність номерів квітів реальним кольорам, а з можливістю оптимізувати процес відображення бітмапами, задаючи рекомендовану для нього палітру. Це реально може мати місце при відтворенні HiColor або TrueColor бітмапами на пристроях, що підтримують палітру - для підвищення якості передачі кольору такому пристрою доцільно призначити палітру, оптимізовану для цього бітмапами. Якщо цього не зробити, то все безліч квітів бітмапами буде приводиться до тієї палітрі, яка вже використовується пристроєм - швидше за все це буде системна палітра.

Ще пізніше в документації з'явилися вказівки про те, що можливе одночасне завдання масок квітів і палітри - після заголовка спочатку розміщуються маски квітів і тільки потім палітра, розмір якої визначається полем biClrUsed заголовка бітмапами. Крім цього, також можна знайти зауваження про те, що маски можуть бути задані для всіх режимів, в яких число біт / піксель дорівнює або перевищує 16, включаючи TrueColor (24 біта / піксель). На практиці ці розширення більшістю додатків не підтримуються.

Для перевірки використовувався стандартний MS Paint, який сам по можливості не виконує аналізу зображень і всю роботу намагається передати GDI. Це дозволяє використовувати його в якості тесту на можливості GDI. Для різних платформ Windows були отримані наступні результати:

Windows 3.11

Підтримує режими 1, 4, 8, 16, 24, 32 біт / піксель для BI_RGB;

Режим BI_BITFIELDS не підтримується 9.

Windows-95

Підтримує режими 1, 4, 8, 16, 24, 32 біт / піксель для BI_RGB;


Windows-98

Windows NT 4.0

понад те, що може Windows-95:

Підтримує режими 16 і 32 біт / піксель для BI_ BITFIELDS;

Для режиму 24 біта / піксель завдання масок (і BI_BITFIELDS) не підтримується.

Для режимів 16, 24, 32 можливе завдання палітри як у BI_RGB так і в BI_ BITFIELDS;

Однак, при роботі в 256 ти кольоровому режимі залишилося враження, що необов'язкова для 16 ти, 24 х і 32 х біт / піксель бітмапами палітра просто ігнорується, навіть якщо присутній. Однак це особливість MS Paint, а не GDI. На жаль, інші перевірені програми (наприклад, Photo Shop 5.0) взагалі відмовилися працювати з HiColor форматами (16 і 32 біт / піксель).

Це означає, що для експорту зображень додаток повинен використовувати по можливості старі, перевірені і широко поширені формати 1, 4, 8 біт / піксель з повною палітрою (у разі OS / 2 доводилося спостерігати помилки при читанні бітмапами із скороченою палітрою); або TrueColor в стандартному варіанті - без палітри і без масок квітів. А ось при читанні бітмапами доцільно допускати всі ці можливі варіанти, що забезпечить сумісність з бітмапами, створюваними іншими додатками, навіть у найближчому майбутньому.

Таким чином для бітмапами Win 32 треба звертати увагу на:

можливо негативну висоту бітмапами;

режим стиснення BI_BITFILEDS - якщо він заданий, то після заголовка є 3 подвійних слова з масками колірних компонент, якщо ж задано режим BI_RGB, BI_RLE4 або BI_RLE8, то масок немає (передбачаються стандартні маски 5-5-5 або 8-8-8);

для форматів 1, 4 і 8 біт на піксель палітра обов'язкове, а для 16, 24 і 32 біт на піксель палітра може бути відсутня (тобто нульове значення biClrUsed інтерпретується або як максимальний розмір палітри, або як її відсутність - дивлячись по числу біт на піксель ). Для HiColor або TrueColor режимів палітра є лише рекомендованої, що полегшує процес відображення повнокольорового бітмапами на пристрої, що підтримує палітри. Саме тому в прикладі на сторінці 134 при визначенні розміру палітри значення поля biBitCount порівнювався з 8, а не перевірялося суворе рівність 24 бітам на піксель - максимальний розмір палітри визначений тільки для 2 х, 16 ти і 256 ти кольорових бітмапами, а для форматів з 16 ма, 24 ма і 32 ма бітами на піксель для завдання палітри необхідно задати поле biClrUsed. За умовчанням в HiColor і TrueColor бітмапами палітра відсутня. Якщо задано і режим BI_BITFIELDS, і biClrUsed не дорівнює 0, то палітра розміщується безпосередньо після масок.

Збереження незалежного від пристрою бітмапами

Для збереження бітмапами необхідно розібратися з усіма необхідними структурами даних, заповнити їх, а потім записати у файл. Завдання можна істотно спростити, якщо вважати, що бітмапами завантажений у вигляді «Packed DIB », що істотно дозволяє збереження бітмапами звести, аналогічно читання, до кількох функцій.

У раніше наведених прикладах я використовував власну структуру _DIB, що описує завантажений в пам'ять DIB. Вона визначена в прикладі на сторінці 120, разом з включенням необхідних заголовних файлів і визначенням допоміжних символів. Крім цього вона буде застосовуватися в даному прикладі, а також з її допомогою будуть виконані операції по перетворенню DIB в DDB (стор. 149) і навпаки - DDB в DIB (стор. 151).

Оскільки формат незалежного бітмапами у попередніх розділах було розглянуто досить докладно, то зараз можна прямо перейти до вихідного тексту.

BOOL StoreDIBtoFile (LPSTR lpszFileName, LP_DIB lpDib) {

BITMAPFILEHEADER bmfh; / / заголовок файлу бітмапами

HFILE hf;

BOOL a = FALSE;

DWORD dwSize;

hf = _lcreat (lpszFileName, 0); </

Додати в блог або на сайт

Цей текст може містити помилки.

Програмування, комп'ютери, інформатика і кібернетика | Лекція
710.9кб. | скачати


Схожі роботи:
Графічний редактор Paint Опис графічного редактора Paint - стандартоного графічного редактора
Організація вводу виводу
Пристрої вводу-виводу ПК
Організація вводу-виводу
Пристрої виводу інформації 2
Розробка графічного редактора
Опис графічного редактора Paint
Основні принципи розробки графічного інтерфейсу користувача
Засоби виводу інформації на принтер в обєктно-орієнтованому середовищі програмування Delphi
© Усі права захищені
написати до нас