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) і 4М для 32 х розрядних (Windows NT, Windows -98) графічних підсистем, причому об'єкти GDI, створені будь-яких додатком, з цим додатком не асоціюються, в наслідок чого автоматичного знищення цих об'єктів не відбувається 3. 3) Перед знищенням об'єкта ви повинні бути впевнені, що він не обраний контекст пристрою. Якщо об'єкт у момент знищення використовується, то він не буде знищений. 4) Об'єкти GDI кешується системою. Тобто повторне створення часто використовуваного об'єкта здійснюється істотно швидше, ніж у перший раз. 5) Крім створюваних у додатку об'єктів, система може вам надати деякі стандартні, які відповідають найбільш часто вживаним об'єктах. Наприклад - тонке перо чорного кольору або кисть білого кольору і т.д. Стандартні об'єкти не можна знищувати. Взагалі-то система повинна зауважити, що робиться спроба знищення стандартного об'єкта і заборонити цю операцію. Але помилки зустрічаються скрізь, навіть у системі, так що краще не сподіватися на її надійність. 6) Різні об'єкти мають хендла зі специфічними назвами HPEN, HBRUSH, HFONT та ін. Ви можете застосовувати просто HANDLE або HGDIOBJ замість всіх цих типів. Застосування специфічних типів може бути кращим при здійсненні суворої перевірки типів. У різних API і реалізації windows.h для різних компіляторів стандартні функції GDI можуть використовувати трохи розрізняються типи хендлов. Так, наприклад, функція SelectObject, яка може працювати з об'єктами різних типів, зазвичай декларована як функція, яка отримує хендл типу HGDIOBJ. Однак у найстаріших 16 ти розрядних версіях windows.h вона може бути описана як отримує просто HANDLE. Часто може бути зручніше застосовувати макроси, визначені в windowsx.h, які здійснюють відповідні операції з необхідним приведенням типів. Наприклад, замість SelectObject можна використовувати макроси SelectPen, SelectBrush, SelectFont і т.д., дивлячись по типу обраного об'єкта. 7) Стандартні об'єкти можна отримати за допомогою функції HGDIOBJ GetStockObject (nIndex); Вона повертає хендл стандартного об'єкта. Потрібний об'єкт задається параметром nIndex. Наприклад, це може бути BLACK_PEN, WHITE_BRUSH, SYSTEM_FONT і т.д. Докладніше - див в описі функції. Або, замість цієї універсальної функції можна застосовувати макроси, визначені в windowsx.h: GetStockPen, GetStockBrush, GetStockFont та ін Ці макроси будуть повертати результат відповідного типу (HPEN, HBRUSH, HFONT, ...). При використанні макросів треба простежити, що б індекс потрібного об'єкта відповідав використовуваному макросу. Наприклад, ви можете помилково використовувати макрос GetStockPen для отримання хендла стандартного шрифту - так як макрос у підсумку звернутися до універсальної функції GetStockObject, то фатальної помилки не виникне, просто повертається результат буде приведений до некоректного типу (у прикладі: HPEN замість HFONT). 8) Для створення власних об'єктів застосовуються функції, що починаються зі слова Create ... Наприклад, CreatePen або CreateSolidBrush. 9) Отриманий об'єкт (стандартний або створений вами) вибирається в контекст пристрою функцією: HGDIOBJ SelectObject (hDC, hObject); Ця функція повертає хендл об'єкта того-ж типу, вибраного раніше в цей контекст. У більшості випадків може бути зручніше скористатися замість функції SelectObject макросами з windowsx.h, призначеними для роботи з конкретними об'єктами: HPEN SelectPen (hDC, hPen); HBRUSH SelectBrush (hDC, hBrush); HFONT SelectFont (hDC, hFont); HBITMAP SelectBitmap (hDC, hBitmap); 10) Для отримання інформації про об'єкт застосовується функція int GetObject (hObject, nSize, lpvStruct); де hObject - хендл об'єкта GDI, інформацію про який ви запитуєте, lpvStruct - покажчик на структуру даних, яка буде заповнюватися інформацією про об'єкт; для об'єктів різних типів визначено різні структури (BITMAP, LOGPEN, LOGBRUSH, LOGFONT і т.д.), nSize - розмір цієї структури. 11) Об'єкт знищується функцією BOOL DeleteObject (hObject); Ця функція видаляє вказаний об'єкт, якщо тільки це не стандартний об'єкт і якщо він не обраний в контекст пристрою. Замість однієї універсальної функції в windowsx.h можна знайти макроси, що видаляють об'єкти конкретних типів: DeletePen, DeleteBrush, DeleteFont і т.д. Звичайне використання Зараз ми розглянемо звичайні схеми використання об'єктів. У самому типовому випадку об'єкт створюється безпосередньо тоді, коли він використовується, і знищується відразу після звільнення з контексту пристрою. Незважаючи на часто виконувану операцію створення і знищення цей метод є кращим, тому що до мінімуму зводиться кількість одночасно існуючих об'єктів (що знижує вимоги до обсягу вільних ресурсів GDI), а витрати часу мінімізуються завдяки кешуванню об'єктів системою. Як приклад ми будемо використовувати тільки один об'єкт GDI - перо, так як застосування всіх інших типів об'єктів аналогічно. void Cls_OnPaint (HWND hwnd) {PAINTSTRUCT ps; HPEN hpenOld, hpenRed, hpenBlue; BeginPaint (hwnd, & ps); / / Створюємо пір'я: hpenRed = CreatePen (PS_SOLID, 0, RGB (255,0,0)); hpenBlue = CreatePen (PS_SOLID, 0, RGB (0,0,255)); / / Вибираємо його в контекст і запам'ятовуємо колишнє: hpenOld = (HPEN) SelectObject (ps.hdc, (HGDIOBJ) hpenRed); ... / / Здійснюємо малювання червоним пером / / Вибираємо інше перо, причому запам'ятовувати попереднє не треба - воно / / І так відомо - це hpenRed SelectObject (ps.hdc, (HGDIOBJ) hpenBlue); ... / / Здійснюємо малювання синім пером / / Звільняємо створене перо з контексту (тобто вибираємо початкове) SelectObject (ps.hdc, (HGDIOBJ) hpenOld); / / І видаляємо створені DeleteObject ((HGDIOBJ) hpenRed); DeleteObject ((HGDIOBJ) hpenBlue); EndPaint (hwnd, & ps);} Цей приклад ілюструє всі основні кроки по роботі зі створюваними об'єктами GDI. Однак у реальному житті він використовується не дуже часто - як правило при малюванні використовується не один об'єкт даного типу, а декілька. Це призводить до появи великої кількості змінних, як-то: hpen1, hpen2, ..., hpen100. Читаність такого тексту порівняно невелика, та й імовірність заплутатися залишається високою. Інше міркування - часто зустрічається приведення типів. У цьому випадку зручніше використовувати макроси з windowsx.h. По-третє - зберігати вихідний об'єкт для відновлення його в контексті пристрою не обов'язково. Якщо існують стандартні об'єкти даного типу, то замість відновлення вихідного можна перед видаленням об'єктів вибрати в контекст будь-який стандартний того ж типу (скажімо, стандартних регіонів або бітмапами не визначено - для них рекомендується зберігати вихідний). У результаті часто використовуються цілі комбайни з функцій GDI, як у наводиться нижче: void Cls_OnPaint (HWND hwnd) {PAINTSTRUCT ps; BeginPaint (hwnd, & ps); / / Створюємо червоне перо і вибираємо його в контекст; / / Колишнє НЕ запам'ятовуємо: SelectPen (ps. Hdc, CreatePen (PS _ SOLID, 0, RGB (255,0,0))); ... / / Здійснюємо малювання червоним пером / / Створюємо і вибираємо синє перо; / / Попереднє - створене нами червоне - відразу знищуємо DeletePen (SelectPen (ps.hdc, CreatePen (PS_SOLID, 0, RGB (0,0,255 )))); ... / / Здійснюємо малювання синім пером / / Звільняємо синє перо з контексту і знищуємо його DeletePen (SelectPen (ps.hdc, GetStockPen (BLACK_PEN))); EndPaint (hwnd, & ps);} У цьому випадку економляться як локальні змінні, так і додатково знижуються вимоги до обсягу вільних ресурсів GDI, хоча до читання подібних довгих виразів треба звикати. Єдине, за чим треба уважно стежити - що б при виборі нового об'єкту замість попереднього, створеного вами, використовувалася функція DeleteObject (або еквівалентний макрос), а при виборі нового об'єкту замість стандартного ця функція не використовувалася. Більш рідкісний випадок - коли об'єкти створюються при обробці WM_CREATE і знищуються при обробці WM_DESTROY (або при запуску і завершенні програми). Цей спосіб використовується рідко, тому що потрібно опис додаткових статичних змінних і, крім того, інтенсивніше витрачаються ресурси GDI. LRESULT WINAPI _export WinProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {PAINTSTRUCT ps; static HPEN hpenRed; / / хендл пера повинен зберігатися в проміжку / / Між обробкою повідомлень switch (uMsg) {case WM_CREATE: ... hpenRed = CreatePen (PS_SOLID, 0, RGB (255,0,0)); break; case WM_PAINT: BeginPaint (hWnd, & ps); SelectPen (ps.hdc, hpenRed); ... / / Тут ми використовуємо вибраний об'єкт / / Теоретично, об'єкт зберігається і після знищення контексту - / / Так що ми можемо не замінювати його на стандартний; правда так робити / / Не рекомендується 4 EndPaint (hWnd, & ps); break; case WM_DESTROY: DeletePen (hpenRed); ... break; ... default: return DefWindowProc (hwnd, uMsg, wParam, lParam);} return 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) |
|