Sunteți pe pagina 1din 36

Capitolul 4.

Noţiuni elementare de grafică


Interfaţa pentru dispozitive grafice (GDI - Graphics Device Interface) este o componentă a sistemului de operare
Windows 95 şi are ca sarcină afişarea elementelor grafice (inclusiv a textului) pe ecran şi la imprimantă. Aşa cum probabil
vă imaginaţi, GDI este o parte foarte importantă a sistemului de operare, nu numai pentru că aplicaţiile pe care le scrieţi
pentru Windows 95 folosesc exclusiv interfaţa GDI pentru afişarea informaţiilor vizuale, dar şi datorită faptului că
sistemul de operare foloseşte pe scară largă interfaţa GDI pentru afişarea unor elemente din interfaţa cu utilizatorul, cum
ar fi meniuri, bare de derulare, pictograme şi indicatoare de mouse.
Probabil datorită modului de programare cu MS-DOS, deseori programatorii începători care folosesc Windows (dar
uneori chiar şi programatorii cu o oarecare experienţă) sunt tentaţi să „ocolească" interfaţa GDI şi să scrie direct pe
ecranul video. Vă rog - nici să nu vă gândiţi la aşa ceva. Nu veţi avea decât dureri de cap, cauzate, de pildă, de interferarea
cu alte programe Windows sau de incompatibilitatea cu versiunile ulterioare ale sistemului de operare. Exceptând câteva
caracteristici introduse abia în Windows 95, interfaţa GDI există de la primele versiuni ale sistemului de operare. Sistemul
de operare Windows 1.0 era format, în esenţă, din trei biblioteci cu legături dinamice: KERNEL (manipularea proceselor,
gestionarea memoriei şi operaţii de intrare/ieşire cu fişiere), USER (interfaţa cu utilizatorul) şi GDI. Versiunile ulterioare
ale sistemului de operare au adăugat interfeţei GDI noi funcţii, deşi, în cea mai mare parte, compatibilitatea cu programele
existente a fost menţinută. Elementele de bază ale interfeţei GDI - indiferent dacă acest lucru e bun sau rău - au rămas
aproape neschimbate.
Pentru o discuţie exhaustivă despre interfaţa GDI ar fi nevoie să dedicăm un întreg volum acestui subiect; de aceea nu
vom încerca acest lucru aici. Scopul pe care ni l-am propus în capitolul de faţă e să vă oferim suficiente informaţii despre
interfaţa GDI pentru a înţelege modul de lucru al procedurilor de desenare prezentate în restul cărţii, plus alte câteva
informaţii care s-ar putea dovedi utile.

Filozofia GDI
În versiunile pe 32 de biţi ale sistemului de operare Windows elementele grafice sunt manipulate, în principal, prin
funcţii exportate din biblioteca cu legături dinamice GDI32.DLL, care, la rândul ei, foloseşte biblioteca cu legături
dinamice pe 16 biţi GDI.EXE. (În versiunile Windows anterioare, bibliotecile cu legături dinamice erau fişiere cu extensia
.EXE, nu .DLL.) Aceste module apelează proceduri din diferite fişiere driver pentru afişare - un fişier .DRV pentru
afişarea pe ecran şi, probabil, unul sau mai multe fişiere .DRV care controlează imprimantele şi plotterele. Driverele
execută operaţiile de acces la componentele hardware ale monitorului video sau convertesc comenzile GDI în coduri sau
comenzi pe care le pot interpreta diferite tipuri de imprimante. Pentru diferite tipuri de plăci video sau de imprimante este
nevoie, desigur, de diferite fişiere driver.
Deoarece la calculatoarele compatibile PC pot fi ataşate diferite dispozitive de afişare, unul dintre scopurile
principale ale interfeţei GDI este să permită manipularea elementelor grafice independent de dispozitiv. Programele scrise
pentru Windows ar trebui să ruleze fără probleme, indiferent de dispozitivul grafic de afişare folosit, dacă acesta este
acceptat de sistemul de operare. Interfaţa GDI realizează acest lucru prin furnizarea unor componente care izolează
programele de caracteristicile particulare ale diferitelor dispozitive de ieşire.
Lumea dispozitivelor grafice de ieşire este împărţită în două categorii: dispozitive rastru şi dispozitive vectoriale.
Majoritatea dispozitivelor de ieşire pentru PC sunt dispozitive rastru, ceea ce înseamnă că reprezentarea imaginilor se face
prin matrice de puncte. Această categorie include plăcile video, imprimantele matriceale şi imprimantele laser.
Dispozitivele vectoriale, care desenează imaginile prin linii, sunt, în general, limitate la plottere.
Majoritatea limbajelor de programare cu posibilităţi grafice tradiţionale se bazează în exclusivitate pe vectori. Aceasta
înseamnă că un program care foloseşte unul dintre aceste limbaje grafice este despărţit de componentele hardware printr-
un nivel de abstractizare. Dispozitivul de ieşire foloseşte pixeli pentru reprezentarea elementelor grafice, dar programul nu
comunică deloc cu interfaţa în limbajul pixelilor. Deşi puteţi să folosiţi interfaţa Windows GDI ca sistem de desenare
vectorială la nivel înalt, puteţi să folosiţi aceeaşi interfaţa şi pentru manipularea la nivel scăzut a pixelilor.
Din acest punct de vedere, interfaţa Windows GDI este pentru limbajele grafice de interfaţă tradiţionale ceea ce este
C pentru alte limbaje de programare. Limbajul C e bine cunoscut pentru gradul înalt de portabilitate între diferite medii şi
sisteme de operare. Dar, în acelaşi timp, limbajul C e cunoscut şi pentru faptul că permite efectuarea unor operaţii de nivel
scăzut, care în alte limbaje de nivel înalt sunt, deseori, imposibile. Aşa cum C este numit uneori „limbaj de asamblare de
nivel înalt", puteţi să consideraţi că GDI este o interfaţă de nivel înalt către componentele hardware ale dispozitivelor
grafice.
Aşa cum aţi văzut, în mod prestabilit, Windows foloseşte un sistem de coordonate bazat pe pixeli. Majoritatea
limbajelor grafice tradiţionale folosesc un sistem de coordonate „virtual", cu o axă verticală şi una orizontală care merg,
de exemplu, de la 0 la 32 767. Deşi unele limbaje grafice nu vă permit să folosiţi coordonate în pixeli, interfaţă GDI vă

$1
lasă să lucraţi în oricare dintre cele două sisteme de coordonate (şi chiar într-un alt sistem, bazat pe dimensiunile fizice).
Puteţi să folosiţi un sistem de coordonate virtual şi să izolaţi astfel programul de componentele hardware, sau să folosiţi
sistemul de coordonate al dispozitivului şi să scrieţi programul direct pentru componentele hardware utilizate.
Unii programatori consideră că în momentul în care începeţi să gândiţi programul în pixeli aţi renunţat la
independenţa de dispozitiv. Aţi văzut deja în Capitolul 3 că acest lucru nu este întotdeauna adevărat. Secretul constă în
utilizarea pixelilor într-o manieră independentă de dispozitiv. Pentru aceasta este necesar ca limbajul grafic să furnizeze
programului posibilitatea de determinare a caracteristicilor hardware ale dispozitivului de ieşire, astfel încât să poată face
modificările corespunzătoare. De exemplu, în programele SYSMETS am folosit dimensiunile în pixeli ale unui caracter
din fontul sistem standard, ca să stabilim spaţiile pentru textul de pe ecran. Această metodă permite programului să se
adapteze la plăci video cu diferite rezoluţii, dimensiuni ale textului şi rate de afişare, în acest capitol veţi vedea şi alte
metode de determinare a dimensiunilor ecranului.
La început, mulţi utilizatori foloseau sistemul de operare Windows pe un monitor monocrom. Pană nu demult,
utilizatorii calculatoarelor portabile (laptop) nu aveau la dispoziţie decât diferite tonuri de gri. Chiar şi astăzi monitoarele
video folosite pentru Windows 95 au posibilităţi diferite de afişare a culorilor (16 culori, 256 de culori sau „full-color") şi
mulţi utilizatori folosesc imprimante alb-negru. Este posibil să folosiţi aceste dispozitive „orbeşte", dar programul are şi
posibilitatea să determine numărul culorilor disponibile pentru un anumit dispozitiv de afişare şi să folosească toate
avantajele oferite de componentele hardware.
Desigur, aşa cum este posibil să scrieţi programe C care au probleme ascunse de portabilitate atunci când sunt rulate
pe alte calculatoare, este posibil să se strecoare în programele Windows şi anumite probleme neprevăzute, legate de
dependenţa de dispozitiv. Acesta e preţul plătit pentru izolarea incompletă de componentele hardware. Vom studia în acest
capitol capcanele dependenţei de dispozitiv.
De asemenea, trebuie să ştiţi că interfaţa Windows GDI îşi are limitele ei. Cel puţin în acest moment, interfaţă GDI nu
poate să facă tot ce v-aţi putea dori de la o interfaţă grafică. Deşi puteţi să mutaţi pe ecran obiecte grafice, GDI este, în
general, un sistem de afişare static, ce permite numai animaţii limitate. Aşa cum este implementată în Windows 95,
interfaţă GDI nu asigură un suport direct pentru afişarea tridimensională sau pentru rotirea obiectelor. De exemplu, atunci
când desenaţi o elipsă, axele acesteia trebuie să fie paralele cu axele sistemului de coordonate. Deşi unele limbaje grafice
folosesc numere în virgulă mobilă pentru coordonatele virtuale, Windows 95 - din motive legate de performanţă -
foloseşte numai numere întregi pe 16 biţi aceasta este una dintre deficienţele sistemului de operare Windows 95. Windows
NT permite folosirea coordonatelor pe 32 de biţi.

Structura interfeţei GDI


Din punctul de vedere al programatorului, interfaţa GDI este formată din câteva sute de apeluri de funcţii şi unele
tipuri de date, macroinstrucţiuni şi structuri asociate acestor funcţii. Înainte de a studia în detaliu câteva dintre aceste
funcţii, haideţi să vedem care este structura generală a interfeţei GDI.
Tipuri de apeluri de funcţii
În general, apelurile de funcţii GDI pot fi clasificate în mai multe categorii. Chiar dacă nu sunt foarte stricte şi există
unele suprapuneri, aceste categorii pot fi enunţate astfel:
• Funcţii care obţin (sau creează) şi eliberează (sau distrug) un context de dispozitiv. Aşa cum am văzut în Capitolul 3,
pentru a desena aveţi nevoie de un context de dispozitiv. Funcţiile GetDC şi ReleaseDC vă permit să faceţi aceste
lucruri în timpul prelucrării altor mesaje decât WM_PAINT, pe când funcţiile BeginPaint şi EndPaint (deşi din punct
de vedere tehnic fac parte din subsistemul USER din Windows) sunt folosite în timpul prelucrării mesajului
WM_PAINT. Vom discuta în curând despre alte funcţii legate de contextul de dispozitiv.
• Funcţii care obţin informaţii despre contextul de dispozitiv. În programele SYSMETS, cu care aţi făcut cunoştinţă în
Capitolul 3, am folosit funcţia GetTextMetrics ca să obţinem informaţii despre dimensiunile fontului selectat în
contextul de dispozitiv. Mai târziu în acest capitol vom prezenta programul DEVCAPS1, pentru a obţine informaţii
generale despre contextul de dispozitiv.
• Funcţii care desenează ceva. Evident, după rezolvarea problemelor preliminare, acestea sunt funcţiile cu adevărat
importante. În Capitolul 3 am folosit funcţia TextOut pentru afişarea textului în zona client a ferestrei. Aşa cum vom
vedea, alte funcţii GDI sunt folosite pentru desenarea liniilor, a zonelor colorate şi a imaginilor de tip bitmap.
• Funcţii care stabilesc sau obţin atribute ale contextului de dispozitiv. Un „atribut" al contextului de dispozitiv
specifică modul de lucru al funcţiilor de desenare. De exemplu, folosiţi funcţia SetTextColor ca să precizaţi culoarea
textului afişat cu funcţia TextOut (sau cu o altă funcţie de afişare a textului). În programele SYSMETS din Capitolul 3
am folosit funcţia SetTextAlign ca să arătăm interfeţei GDI faptul că poziţia de început a şirului de caractere este în
partea dreaptă a şirului de caractere, nu în partea stângă, aşa cum se întâmplă de obicei. Toate atributele contextului
$2
de dispozitiv au valori prestabilite, care devin active la obţinerea contextului de dispozitiv. Pentru fiecare funcţie de
tip Set există şi o funcţie Get corespondentă, folosită pentru obţinerea valorilor curente ale atributelor contextului de
dispozitiv.
• Funcţii care lucrează cu obiecte GDI. Aici este punctul în care lucrurile se încurcă puţin în interfaţa GDI. Mai întâi
vom da un exemplu: în mod prestabilit, toate liniile pe care le desenaţi folosind interfaţa GDI sunt continue şi au o
grosime standard. Ce se întâmplă, însă, dacă doriţi să desenaţi o linie mai groasă sau o linie punctată ori întreruptă?
Grosimea şi stilul liniei nu sunt atribute ale contextului de dispozitiv. Acestea sunt caracteristici ale „peniţei
logice" („logical pen"). Puteţi să indicaţi o peniţă logică prin specificarea acestor caracteristici în funcţiile CreatePen,
CreatePenIndirect şi ExtCreatePen. Funcţiile de mai sus returnează o variabilă handle a peniţei logice create. (Deşi se
consideră că aceste funcţii fac parte din interfaţa GDI, spre deosebire de majoritatea celorlalte funcţii acestea nu au
nevoie, ca parametru, de o variabilă handle a contextului de dispozitiv.) Pentru folosirea peniţei selectaţi variabila
handle a acesteia în contextul de dispozitiv. Din acest moment, orice linie va fi desenată cu peniţa selectată. Ulterior,
deselectaţi obiectul peniţă din contextul de dispozitiv şi distrugeţi-l. În afara peniţelor, puteţi să folosiţi obiecte GDI
pentru crearea pensulelor care colorează o suprafaţă închisă, pentru fonturi, pentru imagini bitmap şi pentru alte
aspecte ale interfeţei GDI, despre care vom discuta în acest capitol.
Primitive GDI
Elementele grafice pe care le afişaţi pe ecran sau le tipăriţi la imprimantă pot fi împărţite în mai multe categorii,
numite „primitive". Iată care sunt aceste categorii:
• Linii şi curbe. Liniile reprezintă baza oricărui sistem de desenare vectorial. GDI permite folosirea liniilor drepte, a
dreptunghiurilor, a elipselor (inclusiv subsetul de elipse cunoscute sub numele de cercuri), a arcelor - care sunt curbe
reprezentând porţiuni din circumferinţa unei elipse sau a curbelor Bezier. Despre toate aceste clemente vom mai
discuta în capitolul de faţă. Orice curbă mai complexă poate fi desenată ca o linie poligonală, adică o serie de linii
foarte scurte care definesc o curbă. Liniile sunt desenate folosind peniţa curentă selectată în contextul de dispozitiv.
• Suprafeţe pline. Dacă o serie de linii sau de curbe închid o suprafaţă, aceasta poate fi „umplută" folosind pensula GDI
curentă. Această pensulă poate fi o culoare compactă, un model (haşuri orizontale, verticale sau pe diagonală) sau o
imagine bitmap repetată pe verticală sau pe orizontală.
• Imagini bitmap. Imaginile bitmap sunt matrice dreptunghiulare de biţi, care corespund pixelilor unui dispozitiv de
afişare. Imaginile bitmap sunt instrumente de bază pentru sistemele grafice de tip rastru. În general, acestea sunt
folosite pentru afişarea imaginilor complexe (deseori preluate din lumea reală) pe ecran sau pentru tipărirea acestora
la imprimantă. De asemenea, imaginile bitmap sunt folosite pentru afişarea unor mici imagini (cum ar fi pictograme,
indicatoare de mouse şi butoane de pe barele cu instrumente de lucru ale aplicaţiilor) care trebuie afişate foarte rapid.
Interfaţa GDI acceptă două tipuri de imagini bitmap: un tip mai vechi (dar util) de imagini bitmap dependente de
dispozitiv şi un tip mai nou (precum cele din Windows 3.0) de imagini bitmap independente de dispozitiv (DIB -
Device Independent Bitmap) care pot fi stocate în fişiere.
• Text. Textul este mai puţin „matematic" decât alte aspecte ale graficii pe calculator. Textul, aşa cum îl ştim, este legat
de sute de ani de tipografia tradiţională, apreciată adesea ca adevărată artă. Din acest motiv, textul este de multe ori
nu doar cea mai complexă parte a sistemului grafic, ci şi cea mai importantă. Structurile create pentru definirea
fonturilor şi pentru obţinerea informaţiilor despre fonturi sunt printre cele mai mari din Windows. Începând cu
versiunea Windows 3.1, interfaţa GDI acceptă fonturile TrueType, bazate pe contururi umplute, care pot fi manipulate
de alte funcţii GDI. Windows 95 acceptă în continuare şi fonturile mai vechi, de tip bitmap (cum este fontul sistem
prestabilit) pentru compatibilitate şi pentru economisirea spaţiului de memorie.
Alte aspecte
Alte aspecte ale interfeţei GDI nu sunt la fel de uşor de clasificat. Printre acestea se numără:
• Moduri de mapare şi transformări. Deşi, în mod prestabilit, desenarea se face folosind ca unităţi de măsură pixelii, nu
sunteţi limitat la acest sistem de măsură. Modurile de mapare GDI vă permit să desenaţi folosind ca unitate de măsură
inci (sau fracţiuni de inci), milimetri sau orice altă unitate de măsură. De asemenea, Windows 95 asigură suportul
pentru o „transformare reală" exprimată printr-o matrice 3x3. Această transformare permite deformarea şi rotirea
obiectelor grafice. Din păcate, această transformare nu este acceptată sub Windows 95.
• Metafişiere (metafiles). Un metafişier este o colecţie de comenzi GDI stocate într-o formă binară. Metafişierele sunt
folosite, în primul rând, pentru transferarea reprezentărilor unor elemente grafice vectoriale prin intermediul
memoriei temporare (clipboard).
• Regiuni (regions). O regiune este o suprafaţă complexă de orice formă, definită ca o combinaţie booleană de regiuni
mai simple. În general, regiunile sunt stocate intern de GDI ca o serie de linii de scanare, diferite de combinaţia de
$3
linii folosită iniţial pentru definirea regiunii. Puteţi să folosiţi regiunile pentru contururi, pentru umplere sau pentru
decupare.
• Căi (paths). O cale este o colecţie de linii drepte şi curbe stocate intern de GDI. Căile pot fi folosite pentru desenare,
pentru umplere sau pentru decupare. De asemenea, căile pot fi transformate în regiuni.
• Decupare (clipping). Desenarea poate fi limitată la o anumită secţiune a zonei client, numită zonă de decupare, care
poate fi dreptunghiulară sau poate avea o altă formă, definită printr-o serie de linii. Zona de decupare este definită, în
general, de o cale sau de o regiune.
• Palete (palettes). Folosirea paletelor proprii este limitată, în general, numai la monitoarele care pot reda 256 de culori.
Windows rezervă 20 dintre aceste culori pentru sistemul de operare. Celelalte 236 de culori pot fi modificate pentru
afişarea corespunzătoare a imaginilor din lumea reală, stocate ca imagini bitmap.
• Tipărire (printing). Deşi discuţiile din acest capitol sunt limitate doar la afişarea pe ecran, tot ceea ce învăţaţi în acest
capitol poate fi aplicat şi operaţiilor de tipărire. (Vezi Capitolul 15 pentru alte informaţii despre tipărire.)
CONTEXTUL DE DISPOZITIV
Înainte de a începe să desenăm, haideţi să mai aflăm câte ceva despre contextele de dispozitiv.
Atunci când vreţi să desenaţi la un dispozitiv de ieşire grafic (cum ar fi ecranul sau imprimanta) trebuie să obţineţi
mai întâi o variabilă handle a contextului de dispozitiv (DC - device context). Prin obţinerea acestei variabile handle
primiţi permisiunea de folosire a dispozitivului. Variabila handle este apoi inclusă ca parametru în apelul unei funcţii GDI,
identificând dispozitivul la care vreţi să desenaţi.
Contextul de dispozitiv conţine mai multe atribute curente, care specifică modul de lucru al funcţiilor GDI pentru
dispozitivul respectiv. Aceste atribute permit ca la apelarea funcţiilor GDI să fie specificate numai coordonatele de început
sau dimensiunea, nu şi toate celelalte informaţii de care sistemul de operare are nevoie pentru desenarea obiectelor pe
dispozitivul folosit. De exemplu, atunci când apelaţi funcţia TextOut trebuie să specificaţi numai variabila handle a
contextului de dispozitiv, coordonatele de început, textul şi lungimea acestuia. Nu trebuie să precizaţi fontul, culoarea
textului, culoarea fondului din spatele textului şi spaţiul dintre caractere, deoarece aceste atribute fac parte din contextul
de dispozitiv. Atunci când doriţi să modificaţi unul dintre aceste atribute ale contextului de dispozitiv, apelaţi o funcţie
specializată. Următoarele apeluri ale funcţiei TextOut pentru acelaşi context de dispozitiv vor folosi atributul modificat.
Obţinerea variabilei handle a contextului de dispozitiv
Sistemul de operare Windows vă pune la dispoziţie mai multe metode pentru obţinerea variabilei handle a contextului
de dispozitiv. Dacă obţineţi o variabilă handle a contextului de dispozitiv în timpul prelucrării unui mesaj, ar trebui să
ştergeţi această variabilă înainte de ieşirea din procedura de fereastră. După ce este ştearsă, variabila handle nu mai poate
fi folosită (nu mai este validă).
Cea mai cunoscută metodă de obţinere şi de ştergere a variabilei handle a contextului de dispozitiv implică folosirea
funcţiilor BeginPaint şi EndPaint în timpul prelucrării mesajului WM_PAINT:
hdc = BeginPaint (hwnd, &ps);
[alte Unii de program]
EndPaint (hwnd, &ps);

Variabila ps este o structură de tip PAINTSTRUCT. Câmpul hdc al acestei structuri conţine variabila handle a
contextului de dispozitiv. Structura PAINTSTRUCT conţine şi o structură de tip RECT numită rcPaint, care defineşte
dreptunghiul ce cuprinde regiunea invalidă a zonei client a ferestrei. Folosind variabila handle a contextului de dispozitiv,
obţinută prin apelarea funcţiei BeginPaint, nu puteţi să desenaţi decât în regiunea invalidă a ferestrei. Funcţia BeginPaint
validează regiunea invalidă.
Programele Windows pot să obţină variabila handle a contextului de dispozitiv şi în timpul prelucrării altor mesaje
decât WM_PAINT:
hdc = GetDC (hwnd);
(alte linii de program]
ReleaseDC (hwnd, hdc);

Acest context de dispozitiv se aplică zonei client a ferestrei care are variabila handle hwnd. Principala diferenţă între
apelul de mai sus şi metoda folosirii funcţiilor BeginPaint şi EndPaint este că variabila handle returnată de funcţia GetDC
vă permite să desenaţi în toată zona client a ferestrei. În plus, funcţiile GetDC şi ReleaseDC nu validează eventualele
regiuni invalide ale zonei client.
Un program Windows poate să obţină şi o variabilă handle a unui context de dispozitiv care se aplică întregii ferestre,
nu numai zonei client a ferestrei:
$4
hdc = GetWindowDC (hwnd);
[alte linii de program]
ReleaseDC (hwnd, hdc);

Contextul de dispozitiv include, în afară de zona client, bara de titlu a ferestrei, barele de derulare şi chenarul. Funcţia
GetWindowDC este rareori folosită de aplicaţii. Dacă vreţi să experimentaţi folosirea acestei funcţii, trebuie să interceptaţi
mesajele WM_NCPAINT („nonclient paint"), împiedicând sistemul de operare să redeseneze porţiunile din fereastră care
nu fac parte din zona client.
Funcţiile BeginPaint, GetDC şi GetWindowDC obţin variabila handle a contextului de dispozitiv asociat unei anumite
ferestre de pe ecran. O funcţie mai generală pentru obţinerea variabilei handle a unui context de dispozitiv este CreateDC:
hdc = CreateDC (pszDriver, pszDevice, pszOutput, pData);
[alte linii de program]
DeleteDC (hdc);

De exemplu, puteţi să obţineţi variabila handle a contextului de dispozitiv pentru tot spaţiul de afişare, cu următorul
apel:
hdc = CreateDC ("DISPLAY", NULL, NULL, NULL);

Scrierea în afara ferestrei proprii nu este în general recomandată, dar poate fi convenabilă pentru unele aplicaţii
speciale. (Deşi această metodă nu e documentată, se poate obţine o variabila handle a contextului de dispozitiv pentru
întregul ecran şi prin apelarea funcţiei GetDC, cu parametrul NULL). În Capitolul 15 vom folosi funcţia CreateDC pentru
a obţine o variabilă handle a contextului de dispozitiv pentru o imprimantă.
Uneori aveţi nevoie de unele informaţii despre un context de dispozitiv fără să desenaţi nimic. În această situaţie
puteţi să obţineţi o variabila handle a contextului de informaţii („information context") folosind funcţia CreateIC.
Parametrii sunt aceiaşi ca şi pentru funcţia CreateDC:
hdclnfo = CreatelC ("DISPLAY", NULL, NULL, NULL);
[alte linii de program]
DeleteDC (hdclnfo);

Nu puteţi să executaţi operaţii de scriere la un dispozitiv folosind această variabilă handle.


Atunci când lucraţi cu imagini bitmap, poate fi uneori utilă obţinerea unui „context de dispozitiv în memorie":
hdcMem = CreateCompatibleDC (hdc);
[alte linii de program]
DeleteDC (hdcHem)

Acesta este un concept destul de abstract. În esenţă, puteţi să selectaţi o imagine bitmap într-un context de dispozitiv
în memorie şi apoi să desenaţi peste folosind funcţiile GDI. Vom discuta mai târziu despre această tehnică şi o vom folosi
în programul GRAFMENU din Capitolul 10.
Aşa cum am menţionat mai devreme, un metafişier este o colecţie de apeluri GDI codificate într-o formă binară.
Puteţi să creaţi un metafişier prin obţinerea unui context de dispozitiv pentru metafişiere:
hdcMeta = CreateMetaFile(pszFilename);
[alte linii de program]
hmf = CloseMetaFile (hdcMeta);

Cât timp acest context este valid, nici un apel GDI pe care îl faceţi folosind parametrul hdcMeta nu afişează nimic pe
ecran, ci devine parte a metafişierului. Apelaţi apoi funcţia CloseMetaFile şi contextul de dispozitiv este invalidat. Funcţia
returnează o variabilă handle a metafişierului (hmf).
Obţinerea informaţiilor despre contextul de dispozitiv
Un context de dispozitiv se referă, de obicei, la un dispozitiv fizic de ieşire, cum ar fi un monitor video sau o
imprimantă. Dacă aveţi nevoie de anumite informaţii despre acest dispozitiv, cum ar fi dimensiunile ecranului
(dimensiunile în pixeli şi cele fizice) sau posibilităţile de folosire a culorilor, puteţi să le obţineţi prin apelarea funcţiei
GetDeviceCaps („get device capabilities"):
iValue = GetDeviceCaps (hdc, iIndex) ;

Parametrul iIndex este unul dintre cei 28 de identificatori definiţi în fişierele antet din Windows. De exemplu, dacă
iIndex are valoarea HORZRES funcţia GetDeviceCaps returnează lăţimea dispozitivului în pixeli; VERTRES returnează
înălţimea dispozitivului în pixeli. Dacă hdc este o variabilă handle a contextului de dispozitiv pentru un monitor video,
informaţiile obţinute sunt aceleaşi cu cele returnate de funcţia GetSystemMetrics. Dacă hdc este o variabilă handle a

$5
contextului de dispozitiv pentru o imprimantă, funcţia GetDeviceCaps returnează înălţimea şi lăţimea zonei pe care
imprimantă o poate tipări.
Puteţi să folosiţi funcţia GetDeviceCaps şi ca să obţineţi informaţii despre posibilităţile unui dispozitiv de prelucrare
a anumitor tipuri de elemente grafice. Această posibilitate nu este importantă pentru ecran, dar poate fi folosită în cazul
imprimantelor. De exemplu, majoritatea plotterelor nu pot tipări imagini bitmap - iar funcţia GetDeviceCaps vă poate
comunica acest lucru.
Programul DEVCAPS1
Programul DEVCAPS1, prezentat în Figura 4-1, afişează o parte dintre informaţiile pe care poate să le returneze
funcţia GetDeviceCaps.

DEVCAPS1.C -- Device Capabilities Display Program No. 1 wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ;
(c) Charles Petzold, 1996 RegisterClassEx (&wndclass) ;
---------------------------------------------------------*/
#include <windows.h> hwnd = CreateWindow (szAppName, "Device Capabilities",
#include <string.h> WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance,
#define NUMLINES ((int) (sizeof devcaps / sizeof devcaps [0])) NULL) ;

struct ShowWindow (hwnd, iCmdShow) ;


{ UpdateWindow (hwnd) ;
int iIndex ;
char *szLabel ; while (GetMessage (&msg, NULL, 0, 0))
char *szDesc ; {
} TranslateMessage (&msg) ;
devcaps [] = DispatchMessage (&msg) ;
{ }
HORZSIZE, "HORZSIZE", "Width in millimeters:", return msg.wParam ;
VERTSIZE, "VERTSIZE", "Height in millimeters:", }
HORZRES, "HORZRES", "Width in pixels:",
VERTRES, "VERTRES", "Height in raster lines:", LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM
BITSPIXEL, "BITSPIXEL", "Color bits per pixel:", wParam, LPARAM lParam)
PLANES, "PLANES", "Number of color planes:", {
NUMBRUSHES, "NUMBRUSHES", "Number of device brushes:", static int cxChar, cxCaps, cyChar ;
NUMPENS, "NUMPENS", "Number of device pens:", char szBuffer[10] ;
NUMMARKERS, "NUMMARKERS", "Number of device markers:", HDC hdc ;
NUMFONTS, "NUMFONTS", "Number of device fonts:", int i;
NUMCOLORS, "NUMCOLORS", "Number of device colors:", PAINTSTRUCT ps ;
PDEVICESIZE, "PDEVICESIZE", "Size of device structure:", TEXTMETRIC tm ;
ASPECTX, "ASPECTX", "Relative width of pixel:",
ASPECTY, "ASPECTY", "Relative height of pixel:", switch (iMsg)
ASPECTXY, "ASPECTXY", "Relative diagonal of pixel:", {
LOGPIXELSX, "LOGPIXELSX", "Horizontal dots per inch:", case WM_CREATE:
LOGPIXELSY, "LOGPIXELSY", "Vertical dots per inch:", hdc = GetDC (hwnd) ;
SIZEPALETTE, "SIZEPALETTE", "Number of palette entries:", GetTextMetrics (hdc, &tm) ;
NUMRESERVED, "NUMRESERVED", "Reserved palette entries:", cxChar = tm.tmAveCharWidth ;
COLORRES, "COLORRES", "Actual color resolution:" cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
}; cyChar = tm.tmHeight + tm.tmExternalLeading ;
ReleaseDC (hwnd, hdc) ;
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); return 0 ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE case WM_PAINT:


hPrevInstance,PSTR szCmdLine, int iCmdShow) hdc = BeginPaint (hwnd, &ps) ;
{ for (i = 0 ; i < NUMLINES ; i++)
static char szAppName[] = "DevCaps1" ; {
HWND hwnd ; TextOut (hdc, cxChar, cyChar * (1 + i),
MSG msg ; devcaps[i].szLabel,
WNDCLASSEX wndclass ; strlen (devcaps[i].szLabel)) ;
wndclass.cbSize = sizeof (wndclass) ; TextOut (hdc, cxChar + 22 * cxCaps, cyChar * (1 + i),
wndclass.style = CS_HREDRAW | CS_VREDRAW ; devcaps[i].szDesc,
wndclass.lpfnWndProc = WndProc ; strlen (devcaps[i].szDesc)) ;
wndclass.cbClsExtra = 0 ; SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;
wndclass.cbWndExtra = 0 ; TextOut (hdc, cxChar + 22 * cxCaps + 40 * cxChar,
wndclass.hInstance = hInstance ; cyChar * (1 + i), szBuffer,
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wsprintf (szBuffer, "%5d",
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; GetDeviceCaps (hdc, devcaps[i].iIndex))) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject SetTextAlign (hdc, TA_LEFT | TA_TOP) ;
(WHITE_BRUSH) ; }
wndclass.lpszMenuName = NULL ; EndPaint (hwnd, &ps) ;
wndclass.lpszClassName = szAppName ; return 0 ;
$6
case WM_DESTROY: return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
PostQuitMessage (0) ; }
return 0 ;
}
Figura 4-1. Programul DEVCAPS1.
Aşa cum se poate vedea, acest program este foarte asemănător cu programul SYSMETS din Capitolul 3. Pentru a micşora
dimensiunea codului, nu am inclus bare de derulare, deoarece ştiam că textul afişat va încăpea pe ecran. Rezultatele afişate de
acest program pe un monitor VGA cu 256 de culori sunt prezentate în Figura 4-2.

$
Figura 4-2. Fereastra afişata de programul DEVCAPS1 pe un monitor VGA cu 256 de culori.
Dimensiunea dispozitivului
Cea mai importantă informaţie pe care poate să o obţină programul despre monitorul video prin apelarea funcţiei
GetDeviceCaps este dimensiunea ecranului (măsurată atât în pixeli, cat şi în milimetri) şi proporţia dimensiunilor unui pixel.
Aceste informaţii pot fi folosite pentru scalarea imaginilor afişate pe ecran.
Valorile HORZSIZE şi VERTSIZE reprezintă lăţimea şi înălţimea suprafeţei de afişare, în milimetri. Desigur, driverul
Windows nu ştie exact ce tip de monitor este ataşat la placa video. Dimensiunile returnate sunt bazate pe dimensiunile
standard cunoscute de placa video.
Valorile HORZRES şi VERTRES reprezintă lăţimea şi înălţimea suprafeţei de afişare, în pixeli. Pentru contextul de
dispozitiv al unui ecran, aceste dimensiuni sunt aceleaşi cu cele returnate de funcţia GetSystemMetrics. Folosind aceste valori
împreună cu HORZSIZE şi VERTSIZE puteţi să obţineţi rezoluţia dispozitivului în pixeli pe milimetru. Dacă ştiţi că un inci
are 25,4 milimetri, puteţi să calculaţi rezoluţia în puncte pe inci (dpi - dots per inch).
Valorile ASPECTX, ASPECTY şi ASPECTXY reprezintă dimensiunile aproximative pentru lăţimea, înălţimea şi
diagonală unui pixel, rotunjite la cel mai apropiat număr întreg. ASPECTXY este rădăcina pătrată a sumei pătratelor valorilor
ASPECTX şi ASPECTY, aşa cum vă amintiţi din teorema lui Pitagora.
Valorile LOGPIXELSX şi LOGPIXELSY reprezintă numărul de pixeli pe un „inci logic", pe orizontală şi pe verticală. În
cazul monitoarelor, un inci logic nu este egal cu un inci fizic (25,4 milimetri) aşa cum puteţi să vă daţi seama imediat dacă
faceţi câteva calcule cu valorile HORZSIZE, VERTSIZE, HORZRES şi VERTRES. Probabil aţi observat că majoritatea
procesoarelor de text sub Windows afişează o riglă care însă nu este prea corectă: dacă măsuraţi rigla aşa cum este aceasta
afişată pe un ecran VGA, veţi vedea că un inci de pe aceasta are de fapt cam 1,5 inci reali. Aceste programe folosesc pentru
afişarea riglei valorile LOGPIXELSX şi LOGPIXELSY. Dacă programele ar folosi dimensiunile fizice reale, textul de 10 sau
12 puncte ar fi atât de mic, încât nu ar putea fi citit. Dimensiunile logice măresc elementele afişate, astfel încât textul să fie
corect dimensionat. Atunci când vom începe să lucrăm cu texte, vom relua această problemă. Această problemă priveşte doar
ecranele; toate dimensiunile returnate de funcţia GetDeviceCaps pentru imprimante sunt consecvente.
Obţinerea informaţiilor despre culori
Pentru afişarea culorilor este nevoie de mai mulţi biţi. Cu cât se folosesc mai mulţi biţi, cu atât pot fi afişate mai multe
culori. Sau, mai precis, numărul culorilor care pot fi afişate simultan este egal cu 2 la o putere egală cu numărul de biţi
folosiţi. De obicei, biţii sunt organizaţi în planuri de culori - un plan pentru roşu, un plan pentru verde, unul pentru albastru şi

$7
unul pentru intensitatea culorii. Adaptoarele video cu 8, 16 sau 24 de biţi pentru fiecare pixel au un singur plan de culoare, în
care un număr de biţi adiacenţi reprezintă culoarea fiecărui pixel.
Funcţia GetDeviceCaps vă permite să determinaţi modul de organizare a memoriei în adaptoarele video şi numărul de
culori care pot fi reprezentate. Apelul de mai jos returnează numărul de planuri de culoare:
iPlanes = GetDeviceCaps (hdc, PLANES);
Apelul următor returnează numărul de biţi de culoare folosiţi pentru fiecare pixel:
iBitsPixel = GetDeviceCaps (hdc, BITSPIXEL)
Majoritatea adaptoarelor video care pot afişa culori folosesc fie mai multe planuri de culoare, fie mai mulţi biţi de culoare
pentru fiecare pixel, dar nu pe amândouă; cu alte cuvinte, unul dintre cele două apeluri de mai sus va returna valoarea 1.
Numărul de culori care pot fi redate de o placă video se poate calcula cu formula următoare:
iColors = 1<<(iPlanes * iBitsPixel);
Această valoare poate să nu fie identică cu numărul de culori obţinut prin apelarea funcţiei GetDeviceCaps cu parametrul
NUMCOLORS:
iColors = GetDeviceCaps (hdc, NUMCOLORS);
De exemplu, cele două numere vor fi diferite pentru majoritatea plotterelor. În cazul unui plotter, PLANES şi BITSPIXEL
vor avea valoarea 1, dar NUMCOLORS va reprezenta numărul de peniţe pe care le foloseşte plotterul. Pentru dispozitivele
monocrome, funcţia GetDeviceCaps apelată cu parametrul NUMCOLORS returnează valoarea 2.
Aceste valori pot fi diferite şi în cazul adaptoarelor video care permit încărcarea paletelor de culori. Funcţia
GetDeviceCaps apelată cu parametrul NUMCOLORS returnează numărul de culori rezervate de Windows, adică 20. Celelalte
236 de culori pot fi stabilite de program folosind un manager de palete.
Windows foloseşte pentru reprezentarea culorilor o valoare întreagă fără semn, pe 32 de biţi. Tipul de date folosit pentru
culori se numeşte COLORREF. Ultimii trei octeţi ai numărului (cei mai puţin semnificativi) specifică valorile pentru culorile
roşu, verde şi albastru, de la 0 la 255, aşa cum se poate vedea în Figura 4.3. Rezultă o paletă potenţială de 224 culori
(aproximativ 16 milioane de culori).

$
Figura 4.3. Valoarea pe 32 de biţi folosita pentru reprezentarea culorilor.
Valoarea pe 32 de biţi de mai sus e numită deseori „culoare RGB". În fişierele antet din Windows sunt definite mai multe
macroinstrucţiuni pentru lucrul cu valorile RGB. Macroinstrucţiunea RGB acceptă trei argumente, care reprezintă valorile
pentru culorile roşu, verde şi albastru şi le combină într-o valoarea întreagă pe 32 de biţi, fără semn:
#define RBG (r, g, b) ((COLORREF)(((BYTE)(r)|\
((WORD)(g)<<8))|\
(((DWORD) (BYTE) (b))<<16)))
Astfel, valoarea RGB (255, 0, 255) este de fapt 0x00FF00FF, valoarea RGB pentru magenta. Dacă toate cele trei
argumente au valoarea 0, se obţine negrul, iar dacă au valoarea 255, se obţine albul. Macroinstrucţiunile GetRValue,
GetGValue şi GetBValue extrag valorile pentru culorile primare, sub forma unor octeţi fără semn, din valoarea RGB a culorii.
Aceste macroinstrucţiuni sunt utile atunci când apelaţi funcţii Windows care returnează culori RGB.
Numărul de culori returnat de funcţia GetDeviceCaps reprezintă numărul de culori pure pe care le poate afişa dispozitivul
respectiv. Windows poate să folosească amestecarea culorilor - aceasta implică folosirea unui model de pixeli de diferite
culori - pentru reprezentarea altor culori în afara celor pure. Nu toate combinaţiile de valori pentru roşu, verde şi albastru
produc modele diferite de amestecare a culorilor. De exemplu, în cazul unui monitor VGA pe care pot fi afişate 16 culori,
valorile pentru roşu, verde şi albastru trebuie să fie incrementate cu patru, ca să genereze un model diferit de amestecare. Ca
urmare, pentru aceste tipuri de adaptoare aveţi la dispoziţie 218 (sau 262 144) culori amestecate.
Puteţi să determinaţi cea mai apropiată culoare pură pentru o anumită valoare apelând funcţia GetNearestColor:
rgbPureColor = GetNearestColor (hdc, rgbColor) ;

Atributele contextului de dispozitiv


Aşa cum am menţionat mai sus, Windows foloseşte contextul de dispozitiv ca să stocheze atributele care specifică modul
de lucru al funcţiilor GDI pentru afişare. De exemplu, atunci când afişaţi un text folosind funcţia TextOut nu trebuie să
specificaţi culoarea textului sau fontul folosit. Windows foloseşte contextul de dispozitiv ca să obţină aceste informaţii.
Atunci când un program obţine o variabilă handle a unui context de dispozitiv, Windows creează un context de dispozitiv
cu valorile prestabilite pentru toate atributele. Atributele contextului de dispozitiv sunt prezentate în tabelul următor. Un
program poate să schimbe sau să obţină aceste atribute.

$8
Atributul contextului Valoare Funcţia folosită pentru Funcţia folosită pentru
de dispozitiv prestabilită modificarea atributului obţinerea atributului
Mod de MM_TEXT SetMapMode GetMapMode
mapare
Originea ferestrei (0,0) SetWindowOrgEx GetWindowOrgEx
OffsetWindowOrgEx
Originea vizorului (0,0) SetViewportOrgEx GetViewportOrgEx
OffsetViewportOrgEx
Extensia ferestrei (1,1) SetWindowExtEx GetWindowExtEx
SetMapMode
ScaleWindowExtEx
Extensia vizorului (1,1) SetViewportExtEx GetViewportExExt
SetMapMode
ScaleViewportExtEx
Peniţă (pen) BLACK_PEN SelectObject SelectObject
Pensulă (brush) WHITE_BRUSH SelectObject SelectObject

Font SYSTEM_FONT SelectObject SelectObject


Bitmap Nu există SelectObject SelectObject
Poziţia curentă a (0,0) MoveToEx GetCurrentPositionEx
peniţei LineTo PolylineTo
PolyBezierTo
Modul de afişare a OPAQUE SetBkMode GetBkMode
fondului
Culoarea Alb SetBkColor GetBkColor
fondului
Culoarea textului Negru SetTextColor GetTextColor
Mod de R2COPYPEN SetROPZ GetROP2
desenare
Mod de BLACKONWHITE SetStretchBltMode GetStretchBltMode
întindere
Mod de umplere a ALTERNATE SetPolyFillMode GetPolyFillMode
prigoanelor
Spaţiu între 0 SetTextCharacterExtra GetTextCharacterExtra
caractere
Originea pensulei (0,0) în SetBrushOrgEx GetBrushOrgEx
coordonate ecran
Regiune de decupare Nu există SelectObject SelectClipRgn GetClipBox

Salvarea contextului de dispozitiv


În acest capitol veţi întâlni diferite funcţii folosite pentru modificarea atributelor contextului de dispozitiv. În mod
normal, Windows creează un nou context de dispozitiv cu valori prestabilite atunci când apelaţi una dintre funcţiile GetDC
sau BeginPaint. Toate modificările făcute în atributele contextului de dispozitiv se pierd atunci când contextul de dispozitiv
este şters din memorie prin apelarea funcţiei ReleaseDC sau a funcţiei EndPaint. Dacă programul trebuie să folosească un
$9
atribut cu o valoarea diferită de cea prestabilită va trebui să iniţializaţi contextul de dispozitiv de fiecare dată când obţineţi o
variabilă handle:
case WM_Paint:
hdc = BeginPaint (hwnd, &ps);
[iniţializează atributele contextului de dispozitiv]
[desenează zona client a ferestrei]
EndPaint (hwnd, &ps);
return 0;

Deşi această abordare este în general satisfăcătoare, s-ar putea să preferaţi să salvaţi modificările făcute asupra
contextului de dispozitiv la distrugerea acestuia, astfel încât valorile salvate să redevină active la apelarea funcţiilor GetDC
sau BeginPaint.
Puteţi realiza acest lucru incluzând indicatorul flag CS_OWNDC în clasa ferestrei, atunci când o înregistraţi:
wndclass.style = CS_HREDRAW | CS_VREDRAH | CS_OWNDC;

Acum fiecare fereastră pe care o creaţi pe baza acestei clase va avea propriul context de dispozitiv, care va fi păstrat până
la distrugerea ferestrei. Atunci când folosiţi stilul de fereastră CS_OWNDC, trebuie să iniţializaţi contextul de dispozitiv o
singură dată, de obicei în timpul prelucrării mesajului WM_CREATE:
case WM_CREATE:
hdc = GetDC (hwnd);
[iniţiallzează atributele contextului de dispozitiv]
ReleaseDC (hwnd, hdc);

Atributele sunt valide până când le modificaţi în mod explicit.


Stilul de fereastră CS_OWNDC afectează numai contextele de dispozitiv obţinute prin apelarea funcţiilor GetDC sau
BeginPaint, nu şi cele obţinute prin apelarea altor funcţii (cum ar fi GetWindowDC). Stilul de fereastră CS_OWNDC are şi un
dezavantaj: sistemul de operare Windows consumă aproximativ 800 de octeţi pentru salvarea contextului de dispozitiv al
fiecărei ferestre create cu acest stil. Chiar dacă folosiţi stilul de fereastră CS_OWNDC, trebuie să distrugeţi contextul de
dispozitiv înainte de ieşirea din procedura de fereastră.
De asemenea, puteţi să folosiţi şi stilul CS_CLASSDC:
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_CLASSDC;

Acest stil face ca fiecare clasă de fereastră să aibă un context de dispozitiv, folosit în comun de toate ferestrele create pe
baza clasei respective. În general, stilul de fereastră CS_CLASSDC este mai greu de folosit decât stilul CS_OWNDC,
deoarece orice modificare pe care o faceţi asupra atributelor contextului de dispozitiv afectează toate ferestrele create pe baza
aceleiaşi clase. Uneori efectele pot fi destul de bizare.
Dacă s-ar putea să doriţi să modificaţi unele dintre atributele contextului de dispozitiv, să desenaţi ceva folosind noile
atribute şi apoi să reveniţi la vechile valori, salvaţi starea curentă a contextului de dispozitiv prin următorul apel:
iSavedID = SaveDC (hdc);

Modificaţi atributele dorite. Atunci când vreţi să reveniţi la starea contextului de dispozitiv dinaintea apelării funcţiei
SaveDC, folosiţi funcţia RestoreDC:
RestoreDC (hdc, iSavedID);

Puteţi să apelaţi funcţia SaveDC de oricâte ori înainte de a apela funcţia RestoreDC. Dacă vreţi să reveniţi la starea
contextului de dispozitiv de dinaintea ultimului apel al funcţiei SaveDC, folosiţi următorul apel:
RestoreDC (hdc, -1);

Desenarea liniilor
Teoretic, singura funcţie necesară pentru desenare este SetPixel (şi, în unele situaţii, GetPixel). Orice altceva poate fi
făcut prin apelarea unor funcţii de nivel înalt, implementate chiar în modulul GDI sau chiar în codul aplicaţiei. De exemplu,
pentru desenarea unei linii nu trebuie decât să apelaţi de mai multe ori funcţia de „scriere" a unui pixel şi să modificaţi
corespunzător coordonatele x şi y.
În realitate, puteţi să executaţi orice operaţie de desenare folosind numai funcţiile de scriere şi de citire a unui pixel, doar
dacă nu vă deranjează să aşteptaţi rezultatele. Pentru un sistem grafic este mult mai eficientă manipularea operaţiilor de
desenare a unei linii sau a altor operaţii grafice complexe la nivelul driverului dispozitivului, care poate conţine un cod
optimizat pentru executarea acestor operaţii. Mai mult, pe măsură ce tehnologia de afişare devine tot mai complexă, plăcile
specializate conţin coprocesoare tot mai performante, care permit chiar componentelor video hardware să facă desenarea
figurilor.
$10
Interfaţa Windows GDI conţine şi funcţiile SetPixel şi GetPixel. Deşi vom folosi funcţia SetPixel în programul
CONNECT din Capitolul 7, în programele de grafică propriu-zisă aceste funcţii sunt rareori folosite. În majoritatea cazurilor,
cea mai simplă primitivă grafică vectorială trebuie să fie considerată linia.
Windows poate să deseneze linii drepte, linii eliptice (linii curbe, pe circumferinţa unei elipse) şi curbe Bezier. Cele şapte
funcţii acceptate de Windows 95 pentru desenarea liniilor sunt LineTo (linii drepte), Polyline şi PolylineTo (o serie de linii
drepte conectate), PolyPolyline (mai multe linii poligonale), Arc (linii eliptice), PolyBezier şi PolyBezierTo. (Interfaţa GDI din
Windows NT acceptă încă trei funcţii pentru desenarea liniilor: ArcTo, AngleArc şi PolyDraw. Aceste funcţii nu sunt însă
acceptate în Windows 95.) Aspectul liniilor desenate cu aceste funcţii poate fi influenţat de cinci atribute ale contextului de
dispozitiv: poziţia curentă a peniţei (numai pentru funcţiile LineTo, PolylineTo şi PolyBezierTo), peniţa selectată, modul de
afişare a fondului (numai pentru peniţele care nu desenează cu o culoare compactă), culoarea fondului (pentru modul
OPAQUE de afişare a fondului) şi modul de desenare.
Din motive pe care le voi arăta puţin mai târziu, tot în această secţiune voi prezenta şi funcţiile Rectangle, Ellipse,
RoundRect, Chord şi Pie, chiar dacă aceste funcţii sunt folosite şi pentru desenarea suprafeţelor pline.
Funcţia LineTo este una dintre puţinele funcţii GDI care nu are nevoie de dimensiunile complete ale obiectului ce
urmează să fie desenat. Funcţia LineTo desenează o linie de la „poziţia curentă" definită în contextul de dispozitiv pană la
punctul specificat la apelarea funcţiei (exclusiv acest punct). Poziţia curentă este folosită ca punct de plecare şi de alte funcţii
GDI. În contextul de dispozitiv prestabilit, poziţia curentă are iniţial valoarea (0,0). Dacă apelaţi funcţia LineTo fără să stabiliţi
mai întâi poziţia curentă, funcţia desenează o linie pornind din colţul din stânga-sus al zonei client.
Dacă doriţi să desenaţi o linie din punctul (xStart, yStart) pană în punctul (xEnd, yEnd) trebuie să apelaţi mai întâi funcţia
MoveToEx ca să stabiliţi ca poziţie curentă punctul (xStart, ySfart):
MoveToEx (hdc, xStart, yStart, &pt) ;

unde pt este o structură de tip POINT, definită în fişierele antet din Windows astfel:
typedef struct tag POINT
{
LONG x ;
LONG y ;
}
POINT ;

Funcţia MoveToEx nu desenează nimic, ci doar schimbă poziţia curentă. Poziţia curentă anterioară este stocată în
structura de tip POINT. Puteţi apoi să folosiţi funcţia LineTo ca să desenaţi linia:
LineTo (hdc, xEnd, yEnd);

Apelul de mai sus desenează o linie până în punctul (xEnd, yEnd), exclusiv acest punct. După apelul funcţiei LineTo,
poziţia curentă devine (xEnd, yEnd).
O scurtă notă istorică: în versiunile pe 16 biţi ale sistemului de operare Windows, funcţia folosită pentru modificarea
poziţiei curente era MoveTo, care avea numai trei parametri - variabila handle a contextului de dispozitiv şi coordonatele x şi
y. Funcţia MoveTo returna poziţia curentă anterioară sub forma a două valori pe 16 biţi împachetate într-un întreg fără semn pe
32 de biţi. În versiunile pe 32 de biţi ale sistemului de operare (Windows NT şi Windows 95) coordonatele sunt valori pe 32
de biţi. Deoarece în versiunea pe 32 de biţi a limbajului C nu este definit un tip de date întreg pe 64 de biţi, funcţia MoveTo a
fost înlocuită cu funcţia MoveToEx. Această modificare a fost necesară chiar dacă valoarea returnată de funcţia MoveTo nu a
fost folosită aproape niciodată cu adevărat în programare !
Iată o veste bună: dacă nu aveţi nevoie de poziţia curentă anterioară - ceea ce se întâmplă destul de des - puteţi să daţi
ultimului parametru al funcţiei MoveToEx valoarea NULL. De fapt, dacă doriţi să transformaţi codul pe 16 biţi existent în cod
pentru Windows 95, puteţi să definiţi o macroinstrucţiune, astfel:
#define MoveTo (hdc, x, y) MoveToEx (hdc, x, y, NULL)

Vom folosi această macroinstrucţiune în mai multe programe din capitolele următoare.
Iată însă şi o veste proastă. Chiar dacă în Windows 95 coordonatele par a fi valori pe 32 de biţi, din acestea sunt folosiţi
numai cei 16 biţi mai puţin semnificativi. Coordonatele pot avea valori numai între -32.768 şi 32.767.
Puteţi să obţineţi poziţia curentă a peniţei apelând funcţia GetCurrentPosition astfel:
GetCurrentPositionEx (hdc, &pt) ;

Secvenţa de cod care urmează desenează în zona client a ferestrei o grilă formată din linii aflate la distanţa de 100 de
pixeli una de alta, începând din colţul din stânga-sus. Variabila hwnd reprezintă variabila handle a ferestrei, hdc este variabila
handle a contextului de dispozitiv, iar x şi y sunt numere întregi:
$11
GetClientRect (hwnd, &rect);
for (x = 0 ; x < rect.right ; x +=100)
{
MoveToEx (hdc, x, 0, NULL) ;
LineTo (hdc, x, rect.bottom) ;
}
for (y = 0 ; y < rect.bottom ; y +=100)
{
MoveToEx (hdc, 0, y. NULL) ;
LineTo (hdc, rect.right, y) ;
}

Deşi pare incomodă folosirea a două funcţii pentru desenarea unei singure linii, poziţia curentă devine utilă atunci când
trebuie să desenaţi o serie de linii conectate. De exemplu, să presupunem că definiţi o matrice de cinci puncte (10 valori) care
marchează un dreptunghi:
POINT pt [5] = { 100, 100, 200, 100, 200, 200, 100, 200, 100, 100 } ;

Remarcaţi faptul că ultimul punct este acelaşi cu primul. Acum trebuie doar să folosiţi funcţia MoveToEx pentru primul
punct şi apoi funcţia LineTo pentru punctele următoare:
MoveToEx (hdc, pt[0].x, pt[0].y, NULL) ;
for (i = 1; i < 5; i++) LineTo (hdc, pt[i].x, pt[i].y) ;

Deoarece funcţia LineTo desenează o linie care începe la punctul curent şi se termină la punctul specificat la apelare
(exclusiv acesta), nici una dintre coordonate nu este scrisă de două ori de codul de mai sus. Deşi suprascrierea unui punct nu
este, în mod normal, o problemă în cazul monitoarelor video, s-ar putea să nu aibă un rezultat prea plăcut în cazul unui plotter
sau în alte moduri de afişare (despre care vom discuta în curând).
Atunci când aveţi o matrice de puncte pe care vreţi să le conectaţi prin linii, puteţi să desenaţi mai uşor liniile folosind
funcţia Polyline. Instrucţiunea de mai jos desenează acelaşi dreptunghi ca şi secvenţa de cod din exemplul anterior:
Polyline (hdc, pt, 5) ;

Ultimul parametru reprezintă numărul de puncte. Această valoare putea fi reprezentată şi prin expresia (sizeof(pt) /
sizeof(POINT)). Funcţia Polyline are acelaşi efect ca şi o funcţie MoveToEx urmată de mai multe funcţii LineTo, exceptând
faptul că funcţia Polyline nu schimbă poziţia curentă. Funcţia PolylineTo este puţin diferită. Aceasta foloseşte ca punct de
plecare poziţia curentă şi stabileşte ca poziţie curentă sfârşitul ultimei linii desenate. Codul de mai jos desenează acelaşi
dreptunghi folosit ca exemplu şi mai sus:
MoveToEx (hdc, pt[0].x, pt[0].y, NULL) ;
PolylineTo (hdc, pt + 1,4) ;

Deşi puteţi să folosiţi funcţiile Polyline şi PolylineTo şi pentru un număr mic de funcţii, acestea sunt mai utile atunci când
desenaţi o curbă complexă formată din sute sau chiar mii de linii. De exemplu, să presupunem că vreţi să desenaţi o sinusoidă.
Programul SINEWAVE, prezentat în Figura 4-4, vă arată cum să faceţi acest lucru.

/*----------------------------------------- wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;


SINEWAVE.C -- Sine Wave Using Polyline wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
(c) Charles Petzold, 1996 wndclass.hbrBackground = (HBRUSH) GetStockObject
-----------------------------------------*/ (WHITE_BRUSH) ;
#include <windows.h> wndclass.lpszMenuName = NULL ;
#include <math.h> wndclass.lpszClassName = szAppName ;
#define NUM 1000 wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ;
#define TWOPI (2 * 3.14159) RegisterClassEx (&wndclass) ;
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; hwnd = CreateWindow (szAppName, "Sine Wave Using Polyline",
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE WS_OVERLAPPEDWINDOW,
hPrevInstance, CW_USEDEFAULT, CW_USEDEFAULT,
PSTR szCmdLine, int iCmdShow) CW_USEDEFAULT, CW_USEDEFAULT,
{ NULL, NULL, hInstance, NULL) ;
static char szAppName[] = "SineWave" ; ShowWindow (hwnd, iCmdShow) ;
HWND hwnd ; UpdateWindow (hwnd) ;
MSG msg ; while (GetMessage (&msg, NULL, 0, 0))
WNDCLASSEX wndclass ; {
wndclass.cbSize = sizeof (wndclass) ; TranslateMessage (&msg) ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ; DispatchMessage (&msg) ;
wndclass.lpfnWndProc = WndProc ; }
wndclass.cbClsExtra = 0 ; return msg.wParam ;
wndclass.cbWndExtra = 0 ; }
wndclass.hInstance = hInstance ;
$12
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM LineTo (hdc, cxClient, cyClient / 2) ;
wParam, LPARAM lParam) for (i = 0 ; i < NUM ; i++)
{ {
static int cxClient, cyClient ; pt[i].x = i * cxClient / NUM ;
HDC hdc ; pt[i].y = (int) (cyClient / 2 *
int i; (1 - sin (TWOPI * i / NUM))) ;
PAINTSTRUCT ps ; }
POINT pt [NUM] ; Polyline (hdc, pt, NUM) ;
switch (iMsg) return 0 ;
{ case WM_DESTROY:
case WM_SIZE: PostQuitMessage (0) ;
cxClient = LOWORD (lParam) ; return 0 ;
cyClient = HIWORD (lParam) ; }
return 0 ; return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
case WM_PAINT: }
hdc = BeginPaint (hwnd, &ps) ;
MoveToEx (hdc, 0, cyClient / 2, NULL) ;
Figura 4-4. Programul SINEWAVE
Acest program conţine o matrice cu 1000 de structuri POINT. Odată cu incrementarea variabilei de ciclare a ciclului for
de la 0 la 999 este incrementată şi variabila membru x de la 0 la cxClient. Variabila membru y a structurii primeşte valorile
corespunzătoare unui ciclu al unei sinusoide lărgite astfel încât să umple zona client. Curba este desenată cu funcţia Polyline.
Deoarece funcţia Polyline este implementată la nivelul driverului, desenarea se face mult mai repede decât dacă ar fi fost
apelată de 1000 de ori funcţia LineTo. Rezultatele programului sunt prezentate în Figura 4-5.

$
Figura 4-5. Fereastra afişată de programul SINEWAVE.
Dreptunghiuri de încadrare
În continuare aş vrea să discutăm despre funcţia Arc, funcţie care desenează o curbă eliptică. Dar funcţia Arc nu prea are
sens dacă nu discutăm mai întâi despre funcţia Ellipse, care, la rândul ei, nu are sens fără o discuţie despre funcţia Rectangle.
Si dacă discutăm despre funcţiile Rectangle şi Ellipse, putem la fel de bine să discutăm şi despre funcţiile RoundRect, Chord
şi Pie.
Problema este că funcţiile Rectangle, Ellipse, RoundRect, Chord şi Pie nu sunt tocmai nişte funcţii de desenare a liniilor.
Desigur, ele desenează şi linii, dar şi colorează zona închisă, cu pensula curentă de colorare a suprafeţelor. În mod prestabilit,
această pensulă are culoarea albă, aşa că s-ar putea ca operaţia să nu fie atât de evidentă atunci când încercaţi pentru prima
dată să utilizaţi aceste funcţii. Deşi, în sens strict, funcţiile aparţin unei alte secţiuni din capitolul de faţă, „Desenarea
suprafeţelor pline", vom discuta acum despre fiecare.
Funcţiile de mai sus sunt asemănătoare prin faptul că sunt construite pe baza unui „dreptunghi de încadrare" („bounding
box"). Dumneavoastră definiţi o casetă care încadrează obiectul - dreptunghiul de încadrare - şi Windows desenează obiectul
în acest dreptunghi.
Cea mai simplă dintre aceste funcţii desenează un dreptunghi:
Rectangle (hdc, xLeft, yTop, xRight, yBottom) ;

Punctul (xLeft, yTop) reprezintă colţul din stânga-sus al dreptunghiului, iar punctul (xRight, yBottom) reprezintă colţul din
dreapta-jos. O figură desenată prin apelarea funcţiei Rectangle e prezentată în Figura 4-6. Laturile dreptunghiului sunt
întotdeauna paralele cu laturile orizontală şi verticală ale ecranului.
xLeft xRight
xTop

$13

xBottom
Figura 4-6. O figură desenată folosind funcţia Rectangle.
Programatorii care au mai lucrat cu sisteme grafice sunt deja familiarizaţi probabil cu problema pixelului suplimentar.
Unele sisteme grafice desenează figurile astfel încât să includă şi coordonatele din dreapta şi de jos, iar alte sisteme grafice
desenează figurile până la aceste coordonate (fără să le includă). Windows foloseşte cea de-a doua metodă; voi prezenta în
continuare o comparaţie, care vă va ajuta să înţelegeţi mai bine mecanismul.
Să presupunem că avem următorul apel de funcţie:
Rectangle (hdc, 1, 1, 5, 4) ;

Am menţionat mai sus faptul că Windows desenează figura într-un dreptunghi de încadrare. Puteţi să vă gândiţi la ecran
ca la o grilă în care fiecare pixel este o celulă delimitată de liniile grilei. Caseta de încadrare imaginară este desenată pe grilă,
apoi dreptunghiul este desenat în limitele casetei. Iată cum va fi desenată figura respectivă:

$
Zona care separă dreptunghiul de marginile din stânga şi de sus ale zonei client are lăţimea de un pixel. Windows
foloseşte pensula curentă pentru colorarea celor doi pixeli din interiorul dreptunghiului.
După ce aţi aflat cum se desenează un dreptunghi, folosind aceiaşi parametri, veţi putea, la fel de simplu, să desenaţi şi o
elipsă:
Ellipse (hdc, xLeft, yTop, xRight, yBottom) ;
În Figura 4-7 este prezentată o figură (împreună cu dreptunghiul de încadrare) desenată cu ajutorul funcţiei Ellipse.

$
Funcţia pentru desenarea unui dreptunghi cu colţurile rotunjite foloseşte acelaşi dreptunghi de încadrare ca şi funcţiile
Rectangle şi Ellipse, dar are încă doi parametri:
RoundRect (hdc, xLeft, yTop, xRight, yBottom, xCornerEllipse, yCornerEllipse) ;

În Figura 4-8 este prezentată o figură desenată cu această funcţie.

$14
$
Pentru desenarea colţurilor rotunjite, Windows foloseşte o mică elipsă. Lăţimea elipsei este xCornerEllipse iar înălţimea
acesteia este yCornerEllipse. Imaginaţi-vă că Windows împarte elipsa în patru părţi egale, folosind fiecare sfert pentru câte un
colţ al dreptunghiului. Colţurile dreptunghiului sunt cu atât mai rotunjite cu cât valorile parametrilor xCornerEllipse şi
yCornerEllipse sunt mai mari. Dacă xCornerEllipse este egal cu jumătate din diferenţa dintre xLeft şi xRight iar
yCornerEllipse este egal cu jumătate din diferenţa dintre yTop şi yBottom, atunci funcţia RoundRect va desena o elipsă.
Colţurile rotunjite ale dreptunghiului din Figura 4-8 au fost desenate folosind o elipsă ale cărei dimensiuni au fost
calculate cu următoarele formule:
xCornerEllipse = (xRight - xLeft) / 4 ;
yCornerEllipse = (yTop - yBottom) / 4 ;

Aceasta este o abordare simplistă, iar rezultatele nu arată foarte bine, deoarece rotunjirea colţurilor este mai pronunţată pe
latura mai mare a dreptunghiului. Pentru corectarea acestei probleme, probabil veţi stabili dimensiuni reale pentru parametrii
xCornerEllipse şi yCornerEllipse.
Funcţiile Arc, Chord şi Pie primesc aceiaşi parametri:
Arc (hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd) ;
Chord (hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd) ;
Pie (hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd) ;

În Figura 4-9 este prezentată o linie desenată cu ajutorul funcţiei Arc; Figurile 4-10 şi 4-11 prezintă desene realizate cu
ajutorul funcţiilor Chord şi Pie. Windows foloseşte o linie imaginară ca să lege punctul definit de coordonatele xStart, yStart
cu centrul elipsei. Din punctul în care această linie intersectează dreptunghiul de încadrare, Windows începe să deseneze o
linie pe circumferinţa elipsei. De asemenea, Windows foloseşte o linie imaginară ca să lege punctul (xEnd, yEnd) cu centrul
elipsei. În punctul în care această linie intersectează dreptunghiul de încadrare, Windows încheie desenul.
În cazul funcţiei Arc, Windows şi-a terminat treaba, deoarece arcul este o linie eliptică, nu o suprafaţă plină. În cazul
funcţiei Chord, Windows uneşte capetele arcului, iar în cazul funcţiei Pie, uneşte capetele arcului cu centrul elipsei.
Interioarele suprafeţelor închise obţinute astfel sunt colorate cu pensula curentă.
S-ar putea să vă întrebaţi de ce trebuie să folosiţi aceste poziţii de început şi de sfârşit în funcţiile Arc, Chord şi Pie. De ce
să nu specificaţi chiar punctele de început şi de sfârşit ale arcului pe circumferinţa elipsei? Ei bine, puteţi să o faceţi, dar mai
întâi trebuie să determinaţi unde se află aceste puncte, pe când metoda folosită de Windows rezolvă problema fără să fie
nevoie de o asemenea precizie.
Programul LINEDEMO prezentat în Figura 4-12 desenează un dreptunghi, o elipsă, un dreptunghi cu colţurile rotunjite şi
două linii, dar nu în această ordine. Programul demonstrează faptul că funcţiile care desenează suprafeţe închise colorează
aceste suprafeţe, deoarece liniile desenate anterior sunt ascunse în spatele elipsei. Rezultatele rulării programului LINEDEMO
sunt prezentate în Figura 4-13.

$15
$

/*-------------------------------------------------- MSG msg ;


LINEDEMO.C -- Line-Drawing Demonstration Program WNDCLASSEX wndclass ;
(c) Charles Petzold, 1996 wndclass.cbSize = sizeof (wndclass) ;
--------------------------------------------------*/ wndclass.style = CS_HREDRAW | CS_VREDRAW ;
#include <windows.h> wndclass.lpfnWndProc = WndProc ;
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; wndclass.cbClsExtra = 0 ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE wndclass.cbWndExtra = 0 ;
hPrevInstance, wndclass.hInstance = hInstance ;
PSTR szCmdLine, int iCmdShow) wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
{ wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
static char szAppName[] = "LineDemo" ; wndclass.hbrBackground = (HBRUSH) GetStockObject
HWND hwnd ; (WHITE_BRUSH) ;
$16
wndclass.lpszMenuName = NULL ; case WM_SIZE:
wndclass.lpszClassName = szAppName ; cxClient = LOWORD (lParam) ;
wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; cyClient = HIWORD (lParam) ;
RegisterClassEx (&wndclass) ; return 0 ;
hwnd = CreateWindow (szAppName, "Line Demonstration", case WM_PAINT:
WS_OVERLAPPEDWINDOW, hdc = BeginPaint (hwnd, &ps) ;
CW_USEDEFAULT, CW_USEDEFAULT, Rectangle (hdc, cxClient / 8, cyClient / 8,
CW_USEDEFAULT, CW_USEDEFAULT, 7 * cxClient / 8, 7 * cyClient / 8) ;
NULL, NULL, hInstance, NULL) ; MoveToEx (hdc, 0, 0, NULL) ;
ShowWindow (hwnd, iCmdShow) ; LineTo (hdc, cxClient, cyClient) ;
UpdateWindow (hwnd) ; MoveToEx (hdc, 0, cyClient, NULL) ;
while (GetMessage (&msg, NULL, 0, 0)) LineTo (hdc, cxClient, 0) ;
{ Ellipse (hdc, cxClient / 8, cyClient / 8,
TranslateMessage (&msg) ; 7 * cxClient / 8, 7 * cyClient / 8) ;
DispatchMessage (&msg) ; RoundRect (hdc, cxClient / 4, cyClient / 4,
} 3 * cxClient / 4, 3 * cyClient / 4,
return msg.wParam ; cxClient / 4, cyClient / 4) ;
} EndPaint (hwnd, &ps) ;
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM return 0 ;
wParam, LPARAM lParam) case WM_DESTROY:
{ PostQuitMessage (0) ;
static int cxClient, cyClient ; return 0 ;
HDC hdc ; }
PAINTSTRUCT ps ; return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
switch (iMsg) }
{
Figura 4-12. Programul LINEDEMO.
Curbe Bezier (Bezier splines)
Termenul „spline" se referea odată la o riglă flexibilă din lemn, metal sau cauciuc (florar), folosită pentru desenarea
curbelor. De exemplu, dacă aveaţi un număr de puncte aparţinând unui grafic şi doreaţi să desenaţi o curbă între acestea (prin
interpolare sau extrapolare) trebuia să marcaţi mai întâi punctele pe hârtie. Ancorând apoi rigla în punctele respective, nu vă
rămânea decât să trasaţi curba de-a lungul riglei. (Vă rog să nu râdeţi. Metoda evocată seamănă cu practici utilizate în secolul
trecut, dar eu ştiu din proprie experienţă că riglele mecanice şi riglele de calcul erau încă folosite în anumite instituţii acum 15
ani.)

$
Figura 4-13. Fereastra afişată de programul LINEDEMO.
În prezent, „spline" se referă la o funcţie matematică. Aceste funcţii au diferite forme. Curbele Bezier sunt unele dintre
cele mai cunoscute pentru programarea grafică. Această funcţie este o achiziţie destul de recentă în arsenalul instrumentelor
grafice de lucru disponibile la nivelul sistemului de operare şi are o origine mai puţin obişnuită. În anii '60, compania de
automobile Renault trecea de la proiectarea manuală a caroseriilor (care implica folosirea argilei) la o proiectare asistată de
calculator. Erau necesare instrumente matematice, iar Pierre Bezier a pus la punct un set de formule care s-au dovedit foarte
utile pentru rezolvarea acestei probleme.
De atunci, datorită formei bidimensionale, curba Bezier s-a dovedit a fi cea mai utilă curbă pentru grafica pe calculator.
De exemplu, în limbajul PostScript funcţiile Bezier sunt folosite pentru toate curbele - elipsele sunt aproximare prin curbe
Bezier De asemenea, funcţiile Bezier sunt folosite pentru definirea contururilor caracterelor din fonturile PostScript.
(Fonturile TrueType folosesc o formă simplificată, mai rapida, a curbelor.)

$17
O curbă Bezier este definită prin patru puncte - două capete şi două puncte de control. Capetele curbei sunt ancorate în
cele două puncte finale. Punctele de control acţionează ca nişte „magneţi" care deformează linia dreaptă dintre cele două
puncte finale. Acest lucru este ilustrat cel mai bine de un program interactiv, numit Bezier prezentat în Figura 4-14.

/*--------------------------------------- static POINT apt[4] ;


BEIZER.C -- Bezier Splines Demo HDC hdc ;
(c) Charles Petzold, 1996 int cxClient, cyClient ;
---------------------------------------*/ PAINTSTRUCT ps ;
#include <windows.h> switch (iMsg)
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; {
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE case WM_SIZE:
hPrevInstance, cxClient = LOWORD (lParam) ;
PSTR szCmdLine, int iCmdShow) cyClient = HIWORD (lParam) ;
{ apt[0].x = cxClient / 4 ;
static char szAppName[] = "Bezier" ; apt[0].y = cyClient / 2 ;
HWND hwnd ; apt[1].x = cxClient / 2 ;
MSG msg ; apt[1].y = cyClient / 4 ;
WNDCLASSEX wndclass ; apt[2].x = cxClient / 2 ;
wndclass.cbSize = sizeof (wndclass) ; apt[2].y = 3 * cyClient / 4 ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ; apt[3].x = 3 * cxClient / 4 ;
wndclass.lpfnWndProc = WndProc ; apt[3].y = cyClient / 2 ;
wndclass.cbClsExtra = 0 ; return 0 ;
wndclass.cbWndExtra = 0 ; case WM_MOUSEMOVE:
wndclass.hInstance = hInstance ; if (wParam & MK_LBUTTON || wParam &
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; MK_RBUTTON)
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; {
wndclass.hbrBackground = (HBRUSH) GetStockObject hdc = GetDC (hwnd) ;
(WHITE_BRUSH) ; SelectObject (hdc, GetStockObject (WHITE_PEN)) ;
wndclass.lpszMenuName = NULL ; DrawBezier (hdc, apt) ;
wndclass.lpszClassName = szAppName ; if (wParam & MK_LBUTTON)
wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; {
RegisterClassEx (&wndclass) ; apt[1].x = LOWORD (lParam) ;
hwnd = CreateWindow (szAppName, "Bezier Splines", apt[1].y = HIWORD (lParam) ;
WS_OVERLAPPEDWINDOW, }
CW_USEDEFAULT, CW_USEDEFAULT, if (wParam & MK_RBUTTON)
CW_USEDEFAULT, CW_USEDEFAULT, {
NULL, NULL, hInstance, NULL) ; apt[2].x = LOWORD (lParam) ;
ShowWindow (hwnd, iCmdShow) ; apt[2].y = HIWORD (lParam) ;
UpdateWindow (hwnd) ; }
while (GetMessage (&msg, NULL, 0, 0)) SelectObject (hdc, GetStockObject (BLACK_PEN)) ;
{ DrawBezier (hdc, apt) ;
TranslateMessage (&msg) ; ReleaseDC (hwnd, hdc) ;
DispatchMessage (&msg) ; }
} return 0 ;
return msg.wParam ; case WM_PAINT:
} InvalidateRect (hwnd, NULL, TRUE) ;
void DrawBezier (HDC hdc, POINT apt[]) hdc = BeginPaint (hwnd, &ps) ;
{ DrawBezier (hdc, apt) ;
PolyBezier (hdc, apt, 4) ; EndPaint (hwnd, &ps) ;
MoveToEx (hdc, apt[0].x, apt[0].y, NULL) ; return 0 ;
LineTo (hdc, apt[1].x, apt[1].y) ; case WM_DESTROY:
MoveToEx (hdc, apt[2].x, apt[2].y, NULL) ; PostQuitMessage (0) ;
LineTo (hdc, apt[3].x, apt[3].y) ; return 0 ;
} }
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
wParam, LPARAM lParam) }
{
Figura 4-14. Programul BEZIER.
Deoarece acest program foloseşte unele tehnici de prelucrare a mesajelor de la mouse pe care le vom studia abia în
Capitolul 6, nu vom discuta modul intern de lucru al programului (deşi acesta poate să vi se pară evident). În schimb, puteţi să
folosiţi programul ca să experimentaţi modul de funcţionare a curbelor Bezier. În acest program, cele două puncte finale ale
curbei sunt stabilite, pe verticală, la jumătatea zonei client şi, pe orizontală, la 1/4 şi 3/4 din lăţimea zonei client. Cele două
puncte de control pot fi mutate, primul - apăsând butonul din stânga al mouse-ului şi deplasând mouse-ul până când
indicatorul Iui ajunge în locul dorit, iar al doilea -apăsând butonul din dreapta al mouse-ului şi deplasând mouse-ul. Figura
4-15 prezintă un rezultat tipic.
În afara curbei Bezier, programul desenează şi două linii drepte: una de la primul capăt al curbei (cel din stânga) până la
primul punct de control, şi a doua de la celălalt capăt al curbei (din dreapta) până la al doilea punct de control.

$18
Funcţiile Bezier sunt considerate utile pentru proiectarea asistată de calculator, datorită câtorva caracteristici:
În primul rând, cu puţină practică, de obicei puteţi să manipulaţi curba până ajunge la o formă apropiată de cea dorită.
În al doilea rând, curbele Bezier sunt foarte uşor de controlat. În unele variante ale funcţiilor spline, curba nu trece prin
nici unul din punctele care o definesc. Curbele Bezier, însă, sunt întotdeauna ancorate în cele două puncte finale. (Aceasta este
una dintre ipotezele de la care porneşte calculul formulei Bezier.) De asemenea, unele forme ale funcţiilor spline au puncte de
singularitate, din care curba se întinde la infinit. În proiectarea asistată de calculator acest lucru este de cele mai multe ori
evitat. Ca urmare, curbele Bezier sunt întotdeauna limitate de un patrulater (numit „carcasă convexă" - „convex hull") format
prin unirea punctelor finale şi a punctelor de control.
În al treilea rând, o altă caracteristică a curbelor Bezier implică relaţiile dintre punctele finale şi punctele de control.
Curba este întotdeauna tangentă la linia trasată de la primul punct final la primul punct de control şi are întotdeauna aceeaşi
direcţie cu această linie. (Această caracteristică este ilustrată vizual de programul BEZIER.) De asemenea, curba este
întotdeauna tangentă la linia trasată de la al doilea punct final la al doilea punct de control şi are întotdeauna aceeaşi direcţie
cu această linie. (Acestea sunt alte două ipoteze de la care este derivată formula Bezier.)
În al patrulea rând, curbele Bezier sunt satisfăcătoare şi din punct de vedere estetic. Ştiu că acesta este un criteriu
subiectiv, dar nu este numai părerea mea.
Înainte de apariţia sistemului de operare Windows 95, trebuia să creaţi propriile curbe Bezier folosind funcţia Polyline.
De asemenea, trebuia să cunoaşteţi următoarele ecuaţii parametrice ale curbelor Bezier:
x(t) = (1-t)3x0 + 3t(1-t)2x1 + 3t2(1-t)x2 + t3x3
y(t) = (1-t)3y0 + 3t(1-t)2y1 + 3t2(1-t)y2 + t3y3
unde (x0, y0) este punctul de început al curbei, (x3, y3) este punctul de sfârşit al curbei, iar cele două puncte de control sunt (x1,
y1) şi (x2, y2). Curba este trasată pentru t având valori de la 0 la 1.

$
Figura 4-15. Fereastra afişată de programul BEZIER.
În Windows 95 nu mai este nevoie să ştiţi aceste formule. Pentru trasarea uneia sau a mai multor curbe Bezier conexe,
puteţi să folosiţi instrucţiunea:
PolyBezier (hdc, pt, iCount) ;

sau instrucţiunea:
PolyBezierTo (hide, pt, iCount) ;

În ambele cazuri, pt este o matrice de structuri POINT. Pentru funcţia PolyBezier primele patru puncte specifică (în
această ordine) punctul de început, primul punct de control, al doilea punct de control şi punctul final al curbei Bezier.
Următoarele curbe Bezier au nevoie doar de trei puncte, deoarece punctul de început al următoarei curbe Bezier este punctul
de sfârşit al primei curbe, şi aşa mai departe. Parametrul iCount reprezintă numărul de puncte din matrice, adică unu plus de
trei ori numărul curbelor pe care vreţi să le desenaţi.
Funcţia PolyBezierTo foloseşte poziţia curentă ca punct de început pentru prima curbă Bezier. Fiecare curbă desenată are
nevoie doar de trei puncte. La returnarea funcţiei, poziţia curentă este punctul final al ultimei curbe desenate.

$19
Atenţie: atunci când trasaţi o serie de curbe Bezier conectate între ele, legătura va fi lină numai dacă al doilea punct de
control al primei curbe, punctul de sfârşit al primei curbe (care este şi punctul de început al celei de-a doua curbe) şi primul
punct de control al celei de-a doua curbe sunt coliniare, adică se află pe aceeaşi linie dreaptă.
Folosirea peniţelor de stoc
Atunci când apelaţi oricare dintre funcţiile de desenare a liniilor despre care am discutat, Windows foloseşte pentru
desenarea liniilor „peniţa" (pen) curentă selectată în contextul de dispozitiv. Peniţa selectată determină culoarea, lăţimea şi
tipul de linie (acestea pot fi continue, punctate sau întrerupte). Peniţa din contextul prestabilit de dispozitiv se numeşte
BLACK_PEN. Aceasta desenează o linie compactă, de culoare neagră, cu grosimea de un pixel, indiferent de modul de
mapare. BLACK_PEN este una dintre cele trei peniţe „de stoc" (stock pen) furnizate de Windows. Celelalte două peniţe de
stoc sunt WHITE_PEN şi NULL_PEN. NULL_PEN este o peniţă care nu desenează nimic. În plus, puteţi să creaţi peniţe
proprii.
În programele Windows puteţi ajunge la aceste peniţe prin variabile handle. În fişierele antet din Windows este definit
tipul de date HPEN, care reprezintă o variabilă handle a unei peniţe. Puteţi să definiţi o variabilă (de exemplu, hPen) folosind
această definiţie de tip:
HPEN hPen ;

Puteţi să obţineţi variabila handle a uneia dintre peniţele de stoc apelând funcţia GetStockObject. De exemplu, să
presupunem că vreţi să folosiţi peniţa de stoc numită WHITE_PEN. Obţineţi variabila handle a acesteia astfel:
hPen = GetStockObject (WHITE_PEN) ;

Apoi trebuie să selectaţi în contextul de dispozitiv peniţa a cărei variabilă aţi obţinut-o, apelând funcţia SelectObject:
SelectObject (hdc, hPen) ;

După acest apel toate liniile pe care le desenaţi vor folosi peniţa WHITE_PEN, până când selectaţi o altă peniţă în
contextul de dispozitiv sau ştergeţi contextul de dispozitiv.
În loc să definiţi explicit variabila hPen, puteţi să combinaţi funcţiile GetStockObject şi SelectObject într-o singură
instrucţiune:
SelectObject (hdc, GetStockObject (WHITE_PEN)) ;

Dacă apoi vreţi să reveniţi la peniţa BLACK_PEN, puteţi să obţineţi o variabilă handle a acesteia şi să o selectaţi în
contextul de dispozitiv tot cu o singură instrucţiune:
SelectObject (hdc, GetStockObject (WHITE_PEN));

Funcţia SelectObject returnează variabila handle a peniţei selectate anterior în contextul de dispozitiv. Dacă deschideţi un
nou context de dispozitiv şi executaţi instrucţiunea:
hPen = SelectObject (hdc, GetStockObject (WHITE_PEN)) ;

atunci peniţa curentă selectată în contextul de dispozitiv va fi WHITE_PEN şi hPen va fi variabila handle a peniţei
BLACK_PEN. Puteţi apoi să selectaţi peniţa BLACK_PEN în contextul de dispozitiv cu următoarea instrucţiune:
SelectObject (hdc, hPen) ;

Crearea, selectarea şi ştergerea peniţelor


Deşi folosirea peniţelor definite ca obiecte de stoc este foarte convenabilă, aveţi la dispoziţie doar un număr limitat de
opţiuni: o peniţă neagră, pentru linie continuă, una albă pentru linie continuă sau una care nu desenează nimic. Dacă doriţi
ceva diferit, trebuie să creaţi propriile dumneavoastră peniţe. Iată procedura generală: creaţi mai întâi o „peniţă
logică" (logical pen) care este doar o descriere a unei peniţe, folosind funcţiile CreatePen sau CreatePenIndirect. (De
asemenea, puteţi să folosiţi şi funcţia ExtCreatePen, despre care vom discuta atunci când vom ajunge la căile GDI.) Aceste
funcţii returnează o variabilă handle a peniţei logice. Selectaţi peniţa în contextul de dispozitiv apelând funcţia SelectObject.
Apoi puteţi să desenaţi linii cu peniţa selectată. La un moment dat nu poate fi selectată, într-un context de dispozitiv, decât o
peniţă. După ce ştergeţi contextul de dispozitiv (sau după ce selectaţi o altă peniţă în contextul de dispozitiv) puteţi să ştergeţi
peniţa logică pe care aţi creat-o apelând funcţia DeleteObject. După ce faceţi acest lucru, variabila handle a peniţei nu mai este
validă.
O peniţă logică este un „obiect GDI". Dumneavoastră creaţi şi folosiţi peniţa, dar aceasta nu aparţine programului, ci
modulului GDI. Peniţa este unul dintre cele şase obiecte GDI pe care puteţi să le creaţi. Celelalte cinci sunt pensulele,
imaginile bitmap, regiunile, fonturile şi paletele. Exceptând paletele, toate celelalte obiecte pot fi selectate în contextul de
dispozitiv folosind funcţia SelectObject.
Pentru folosirea obiectelor GDI trebuie să respectaţi următoarele reguli:
$20
➢ La sfârşitul programului ştergeţi toate obiectele GDI pe care le-aţi creat.
➢ Nu ştergeţi obiectele GDI în timp ce sunt selectate într-un context de dispozitiv valid.
➢ Nu ştergeţi obiectele de stoc.
Chiar dacă sunt nişte reguli rezonabile, neplăcerile pot exista. Voi prezenta câteva exemple ca să înţelegeţi cum
funcţionează aceste reguli şi pentru a putea evita eventuale probleme.
Sintaxa generică a funcţiei CreatePen este:
hPen = CreatePen (iPenStyle, iWidth, rgbColor) ;

Parametrul iPenStyle precizează dacă se desenează o linie continuă, o linie punctată sau una întreruptă. Parametrul
iPenStyle poate fi unul dintre identificatorii definiţi în fişierele antet din Windows. Figura 4-16 prezintă stilul de linie produs
de fiecare identificator.

$
Figura 4-16. Cele şapte stiluri de peniţe.
Pentru stilurile PS_SOLID, PS_NULL şi PS_INSIDEFRAME, parametrul iWidth reprezintă grosimea liniei. Dacă iWidth
are valoarea 0, liniile desenate de Windows vor avea grosimea de un pixel. Liniile desenate cu peniţele de stoc au grosimea de
un pixel. Dacă parametrul iWidth are o valoare mai mare de 1, Windows va desena o linie continuă.
Parametrul rgbColor din funcţia CreatePen este un număr întreg fără semn reprezentând culoarea peniţei. Pentru toate
stilurile de peniţe, exceptând PS_INSIDEFRAME, atunci când selectaţi peniţa în contextul de dispozitiv, Windows
converteşte acest parametru la cea mai apropiată culoare pură pe care o poate reprezenta dispozitivul de afişare.
PS_INSIDEFRAME este singurul stil care poate folosi culori amestecate, dar numai pentru grosimi mai mari de un pixel.
Stilul PS_INSIDEFRAME are şi o altă particularitate atunci când este folosit pentru funcţii care definesc o suprafaţă
plină: pentru toate stilurile de peniţe, exceptând PS_INSIDEFRAME, dacă grosimea liniei este mai mare de un pixel, peniţa
este centrată pe linie, astfel încât o parte a liniei poate să fie desenată în afara dreptunghiului de încadrare; în cazul folosirii
stilului PS_INSIDEFRAME, linia este desenată în întregime în interiorul dreptunghiului de încadrare.
Puteţi să creaţi o peniţă şi cu ajutorul unei structuri de tip LOGPEN („logical pen"), apelând funcţia CreatePenIndirect.
Dacă programul foloseşte un număr mai mare de peniţe diferite pe care Ie iniţializaţi în codul sursă, această metodă este mai
eficientă. Mai întâi definiţi o variabilă de tip LOGPEN, cum ar fi logpen:
LOGPEN logpen ;
Această structură are trei membri: lopnStyle (UINT) este stilul peniţei, lopn Width (POINT) este grosimea peniţei în
unităţi logice, iar lopnColor (COLORREF) este culoarea peniţei. Membrul lopn Width este o structură de tip POINT, dar
Windows foloseşte numai membrul lopnWidth.x al acestei structuri şi ignoră membrul lopnWidth.y. Apoi creaţi peniţa,
transmiţând funcţiei CreatePenIndirect adresa structurii logpen:
hPen = CreatePenIndirect (&logpen) ;
De asemenea, puteţi să obţineţi informaţii despre o peniţă logică existentă. Dacă aveţi deja o variabilă handle a unei
peniţe puteţi să copiaţi datele care definesc peniţa logică într-o structură de tip LOGPEN folosind funcţia GetObject:
GetObject (hPen, sizeof (LOGPEN), (LPVOID) &logpen) ;
Remarcaţi faptul că funcţiile CreatePen şi CreatePenIndirect nu au nevoie de o variabilă handle a contextului de
dispozitiv. Aceste funcţii creează peniţe logice care nu au nici o legătură cu contextul de dispozitiv până când nu apelaţi
funcţia SelectObject. De exemplu, puteţi să folosiţi aceeaşi peniţă logică pentru mai multe dispozitive, cum ar fi imprimanta şi
ecranul.
Iată o metodă pentru crearea, selectarea şi ştergerea peniţelor. Să presupunem că programul foloseşte trei peniţe: una
neagră cu grosimea de un pixel, una roşie cu grosimea de trei pixeli şi una neagră cu linie punctată. Mai întâi definiţi
variabilele pentru stocarea variabilelor handle ale celor trei stilouri:
static HPEN hPen1, hPen2, hPen3;
În timpul prelucrării mesajului WM_CREATE, creaţi cele trei peniţe:
$21
hPen1 = CreatePen (PS_SOLID, 1, 0);
hPen2 = CreatePen (PS_SOLID, 3, RGB (255, 0, 0));
hPen3 = CreatePen (PS_DOT, 0, 0);

În timpul prelucrării mesajului WM_PAINT (sau în orice alt moment în care aveţi o variabilă handle validă a contextului
de dispozitiv) puteţi să selectaţi oricare dintre aceste peniţe în contextul de dispozitiv şi să desenaţi:
SelectObject (hdc, hPen2) ;
[funcţii de desenare a liniilor]
SelectObject (hdc, hPen1) ;
[alte funcţii de desenare a liniilor]

În timpul prelucrării mesajului WM_DESTROY puteţi să ştergeţi cele trei peniţe create:
DeleteObject (hPen1) ;
DeleteObject (hPen2) ;
DeleteObject (hPen3) ;

Aceasta este metoda cea mai directă pentru crearea, selectarea şi ştergerea peniţelor, dar are dezavantajul că peniţele
create ocupă spaţiu în memorie pe toată durata rulării programului. O altă soluţie este să creaţi peniţele necesare în timpul
prelucrării fiecărui mesaj WM_PAINT şi să le ştergeţi după apelarea funcţiei EndPaint. (Puteţi să le ştergeţi şi înainte de
apelarea funcţiei EndPaint, dar trebuie să aveţi grijă să nu ştergeţi peniţa curentă selectată în contextul de dispozitiv.)
De asemenea, puteţi să creaţi peniţele atunci când sunt necesare, combinând funcţiile CreatePen şi SelectObject în
aceeaşi instrucţiune:
SelectObject (hdc, CreatePen (PS_DASH, 0, RGB (255, 0, 0))) ;
Din acest moment, veţi folosi o peniţă pentru linii întrerupte de culoare roşie. După ce terminaţi de făcut desenele cu
acest tip de linie, puteţi să ştergeţi peniţa. Hopa! Cum puteţi să ştergeţi peniţa dacă nu aţi salvat variabila handle a acesteia?
Vă amintiţi că funcţia SelectObject returnează variabila handle a peniţei selectate anterior în contextul de dispozitiv. Aşa că
puteţi să ştergeţi peniţa selectând în contextul de dispozitiv peniţa de stoc BLACK_PEN şi ştergând variabila handle returnată
de funcţia SelectObject:
DeleteObject (SelectObject (hdc, GetStockObject (BLACK PEN))) ;

Există şi o altă metodă. Atunci când selectaţi în contextul de dispozitiv o peniţă nou creată, salvaţi variabila handle
returnată de funcţia SelectObject;
hPen = SelectObject (hdc, CreatePen (PS_DASH, 0, RGB (255, 0, 0))) ;

Ce este hPen? Dacă aceasta este prima apelare a funcţiei SelectObject de la obţinerea contextului de dispozitiv, atunci
hPen este variabila handle a obiectului de stoc BLACK_PEN. Puteţi acum să selectaţi peniţa BLACK_PEN în contextul de
dispozitiv şi să o ştergeţi pe cea creată (variabila handle returnată la a doua apelare a funcţiei SelectObject) printr-o singură
instrucţiune:
DeleteObject (SelectObject (hdc, hPen)) ;

Umplerea golurilor
Folosirea peniţelor pentru linii punctate sau pentru linii întrerupte ridică o întrebare interesantă: ce se întâmplă cu pauzele
dintre puncte sau dintre liniuţe? Culoarea acestor spaţii depinde de atributele pentru culoarea fondului şi de modul de desenare
a fondului, definite în contextul de dispozitiv.
Modul prestabilit de desenare a fondului este OPAQUE, ceea ce înseamnă că Windows umple spaţiile cu culoarea
fondului, care în mod prestabilit este alb. Rezultatele sunt aceleaşi în cazul pensulei WHITE_BRUSH, folosită de majoritatea
programelor în clasa ferestrei pentru ştergerea fondului.
Puteţi să schimbaţi culoarea fondului pentru completarea spaţiilor goale dintre linii, prin apelarea funcţiei SetBkColor:
SetBkColor (hdc, rgbColor) ;

Ca şi în cazul valorii rgbColor folosită pentru culoarea peniţei, Windows converteşte valoarea specificată într-o culoare
pură. Puteţi să obţineţi culoarea definîtă în contextul de dispozitiv prin apelarea funcţiei GetBkColor.
De asemenea, puteţi să împiedicaţi colorarea spaţiilor stabilind modul TRANSPARENT de desenare a fondului:
SetBkMode (hdc, TRANSPARENT) ;

Windows va ignora culoarea fondului şi nu va mai colora spaţiile goale. Modul de desenare a fondului (TRANSPARENT
sau OPAQUE) poate fi obţinut cu ajutorul funcţiei GetBkMode.

$22
Moduri de desenare
Apectul liniilor desenate pe ecran este afectat şi de modul de desenare definit în contextul de dispozitiv. Imaginaţi-vă că
desenaţi o linie a cărei culoare este bazată nu numai pe culoarea peniţei, ci şi pe culoarea originală a suprafeţei pe care se face
desenarea. Imaginaţi-vă o cale prin care să puteţi folosi aceeaşi peniţă ca să desenaţi o linie albă pe fond negru, dar şi o linie
neagră pe fond alb, ştiind ce culoare are fondul. Dacă vă este utilă această facilitate, puteţi să o obţineţi prin schimbarea
modului de desenare.
Atunci când foloseşte o peniţă pentru desenarea unei linii, Windows face de fapt o operaţie booleană între pixelii peniţei
şi pixelii de pe suprafaţa destinaţie. Efectuarea operaţiei booleene între pixeli se numeşte „operaţie rastru" (ROP - raster
operation).
Deoarece desenarea unei linii implică numai două modele de pixeli (peniţa şi destinaţia), operaţia booleană se numeşte
„operaţie rastru binară" sau ROP2. Windows defineşte 16 coduri ROP2 care specifică modul de combinare între pixelii peniţei
şi pixelii suprafeţei, în contextul de dispozitiv prestabilit, modul de desenare definit este R2_COPYPEN, ceea ce înseamnă că
Windows copiază pixelii peniţei pe suprafaţa destinaţie - adică modul normal în care suntem obişnuiţi să funcţioneze o peniţă.
Există alte 15 coduri ROP2.
Care este originea celor 16 coduri ROP2 diferite? Pentru exemplificare, să presupunem că avem un sistem monocrom.
Culoarea destinaţiei (culoarea zonei client a ferestrei) poate fi negru (reprezentată prin valorea 0) sau alb (1). La fel, culoarea
peniţei poate fi alb sau negru. Există patru combinaţii posibile în cazul folosirii unei peniţe alb/negru pe o suprafaţă alb/negru:
alb pe alb, alb pe negru, negru pe alb şi negru pe negru.
Ce se întâmplă cu destinaţia după ce desenaţi cu peniţa? O posibilitate este ca linia să fie desenată întotdeauna cu negru,
indiferent de culoarea peniţei şi a destinaţiei: acest mod de desenare este indicat de codul R2_BLACK. O altă posibilitate este
ca linia să fie desenată cu negru, exceptând situaţia în care atât peniţa cât şi destinaţia sunt negre, caz în care linia este
desenată cu alb. Deşi pare puţin ciudat, Windows are un nume pentru acest mod de desenare: R2_NOTMERGEPEN.
Windows face o operaţie orientată pe biţi de tip SAU între pixelii destinaţie şi pixelii peniţei, şi inversează rezultatul.
Tabelul de mai jos prezintă toate cele 16 moduri de desenare ROP2. Tabelul indică modul în care sunt combinate culoarea
peniţei (P) şi culoarea destinaţiei (D) pentru obţinerea culorii afişate. În coloana „Operaţia booleană" sunt folosite notaţiile C
pentru indicarea modului în care sunt combinaţi pixelii peniţei cu pixelii destinaţiei.

Peniţă (P): 1 1 0 0 Operaţia Mod de desenare


Destinaţie (D): 1 0 1 0 booleană

Rezultate: 0 0 0 0 0 R2_BLACK

0 0 0 1 ~(P | D) R2_NOTMERGEPEN
0 0 1 0 ~P & D R2_MASKNOTPEN
0 0 1 1 ~P R2_NOTCOPYPEN
0 1 0 0 P & ~D R2_MASKPENNOT
0 1 0 1 ~D R2_NOT
0 1 1 0 P^D R2_XORPEN
0 1 1 1 ~(P & D) R2_NOTMASKPEN
1 0 0 0 P&D R2_MASKPEN
1 0 0 1 ~(P ^ D) R2_NOTXORPEN
1 0 1 0 D R2_NOP
1 0 1 1 ~P | D R2_MERGENOTPEN
1 1 0 0 P R2_COPYPEN
(prestabilit)
1 1 0 1 P | ~D R2_MERGEPENNOT
1 1 1 0 P|D R2_MERGEPEN
$23
1 1 1 1 1 R2_WHITE

Puteţi să selectaţi un nou mod de desenare în contextul de dispozitiv astfel:


SetROP2 (hdc, iDrawMode) ;
unde parametrul iDrawMode este una dintre valorile din coloana „Mod de desenare" a tabelului de mai sus. Puteţi să obţineţi
modul curent de desenare astfel:
iDrawMode = GetROP2 (hdc) ;

Modul de desenare prestabilit al contextului de dispozitiv este R2_COPYPEN, care transferă la destinaţie culoarea
peniţei. Modul R2_NOTCOPYPEN desenează cu alb dacă peniţa este neagră şi cu negru dacă peniţa este albă. Modul
R2_BLACK desenează întotdeauna cu negru, indiferent de culoarea peniţei şi a destinaţiei. În mod similar, modul
R2_WHITE desenează întotdeauna cu alb. Modul R2_NOP este o „operaţie nulă" - destinaţia rămâne nemodificată.
Pentru început, am folosit ca exemplu un sistem monocrom. În realitate, pe un monitor monocrom Windows poate simula
diferite tonuri de gri prin amestecarea pixelilor albi cu cei negri. Atunci când desenează cu o peniţă pe un fond având culori
amestecate, Windows execută o operaţie orientată pe biţi pentru fiecare pixel. Modul R2_NOT inversează întotdeauna
destinaţia, indiferent de culoarea acesteia şi de culoarea peniţei. Acest mod de desenare este util atunci când nu cunoaşteţi
culoarea destinaţiei, deoarece liniile vor fi întotdeauna vizibile. (Sau aproape întotdeauna, pentru că în cazul unui fond 50%
gri, liniile vor fi virtual invizibile.) Folosirea modului R2_NOT este ilustrată în programul BLOKOUT din Capitolul 6.
Desenarea suprafeţelor pline
Să mergem un pas mai departe şi să trecem la desenarea figurilor. Cele şapte funcţii Windows pentru desenarea figurilor
sunt prezentate în tabelul următor:

Funcţie Figura

Rectangle Dreptunghi cu colţuri drepte


Ellipse Elipsă
RoundRect Dreptunghi cu colţuri rotunjite
Chord Arc pe circumferinţa unei elipse, având capetele unite printr-o coardă
Pie Suprafaţă de forma unei felii de plăcintă, reprezentând un segment de
Polygon elipsă.
PolyPolygon Figură geometrică având mai multe laturi Mai multe figuri geometrice
cu mai multe laturi

Windows desenează conturul figurilor folosind peniţa curentă selectată în contextul de dispozitiv. Pentru acest contur sunt
folosite atributele stabilite pentru modul de desenare a fondului, culoarea fondului şi modul de desenare, ca şi în cazul
desenării unei linii simple. Tot ceea ce aţi învăţat despre linii se aplică şi în cazul contururilor.
Figurile sunt umplute folosind pensula selectată în contextul de dispozitiv. În mod prestabilit, aceasta este pensula de stoc
WHITE_BRUSH, ceea ce înseamnă că interiorul figurilor va fi umplut cu alb. Windows defineşte şase pensule de stoc:
WHITE_BRUSH, LTGRAY_BRUSH, GRAY_BRUSH, DKGRAY_BRUSH, BLACK_BRUSH şi NULL_BRUSH (sau
HOLLOW_BRUSH).
Puteţi să selectaţi una dintre aceste pensule în contextul de dispozitiv la fel cum selectaţi o peniţă de stoc. Windows
defineşte tipul HBRUSH ca variabilă handle a unei pensule, aşa că puteţi să definiţi mai întâi o variabilă de acest tip:
HBRUSH hBrush ;

Puteţi să obţineţi variabila handle a pensulei de stoc GRAY_BRUSH apelând funcţia GetStockObject:.
hBrush = GetStoCkObject (GRAY_BRUSH) ;

Puteţi să o selectaţi apoi în contextul de dispozitiv folosind funcţia SelectObject:


SelectObject (hdc, hBrush) ;

Din acest moment, figurile desenate vor avea interiorul gri.


Dacă vreţi să desenaţi o figură fără contur, selectaţi în contextul de dispozitiv peniţa NULL_PEN:
SelectObject (hdc, GetStockObject (NULL_PEN)) ;

Dacă vreţi să desenaţi o figură fără să îi umpleţi interiorul, selectaţi în contextul de dispozitiv pensula NULL_BRUSH:
$24
SelectObject (hdc, GetStockObject (NULL_BRUSH)) ;

Aşa cum puteţi să creaţi peniţe proprii, puteţi să creaţi şi pensule proprii. Vom trata în curând şi acest subiect
Funcţia Polygon şi modul de umplere a poligoanelor
Am discutat deja despre primele cinci funcţii de desenare a suprafeţelor pline. Polygon este cea de-a şasea funcţie de
desenare a unei figuri colorate în interior, cu contur. Apelul funcţiei Polygon este asemănător cu cel al funcţiei Polyline:
Polygon (hdc, pt, iCount) ;

Parametrul pt este un pointer la o matrice de structuri POINT, iar iCount reprezintă numărul de puncte din matrice. Dacă
ultimul punct din matrice este diferit de primul punct, Windows mai desenează o linie care conectează ultimul şi primul punct.
(Acest lucru nu se întâmplă şi în cazul funcţiei Polyline.)
Suprafaţa închisă de poligon este colorată folosind pensula curentă în două moduri, în funcţie de modul de umplere a
poligoanelor, definit în contextul de dispozitiv. Modul prestabilit de umplere a poligoanelor este ALTERNATE, ceea ce
înseamnă că Windows colorează numai suprafeţele închise la care se poate ajunge din exteriorul poligonului prin traversarea
unui număr impar de laturi (1, 3, 5 şi aşa mai departe). Celelalte suprafeţe interioare nu sunt umplute. Dacă selectaţi
WINDING ca mod de umplere, Windows colorează toate suprafeţele închise de poligon. Puteţi să selectaţi modul de umplere
cu ajutorul funcţiei SetPolyFillMode:
SetPolyFillMode (hdc, iMode) ;
Cele două moduri de umplere a poligoanelor sunt ilustrate cel mai bine pe modelul unei stele cu cinci colţuri. În Figura
4-17 steaua din partea stângă a fost desenată cu modul de umplere ALTERNATE, iar steaua din partea dreaptă a fost desenată
cu modul de umplere WINDING.

$ $
Figura 4-17. Figuri desenate cu cele două moduri de umplere a poligoanelor ALTERNATE (stângă) şi WINDING

Umplerea suprafeţelor interioare


Interiorul figurilor Rectangle, RoundRect, Ellipse, Chord, Pie, Polygon şi PollyPolygon este umplut cu pensula - numită
uneori şi „model" („pattern") - selectată în contextul de dispozitiv. O pensulă este de fapt o imagine bitmap 8x8 repetată pe
verticală şi pe orizontală, pentru umplerea unei suprafeţe.
Atunci când Windows foloseşte metoda amestecării culorilor (dithering) pentru afişarea unui număr mai mare de culori
decât ar fi în mod normal disponibile pe monitorul respectiv, de fapt foloseşte o pensulă pentru fiecare culoare. Pe un ecran
monocrom, Windows poate să afişeze 64 de tonuri de gri prin amestecarea pixelilor pentru alb cu cei pentru negru. Pentru
negru toţi biţii din matricea 8x8 au valoarea zero. Unul dintre cei 64 de biţi are valoarea 1 (adică alb) pentru primul ton de gri,
doi biţi au valoarea 1 pentru al doilea ton de gri şi aşa mai departe, până când toţi cei 64 de biţi au valoarea 1 ca să se obţină
albul pur. În cazul unui monitor color, culorile amestecate sunt tot imagini bitmap, dar domeniul disponibil de culori este mult
mai mare.
Windows conţine patru funcţii pe care puteţi să le folosiţi pentru crearea pensulelor logice. Selectaţi o pensulă în
contextul de dispozitiv folosind funcţia SelectObject. Ca şi peniţele logice, pensulele logice sunt obiecte GDI. Trebuie să
ştergeţi toate pensulele pe care le-aţi creat, dar nu trebuie să le ştergeţi cât timp sunt selectate în contextul de dispozitiv.
Iată prima funcţie pentru crearea unei pensule logice:
hBrush = CreateSolidBrush (rgbColor) ;

$25
Cuvântul Solid din numele acestei funcţii nu înseamnă că pensula foloseşte o culoare pură. Atunci când selectaţi pensula
în contextul de dispozitiv, Windows creează o imagine bitmap 8x8 pentru culorile amestecate şi foloseşte imaginea respectivă
atunci când este necesară pensula selectată.
Puteţi să creaţi şi o pensulă „haşurată" cu linii orizontale, verticale sau oblice. Acest stil de pensule este folosit frecvent
pentru colorarea barelor din diagrame sau grafice şi pentru desenele executate de plottere. Funcţia care creează o pensulă
haşurată este:
hBrush = CreateHatchBrush (iHatchStyle, rgbColor) ;

Parametrul iHatchStyle precizează aspectul haşurii şi poate avea una dintre următoarele valori: HS_HORIZONTAL,
HS_VERTICAL, HS_FDIAGONAL, HS_BDIAGONAL, HS_CROSS şi HS_DIAGCROSS. Figura 4-18 prezintă haşurile
produse de fiecare dintre stilurile de mai sus.
Parametrul rgbColor din funcţia CreateHatchBrush reprezintă culoarea liniilor cu care se face haşurarea. Atunci când
selectaţi pensula în contextul de dispozitiv, Windows converteşte această culoare în cea mai apropiată culoare pură. Spaţiul
dintre liniile de haşură sunt colorate în funcţie de modul de desenare a fondului şi de culoarea fondului, definite în contextul
de dispozitiv. Dacă modul de desenare a fondului este OPAQUE, culoarea fondului (care este convertită, la rândul ei, într-o
culoare pură) este folosită pentru umplerea spaţiilor dintre linii, în acest caz, nici liniile de haşură, nici culoarea de umplere nu
pot fi culori amestecate. Dacă modul de desenare a fondului este TRANSPARENT, Windows desenează liniile de haşura fără
să umple spaţiile dintre acestea.

$
Figura 4-18. Cele şase stiluri de haşura.
Deoarece pensulele sunt întotdeauna imagini bitmap 8x8, aspectul pensulelor haşurate va depinde de rezoluţia
dispozitivului pe care se face afişarea. Fiecare dintre haşurile din Figura 4-18 a fost desenată într-un dreptunghi de 32x16
pixeli, ceea ce înseamnă că imaginea bitmap 8x8 a fost repetată de patru ori pe orizontală şi de două ori pe verticală. Pe o
imprimantă laser cu rezoluţia 300 dpi (dots per inch) acelaşi dreptunghi de 32x16 pixeli ar ocupa o suprafaţă cu înălţimea de 1
/9 inci si lăţimea de 1/19 inci.
Cu ajutorul funcţiei CreatePatternBrush puteţi să creaţi pensule proprii, bazate pe o imagine bitmap:
hBrush = CreatePatternBrush (hBitmap) ;

Vom discuta despre această funcţie în secţiunea rezervată imaginilor bitmap din capitolul de faţă.
Windows conţine o funcţie care poate înlocui toate celelalte trei funcţii de creare a pensulelor (CreateSolidBrush,
CreateHatchBrush şi CreatePatternBrush):
hBrush = CreateBrushIndirect (&logbrush) ;

Variabila logbrush este o structură de tip LOGBRUSH („logical brush"). Cele trei câmpuri ale structurii sunt prezentate
mai jos. Valoarea câmpului lbStyle determină modul în care Windows interpretează celelalte două câmpuri:

lbStyle (UINT) lbColor (COLORREF) lbHatch (LONG)


BS_SOLID Culoarea pensulei Ignorat
BS_HOLLOW Ignorat Ignorat
BS_HATCHED Culoarea haşurii Stilul de haşură
BS_PATTERN Ignorat Variabila handle a unei imagini bitmap
Am folosit mai devreme funcţia SelectObject ca să selectăm în contextul de dispozitiv o peniţă logică, funcţia
DeleteObject ca să ştergem o peniţă logică şi funcţia GetObject ca să obţinem informaţii despre o peniţă logică. Puteţi să
folosiţi aceleaşi funcţii şi pentru pensule. Dacă aveţi o variabilă handle a unei pensule, puteţi să selectaţi pensula în contextul
de dispozitiv folosind funcţia SelectObject:
SelectObject (hdc, hBrush) ;

O pensulă creată poate fi ştearsă cu ajutorul funcţiei DeleteObject:


DeleteObject (hBrush) ;

$26
Totuşi, nu ştergeţi pensula selectată în contextul de dispozitiv. Dacă vreţi să obţineţi informaţii despre o pensulă, apelaţi
funcţia GetObject:
GetObject (hBrush, sizeof (LOGBRUSH), (LPVOID) &logbrush) ;

unde variabila logbrush este o structură de tip LOGBRUSH („logical brush").


Modurile de mapare
Până acum am presupus că toate desenele se fac într-un sistem de coordonate raportate la colţul, din stânga-sus al zonei
client şi folosind ca unitate de măsură pixelul. Acesta este modul de lucru prestabilit, dar nu este singurul mod de lucru.
Un atribut al contextului de dispozitiv care afectează aproape toate operaţiile de desenare din zona client este „modul de
mapare" („mapping mode"). Alte patru atribute ale contextului de dispozitiv - originea ferestrei, originea vizorului (viewport),
extensia ferestrei şi extensia vizorului - sunt strâns legate de modul de mapare.
Majoritatea funcţiilor GDI primesc coordonate sau dimensiuni ca parametri. Iată, ca exemplu, funcţia TextOut:
TextOut (hdc, x, y, szBuffer, iLength) ;

Parametrii x şi y indică poziţia în care începe textul. Parametrul x reprezintă poziţia pe axa orizontală, iar parametrul y
reprezintă poziţia pe axa verticală. Deseori, pentru indicarea acestui punct este folosită notaţia (x, y).
În funcţia TextOut şi, de fapt, în toate funcţiile GDI, coordonatele sunt furnizate în „unităţi logice". Windows trebuie să
transforme „unităţile logice" în „unităţi de dispozitiv", adică în pixeli. Această transformare este guvernată de modul de
mapare, de originile ferestrei, ale vizorului şi de extensiile ferestrei şi ale vizorului. De asemenea, modul de mapare stabileşte
şi orientarea axelor x şi y, adică determină sensul în care cresc valorile coordonatelor x şi y.
Windows defineşte opt moduri de mapare. Acestea sunt prezentate în tabelul următor, folosind identificatorii definiţi în
fişierele antet din Windows:

Mod de mapare Unităţi logice Creşterea valorilor axax axay

MM_TEXT Pixel Spre dreapta În jos


MM_LOMETRIC 0,1 mm Spre dreapta În sus
MM_HIMETRIC 0,01mm Spre dreapta În sus
MM_LOENGLISH 0,01 inci Spre dreapta În sus
MM_HIENGLISH 0,001 inci Spre dreapta Însus
MM_TWIPS 1/1440 inci Spre dreapta În sus
MM_ISOTROPIC Arbitrar (x = y) Selectabil Selectabil
MM_ANISOTROPIC Arbitrar (x != y) Selectabil Selectabil

Puteţi să selectaţi modul de mapare folosind funcţia SetMapMode:


SetMapMode (hdc, iMapHode) ;

unde iMapMode este unul dintre cei opt identificatori definiţi pentru modurile de mapare. Puteţi să obţineţi modul de mapare
curent folosind funcţia GefMapMode:
iMapMode = GetMapMode (hdc) ;

Modul de mapare prestabilit este MM_TEXT. În acest mod de mapare unităţile logice sunt aceleaşi cu unităţile fizice,
ceea ce vă permite (sau, privind dintr-o altă perspectivă, vă forţează) să lucraţi în pixeli. Într-un apel al funcţiei TextOut care
arată astfel:
TextOut (hdc, 8, 16, szBuffer, iLength) ;

textul începe la o distanţă de opt pixeli faţă de marginea din stânga a zonei client şi de 16 pixeli faţă de marginea de sus a
acesteia.
Dacă modul de mapare este MM_LOENGLISH, o unitate logică este egală cu o sutime de inci:
SetMapHode (hdc, HM_LOENGLISH) ;

$27
Acum apelul de mai sus al funcţiei TextOut trebuie să arate astfel:
TextOut (hdc, 50, -100, szBuffer, iLength) ;

Textul afişat începe la 0,5 inci faţă de marginea din stânga şi la 1 inci faţă de marginea de sus a zonei client. (Motivul
folosirii unei valori negative pentru coordonata y va deveni evident ceva mai târziu, atunci când vom discuta în detaliu despre
modurile de mapare.) Celelalte moduri de mapare permit programului să specifice coordonatele în milimetri, în puncte pentru
imprimantă sau într-un sistem de coordonate arbitrar.
Dacă vi se pare convenabilă folosirea pixelilor ca unitate de măsură, puteţi să folosiţi numai modul de mapare
MM_TEXT. Dacă trebuie să afişaţi o imagine la dimensiunile reale în inci sau milimetri, puteţi să obţineţi informaţiile
necesare cu ajutorul funcţiei GetDeviceCaps şi să faceţi singur scalarea. Celelalte moduri de mapare sunt doar metode
convenabile de evitare a acestor operaţii de scalare.
Indiferent de modul de mapare folosit, toate coordonatele specificate la apelarea funcţiilor Windows trebuie să fie numere
întregi cu semn având valori de la -32 768 la 32 767. În plus, unele funcţii care folosesc coordonate pentru punctele de început
şi de sfârşit ale unui dreptunghi cer ca lăţimea şi înălţimea dreptunghiului să nu depăşească 32 767.
Coordonate de dispozitiv şi coordonate logice
S-ar putea să vă puneţi următoarea întrebare: „Dacă folosesc modul de mapare MM_LOENGLISH voi primi mesajele
WM_SIZE în sutimi de inci?" Bineînţeles că nu. Windows va folosi în continuare coordonatele de dispozitiv pentru toate
mesajele (cum ar fi WM_SIZE, WM_MOVE şi WM_MOUSEMOVE), pentru toate funcţiile care nu fac parte din interfaţa
GDI şi chiar pentru unele funcţii GDI. Lucrurile se petrec în felul următor: modul de mapare fiind un atribut al contextului de
dispozitiv, are efect numai atunci când folosiţi funcţii GDI care primesc o variabilă handle a contextului de dispozitiv ca
parametru. GetSystemMetrics nu este o funcţie GDI, aşa că va returna în continuare dimensiunile în unităţi de dispozitiv, adică
în pixeli. Deşi GetDeviceCaps este o funcţie GDI care primeşte ca parametru o variabilă handle a contextului de dispozitiv,
Windows continuă să returneze unităţi de dispozitiv pentru identificatorii HORZRES şi VERTRES, deoarece unul dintre
scopurile acestei funcţii este să furnizeze programului dimensiunea în pixeli a dispozitivului.
Totuşi, valorile din structura TEXTMETRIC pe care le obţineţi prin apelarea funcţiei GetTextMetrics sunt furnizate în
unităţi logice. Dacă modul de mapare este MM_LOENGLISH în momentul apelării funcţiei, GetTextMetrics returnează
lăţimea şi înălţimea caracterelor, în sutimi de inci. Atunci când apelaţi funcţia GetTextMetrics ca să obţineţi înălţimea şi
lăţimea caracterelor, modul de mapare trebuie să fie acelaşi cu cel pe care îl veţi folosi atunci când afişaţi textul pe baza
acestor dimensiuni. Pe măsură ce voi prezenta diferite funcţii GDI în acest capitol, voi preciza dacă acestea utilizează
coordonate logice sau coordonate de dispozitiv. Toate funcţiile despre care am discutat până acum utilizează coordonate
logice, exceptând cele pentru stabilirea spaţiilor între puncte sau liniuţe pentru stilurile de linii şi între liniile de haşură din
modele. Acestea sunt independente de modul de mapare.
Sistemele de coordonate ale dispozitivului
Windows mapează coordonatele logice specificate în funcţiile GDI la coordonatele dispozitivului. Înainte de a discuta
despre sistemele de coordonate logice folosite de diferitele moduri de mapare, vom discuta despre diferitele sisteme de
coordonate de dispozitiv definite în Windows pentru zona de afişare. Deşi în general am lucrat doar în zona client a ferestrei,
în anumite situaţii Windows foloseşte alte două sisteme de coordonate de dispozitiv. În toate sistemele de coordonate de
dispozitiv sunt folosiţi pixelii ca unitate de măsură. Valorile de pe axa orizontală (x) cresc de la stânga la dreapta iar valorile
de pe axa verticală (y) cresc de sus în jos.
Atunci când folosim întregul ecran, spunem că lucrăm în „coordonate ecran". Colţul din stânga-sus este punctul de
coordonate (0, 0). Coordonatele ecran sunt folosite în mesajul WM_MOVE (pentru alte ferestre decât ferestrele descendent) şi
în următoarele funcţii Windows: CreateWindow şi MoveWindow (ambele pentru alte ferestre decât ferestrele descendent),
GetMessagePos, GetCursorPos, SetCursorPos, GetWindowRect, WindowFromPoint şi SetBrushOrgEx. Acestea sunt funcţii
care fie nu au o fereastră asociată (cum ar fi cele două funcţii pentru cursor), fie trebuie să mute (sau să găsească) o fereastră
pe baza unui punct de pe ecran. Dacă folosiţi funcţia CreateDC cu parametrul DISPLAY ca să obţineţi un context de
dispozitiv pentru întregul ecran, atunci coordonatele logice specificate la apelarea funcţiilor GDI vor fi mapate la coordonatele
ecranului.
„Coordonatele de fereastră" se referă la întreaga fereastră a ecranului, inclusiv bara de titlu, meniu, barele de derulare şi
chenarul ferestrei. Pentru o fereastră normală, punctul (0, 0) este colţul din stânga-sus al chenarului de redimensionare.
Coordonatele de fereastră sunt folosite mai rar în Windows, dar dacă obţineţi un context de dispozitiv cu ajutorul funcţiei
GetWindowDC, atunci coordonatele logice specificate la apelarea funcţiilor GDI vor fi mapate la coordonatele ferestrei.
Al treilea sistem de coordonate de dispozitiv - cu care vom lucra cel mai des -foloseşte „coordonatele zonei client".
Punctul (0,0) este colţul din stânga-sus al zonei client. Dacă obţineţi un context de dispozitiv cu ajutorul funcţiei GetDC sau al

$28
funcţiei BeginPaint, atunci coordonatele logice specificate Ia apelarea funcţiilor GDI vor fi mapate la coordonatele zonei
client.
Puteţi să transformaţi coordonatele zonei client în coordonatele ecranului şi invers folosind funcţiile ClientToScreen şi
ScreenToClient. De asemenea, puteţi şă obţineţi poziţia şi dimensiunea întregii ferestre în coordonate ecran folosind funcţia
GetWindowRect. Aceste funcţii vă furnizează suficiente informaţii ca să transferaţi coordonatele între oricare dintre sistemele
de coordonate de dispozitiv.
Vizorul şi fereastra
Modurile de mapare definite în Windows stabilesc modul în care sunt mapate coordonatele logice specificate în funcţiile
GDI la coordonatele de dispozitiv, pe baza faptului că sistemul de coordonate de dispozitiv folosit depinde de funcţia folosită
pentru obţinerea contextului de dispozitiv. Pentru a ne continua discuţia despre modurile de mapare, trebuie să mai facem o
precizare legată de terminologie: se spune că modul de mapare defineşte maparea coordonatelor „de fereastră" (window) -
coordonate logice - la coordonatele vizorului (viewport) - coordonate de dispozitiv.
Folosirea termenilor window (fereastră) şi viewport (vizor) nu este totuşi cea mai potrivită alegere. În alte limbaje pentru
interfeţele grafice, termenul viewport se referă la o regiune de decupare (clipping region). Termenul window defineşte, în
general, zona pe care o ocupă un program pe ecran. În timpul discuţiilor de faţă va trebui să renunţăm la vechile definiţii ale
acestor termeni.
Pentru vizor se folosesc coordonatele de dispozitiv (pixeli). De cele mai multe ori, vizorul coincide cu zona client a
ferestrei, dar poate să însemne şi coordonatele întregii ferestre sau coordonatele întregului ecran, dacă aţi obţinut contextul de
dispozitiv prin apelarea funcţiilor GetWindowDC sau CreateDC. Punctul de coordonate (0, 0) este colţul din stânga-sus al
zonei client (sau al ferestrei, ori al ecranului). Valorile coordonatei x cresc către dreapta, iar valorile coordonatei y cresc în jos.
Pentru fereastră se utilizează coordonatele logice, care pot fi,exprimate în pixeli, milimetri, inci sau orice altă unitate de
măsură doriţi. Coordonatele logice ale ferestrei sunt specificate la apelarea funcţiilor GDI.
Pentru toate modurile de mapare, Windows transformă coordonatele ferestrei (coordonate logice) în coordonate ale
vizorului (coordonate de dispozitiv) folosind două formule:

$
unde (xWindow, yWindow) este punctul în coordonate logice care trebuie translatat iar (xViewport, yViewport) este punctul în
coordonate de dispozitiv. În cazul în care coordonatele de dispozitiv sunt coordonate ale zonei client sau coordonate ale
ferestrei, Windows trebuie să le transforme în coordonate ecran înainte de a desena un obiect.
Formulele de mai sus folosesc două puncte corespunzătoare originii ferestrei şi a vizorului: (xWinOrg, yWinOrg)
reprezintă originea ferestrei în coordonate logice, iar (xViewOrg, yViewOrg) reprezintă originea, vizorului în coordonate de
dispozitiv. În contextul de dispozitiv prestabilit, aceste puncte au valoarea (0, 0), dar pot fi modificate. Formulele de mai sus
implică faptul că punctul (xWinOrg, yWinOrg) este întotdeauna mapat la punctul (xViewOrg, yViewOrg).
De asemenea, formulele de mai sus folosesc două puncte, pentru specificarea „extensiilor" ferestrei şi vizorului:
(xWinExt, yWinExt) reprezintă extensia, ferestrei în coordonate logice, iar (xViewExt, yViewExt) reprezintă extensia vizorului
în coordonate de dispozitiv. În majoritatea modurilor de mapare, extensiile sunt implicate de modul de mapare şi nu pot fi
modificate. Extensia în sine nu are nici o semnificaţie, dar raportul între extensia vizorului şi extensia ferestrei reprezintă un
factor de scalare folosit pentru convertirea unităţilor logice în unităţi de dispozitiv. Extensiile pot avea valori negative: aceasta
înseamnă că nu este obligatoriu ca valorile pe axa x să crească spre dreapta şi valorile pe axa y să crească în jos.
Windows poate să transforme şi coordonatele vizorului (coordonate de dispozitiv) în coordonate ale ferestrei (coordonate
logice):

$
Windows include două funcţii care vă permit să convertiţi punctele de dispozitiv în puncte logice şi invers. Funcţia
următoare converteşte punctele de dispozitiv în puncte logice:
$29
DPtoLP (hdc, pPoints, iNumber) ;

Variabila pPoints este o matrice de structuri POINT, iar iNumber este numărul de puncte ce urmează să fie convertite.
Veţi vedea că această funcţie este foarte utilă pentru convertirea dimensiunii zonei client obţinute prin apelarea funcţiei
GetClientRect (care returnează valorile în unităţi de dispozitiv) în coordonate logice:
GetClientRect (hwnd, &rect);
DPtoLP (hdc, (PPoint) &rect, 2);

Funcţia următoare converteşte punctele logice în puncte de dispozitiv:


LPtoDP (hdc, pPoints, iNumber) ;

Folosirea modului de mapare MM_TEXT


Pentru modul de mapare MM_TEXT, originile şi extensiile prestabilite sunt următoarele:
Originea ferestrei: (0, 0) Poate fi modificată
Originea vizorului: (0, 0) Poate fi modificată
Extensia ferestrei: (1, 1) Nu poate fi modificată
Extensia vizorului: (1, 1) Nu poate fi modificată
Raportul din extensia ferestrei şi extensia vizorului este 1, aşa că nu este necesară nici o operaţie de scalare pentru
transformarea coordonatelor logice în coordonate de dispozitiv. Formulele prezentate anterior se reduc la acestea:
xViewport = xWindow - xWinOrg + xViewOrg
yViexport = yWindow - yWinOrg + yViewOrg
Acest mod de mapare se numeşte „mapare de tip text", nu fiindcă este cea mai potrivită pentru text, ci datorită orientării
axelor. În general, citim textul de la stânga spre dreapta şi de sus în jos, iar în modul de mapare MM_TEXT, valorile cresc în
acelaşi sens:

$
Windows furnizează funcţiile SetViewportOrgEx şi SetWindowOrgEx pentru modificarea originii vizorului şi a ferestrei.
Aceste funcţii au ca efect deplasarea axelor, astfel încât punctul de coordonate (0, 0) nu se mai referă la colţul din stânga-sus
al ecranului. În general, veţi folosi ori funcţia SetViewportOrgEx, ori SetWindowOrgEx, dar nu pe amândouă.
Iată cum lucrează aceste funcţii: dacă schimbaţi originea vizorului la (xViewOrg, yViewOrg), atunci punctul logic de
coordonate (0, 0) va fi mapat la punctul de dispozitiv (xViewOrg, yViewOrg). Dacă schimbaţi originea ferestrei la (xWinOrg,
yWinOrg), atunci punctul logic (xWinOrg, yWinOrg) va fi mapat la punctul de dispozitiv (0, 0). Indiferent de modificările pe
care le faceţi asupra originii ferestrei şi a vizorului, punctul de dispozitiv (0, 0) este întotdeauna colţul din stânga-sus al zonei
client.
De exemplu, să presupunem că zona client are lăţimea cxClient şi înălţimea cyClient. Dacă vreţi ca punctul logic de
coordonate (0, 0) să se afle în centrul zonei client, puteţi să apelaţi funcţia următoare:
SetViewportOrgEx (hdc, cxClient/2, cyClient/2, NULL) ;

Argumentele funcţiei SetViewportEx sunt exprimate întotdeauna în unităţi de dispozitiv. Punctul logic (0, 0) va fi acum
mapat la punctul de dispozitiv (cxClient/2, cyClient/2). Din acest moment folosiţi zona client ca şi cum ar avea următorul
sistem de coordonate:
Valorile logice ale axei x sunt cuprinse în intervalul de la -cxClient/2 la +cxClient/2 iar valorile logice ale axei y sunt
cuprinse în intervalul de la -cyClient/2 la +cyClient/2. Colţul din dreapta-jos al zonei client are coordonatele (cxClient/2,
cyClient/2). Dacă vreţi să afişaţi text începând din colţul din stânga-sus al zonei client, care are coordonatele de dispozitiv (0,
0), trebuie să folosiţi coordonate logice negative:
TextOut (hdc, -cxClient/2, -cyClient/2, "Hello", 5) ;
$30
Acelaşi rezultat poate fi obţinut cu ajutorul funcţiei SetWindowOrgEx în locul funcţiei SetViewportOrgEx:
SetWindowOrgEx (hdc, -cxClient/2, -cyClient/2, NULL) ;

Argumentele funcţiei SetWindowOrgEx sunt exprimate întotdeauna în coordonate logice. După apelul de mai sus, punctul
logic (-cxClient/2, -cyClient/2) este mapat la punctul de dispozitiv (0, 0), care este colţul din stânga-sus al zonei client.

$
Ceea ce probabil nu doriţi să faceţi (decât dacă ştiţi ce rezultat veţi obţine) este să folosiţi cele două funcţii împreună:
SetViewportOrgEx (hdc, cxClient/2, cyClient/2, NULL) ;
SetWindowOrgEx (hdc, -cxClient/2, -cyClient/2, NULL) ;

Aceasta înseamnă că punctul logic (-cxClient/2, -cyClient/2) este mapat la punctul de dispozitiv (cxClient/2, cyClient/2),
rezultând următorul sistem de coordonate:

$
Puteţi să obţineţi originea vizorului apelând funcţia GetViewportOrgEx:
GetViewportOrgEx (hdc, &pt) ;

şi originea ferestrei apelând funcţia GetWindowOrgEx:


GetWindowOrgEx (hdc, &pt) ;

unde pt este o structură de tip POINT. Valorile returnate de funcţia GetViewportOrgEx sunt în coordonate de dispozitiv, iar
valorile returnate de funcţia GetWindowOrgEx sunt în coordonate logice.
Uneori este de dorit să modificaţi originea ferestrei sau a vizorului ca să deplasaţi imaginea afişată pe ecran - de exemplu,
ca răspuns la acţionarea barei de derulare de către utilizator. Modificarea originii nu determină deplasarea imediată a imaginii
afişate. Mai întâi modificaţi originea, apoi redesenaţi ecranul. De exemplu, în programul SYSMETS2 din Capitolul 2 am
folosit valoarea iVscrollPos (poziţia curentă a casetei de derulare de pe bara de derulare verticală) ca să ajustăm coordonatele
de afişare pe axa y:
case WM_PAINT :
BeginPaint (hwnd, &ps) ;

for 0 = 0; i<NUMLINES ; i++)


{
y = cyChar * (1 - iVscrollPos + 1) ;
[afişează textul]
}
EndPaint (hwnd, &ps) ;

return 0 ;

$31
Putem să obţinem acelaşi rezultat cu ajutorul funcţiei SetWindowOrgEx:
case WM_PAINT :
BeginPaint (hwnd, &ps);
SetWindowOrgEx (ps.hdc, 0, cyChar * iVscrollPos) ;
for (i = 0; i < NUMLINES ; i++)
{
y = cyChar * (1 + i) ;
[afişează textul]
}
EndPaint (hwnd, &ps) ;
return 0 ;

Acum nu mai este nevoie să fie folosită valoarea iVscrollPos pentru calcularea coordonatei y pentru funcţia TextOut.
Aceasta înseamnă că puteţi să apelaţi funcţiile de afişare a textului dintr-o subrutină, fără să transmiteţi subrutinei valoarea
iVscrollPos, deoarece ajustarea poziţiei de afişare a textului s-a făcut prin modificarea originii ferestrei.
Dacă aţi mai lucrat cu sistemele de coordonate rectangulare (carteziene), probabil deplasarea punctului logic de
coordonate (0, 0) în centrul zonei client vi se pare o operaţie logică. Totuşi, modul de mapare MM_TEXT prezintă o mică
problemă: de obicei, într-un sistem de coordonate cartezian, valorile de pe axa y cresc în sus, pe când în modul de mapare
MM_TEXT acestea cresc în jos. In acest sens, modul de mapare MM_TEXT este un caz izolat, această problemă fiind
corectată de celelalte cinci moduri de mapare.
Modurile de mapare „metrice"
Windows include două moduri de mapare în care coordonatele logice sunt exprimate în unităţi fizice. Deoarece
coordonatele logice pe axele x şi y sunt mapate la unităţi fizice identice, aceste moduri de mapare vă ajută să desenaţi cercuri
„mai rotunde" şi pătrate „mai pătrate".
Cele cinci moduri de mapare „metrice" sunt prezentate mai jos în ordinea crescătoare a preciziei. Cele două coloane din
partea dreaptă prezintă dimensiunile unităţilor logice în inci (in.) şi în milimetri (mm) pentru comparare:

Mod de mapare Unităţi logice Inci Milimetri


MM_LOENGLISH 0,01 in. 0,001 0,254
MM_LOMETRIC 0,1 mm 0,00394 0,1
MM_HIENGLISH 0,001 in. 0,001 0,0254
MM_TWIPS* 1/1440 in. 0,000694 0,0176
MM_HIMETRIC 0,01 mm 0,000394 0,01
* Un twip este 1/20 dintr-un punct (care este 1/72 dintr-un inci), deci 1 /1440 dintr-un inci.
Ca să puteţi face o comparaţie între modul de mapare MM_TEXT şi aceste rezoluţii, trebuie să vă amintesc faptul că, pe
un monitor VGA standard, fiecare pixel este un pătrat cu latura de 0,325 mm, ceea ce înseamnă că coordonatele unui
dispozitiv VGA sunt mult mai grosiere decât oricare dintre modurile de mapare metrice.
Pentru o imprimantă laser cu rezoluţia de 300 dpi (puncte pe inci) fiecare pixel are 0,0033 inci - o rezoluţie mai mare
decât cea a modurilor de mapare MM_LOENGLISH si MM_LOMETRIC, dar mai mică decât cea a modurilor de mapare
MM_HIENGLISH, MM_TWIPS şi MM_HIMETRIC.
Originile şi extensiile prestabilite sunt următoarele:
Originea ferestrei: (0,0) Poate fi modificată
Originea vizorului: (0,0) Poate fi modificată
Extensia ferestrei: (?, ?) Nu poate fi modificată
Extensia vizorului: (?, ?) Nu poate fi modificjată
Extensiile ferestrei şi ale vizorului depind de modul de mapare şi de raportul de afişare a dispozitivului. Aşa cum am
menţionat anterior, extensiile nu sunt impor tante ca atare, având o semnificaţie numai exprimate ca rapoarte. Iată care sunt
formulele de transformare a coordonatelor:

$
De exemplu, pentru modul de mapare MM_LOENGLISH, Windows foloseşte extensiile pentru următoarele calcule:
$32
$
Pentru multe dispozitive de afişare (cum ar fi VGA) acest raport va fi mai mic decât 1. Deoarece Windows lucrează
numai cu numere întregi, folosirea unui raport în locul unui factor de scalare este absolut necesară pentru o mai mare precizie
în transformarea coordonatelor logice în coordonate de dispozitiv, şi invers.
Remarcaţi semnul minus din faţa raportului extensiilor pe axa verticală. Acest semn modifică orientarea axei y. Pentru
cele cinci moduri de mapare, valorile y cresc pe măsură ce vă deplasaţi în sus pe dispozitiv. Originile prestabilite ale ferestrei
şi ale vizorului sunt (0,0). Consecinţele sunt interesante: atunci când treceţi la unul dintre aceste cinci moduri de mapare,
sistemul de coordonate arată astfel:

$
Singurul mod în care puteţi să afişaţi ceva pe ecran este să folosiţi valori negative pentru coordonata y. De exemplu,
codul următor:
SetMapMode (hdc, MM_LOENGLISH) ;
TextOut (hdc, 100, -100, "Hello", 5 ) ;

afişează textul „Hello" la o distanţă de un inci de marginea din stânga şi de marginea de sus a zonei client.
Ca să vă păstraţi judecata limpede, probabil veţi dori să schimbaţi această situaţie. O soluţie este să mutaţi punctul de
coordonate (0,0) în colţul din stânga-jos al zonei client. Presupunând că cyClient este înălţimea zonei client în pixeli, puteţi să
faceţi acest lucru apelând funcţia SetViewportOrgEx astfel:
SetViewportOrgEx (hdc, 0, cyClient, NULL) ;
Acum sistemul de coordonate arată astfel:

$
O altă soluţie este să mutaţi punctul de coordonate (0,0) în centrul zonei client:
SetViewportOrgEx (hdc, cxClient/2, cyClient/2, NULL) ;

Acum sistemul de coordonate arată astfel:

$
Acum avem un sistem de coordonate cartezian, cu patru cadrane, cu aceleaşi unităţi de măsură pe axele x şi y, în inci,
milimetri sau twips.
$33
Pentru mutarea punctului logic (0,0) puteţi să folosiţi şi funcţia SetWindowOrgEx, dar această cale este puţin mai dificilă,
deoarece parametrii funcţiei SetWindowOrgEx trebuie să fie în coordonate logice. Mai întâi trebuie să transformaţi
coordonatele de dispozitiv (cxClient, cyClient) în coordonate logice, folosind funcţia DPtoLP. Presupunând că pt este o
structură de tip POINT, codul de mai jos mută punctul logic (0,0) în centrul zonei client:
pt.x = cxClient ;
pt.y = cyClient ;
DPtoLP (hdc, &pt, 1) ;
SetWindowOrgEx (hdc, -pt.x/2, -pt.y/2, NULL) ;

Moduri de mapare proprii


Ultimele două moduri de mapare se numesc MM_ISOTROPIC şi MM_ANISOTROPIC. Acestea sunt singurele moduri
de mapare care vă permit să modificaţi extensiile ferestrei şi ale vizorului, ceea ce înseamnă că vă permit să modificaţi
factorul de scalare pe care Windows îl foloseşte pentru convertirea coordonatelor logice şi a coordonatelor de dispozitiv.
Cuvântul „izotrop" înseamnă „egal în toate direcţiile"; „anizotrop" este antonimul acestuia. Ca şi modurile de mapare metrice
prezentate mai înainte, MM_ISOTROPIC foloseşte axe scalate egal. Unităţile logice de pe axa x au aceeaşi dimensiune fizică
ca şi unităţile logice de pe axa y. Acest fapt vă ajută să creaţi imagini care păstrează raportul corect de afişare, indiferent de
raportul de afişare al dispozitivului.
Diferenţa dintre modurile de mapare metrice şi modul de mapare MM_ISOTROPIC constă în faptul că puteţi să
controlaţi dimensiunea fizică a unităţilor logice. Dacă doriţi, puteţi să ajustaţi dimensiunea fizică a unităţilor logice în funcţie
de dimensiunea zonei client, astfel încât imaginile pe care le desenaţi să fie întotdeauna conţinute de zona client, mărindu-se şi
micşorându-se corespunzător. Programul ANACLOCK (ceasul analogic) din Capitolul 7 este un exemplu de imagine izotropă.
Ceasul este întotdeauna rotund. Dacă redimensionaţi fereastra, ceasul se redimen-sionează automat. Un program Windows
poate să manipuleze redimensionarea unei imagini prin ajustarea extensiilor ferestrei şi ale vizorului. Apoi programul poate să
folosească aceleaşi unităţi logice la apelarea funcţiilor de desenare, indiferent de dimensiunea ferestrei.
Uneori, modurile de mapare metrice şi modul MM_TEXT sunt numite moduri de mapare „complet restricţionate" („fully
constrained"). Aceasta înseamnă că nu puteţi să modificaţi extensiile ferestrei şi ale vizorului sau modul în care Windows
scalează coordonatele logice faţă de coordonatele de dispozitiv. MM_ISOTROPIC este un mod de mapare „partial
restricţionat" („partly constrained"). Windows vă permite să modificaţi extensiile ferestrei şi ale vizorului, dar le ajustează
astfel încât unităţile logice x şi y să aibă aceleaşi dimensiuni fizice. Modul de mapare MM_ANISOTROPIC este
„nerestricţionat". Puteţi să modificaţi extensiile ferestrei şi ale vizorului, fără ca Windows să mai facă vreo ajustare a valorilor.
Modul de mapare MM_ISOTROPIC
Modul de mapare MM_ISOTROPIC este ideal pentru folosirea unor axe arbitrare, cu unităţi logice egale pe cele două
axe. Dreptunghiurile cu lăţimi şi înălţimi logice egale sunt afişate ca pătrate. Elipsele cu lăţimi şi înălţimi logice egale sunt
afişate ca cercuri.
Atunci când selectaţi pentru prima dată modul de mapare MM_ISOTROPIC, Windows foloseşte aceleaşi extensii pentru
fereastră şi pentru vizor, ca şi pentru modul de mapare MM_LOMETRIC. (Totuşi, nu vă bazaţi pe acest fapt.) Diferenţa este
că acum puteţi să schimbaţi extensiile după cum dopţi, apelând funcţiile SetWindowExtEx şi SetViewportExtEx. Windows va
ajusta apoi aceste extensii astfel încât unităţile logice de pe ambele axe să reprezinte distanţe fizice egale.
În general, veţi folosi ca parametri ai funcţiei SetWindowExtEx dimensiunile dorite pentru fereastra logică, iar ca
parametri ai funcţiei SetViewportExtEx dimensiunile reale ale zonei client. Atunci când ajustează aceste extensii, Windows
trebuie să încadreze fereastra logică în vizorul fizic, ceea ce înseamnă că o parte a zonei client poate să rămână în afara
ferestrei logice. Pentru folosirea mai eficientă a spaţiului din zona client trebuie să apelaţi funcţia SetWindowExtEx înainte de
apelarea funcţiei SetViewportExtEx.
De exemplu, să presupunem că vreţi să folosiţi un sistem de coordonate virtual, „tradiţional", cu un singur cadran în care
punctul (0, 0) este colţul din stânga-jos al zonei client, iar lăţimea şi înălţimea au valori de la 0 la 32.767. Dacă doriţi ca
unităţile logice x şi y să aibă aceleaşi dimensiuni, iată ce trebuie să faceţi:
SetMapMode (hdc, HH_ISOTROPIC) ;
SetWindowExtEx (hdc, 32767, 32767, NULL) ;
SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ;
SetViewportOrgEx (hdc, 0, cyClient, NULL) ;

Dacă apoi obţineţi extensiile ferestrei şi ale vizorului apelând funcţiile GetWindowExtEx şi GetViewportExtEx veţi vedea
că acestea au alte valori decât cele specificate. Windows ajustează extensiile în funcţie de raportul de afişare (aspect ratio) al
dispozitivului, astfel încât unităţile logice de pe cele două axe să reprezinte aceleaşi dimensiuni fizice.
Dacă zona client este mai mult lată decât înaltă (în dimensiuni fizice), Windows ajustează extensia x astfel încât fereastra
logică să fie mai mică decât vizorul zonei client. Fereastra logică va fi poziţionată în partea stângă a zonei client:
$34
$
Nu puteţi să afişaţi nimic în partea dreaptă a zonei client dincolo de capătul axei x, deoarece pentru aceasta ar trebui să
folosiţi o coordonată logică x mai mare de 32.767. Dacă zona client este mai mult înaltă decât lată (în dimensiuni fizice),
Windows ajustează extensia y. Fereastra logică va fi poziţionată în partea de jos a zonei client:

$
Nu puteţi să afişaţi nimic în partea de sus a zonei client dincolo de capătul axei y, deoarece pentru aceasta ar trebui să
folosiţi o coordonată logică y mai mare de 32.767. Dacă preferaţi ca fereastra logică să fie poziţionată întotdeauna în partea
stângă şi în partea de sus a zonei client, puteţi să modificaţi astfel codul precedent:
SetMapMode (hdc, HH_ISOTROPIC) ;
SetWindowExtEx (hdc, 32767, 32767, NULL) ;
SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ;
SetWindowOrgEx (hdc, 0, 32767, NULL) ;

În acest fel, prin apelul funcţiei SetWihdowOrgEx precizăm că dorim ca punctul logic (0,32767) să fie mapat la punctul
de dispozitiv (0,0). Dacă zona client este mai mult înaltă decât lată, sistemul de coordonate este poziţionat astfel:

$
Pentru imaginile de genul celei folosite de programul ANACLOCK puteţi să folosiţi un sistem de coordonate cartezian cu
patru cadrane având axe arbitrar scalate în cele patru direcţii şi cu punctul de coordonate (0, 0) în centrul zonei client. Dacă,
de exemplu, vreţi ca fiecare axă să ia valori de la 0 la 1000, folosiţi codul următor:
SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExtEx (hdc, 1000, 1000, NULL);
SetViewportExtEx (hdc, cxClient/2, -cyClient/2, NULL) ;
SetViewportOrgEx (hdc, cxClient/2, cyCllent/2, NULL) ;

Dacă zona client este mai mult lată decât înaltă, sistemul logic de coordonate arată astfel:

$35
$
Sistemul logic de coordonate este centrat şi atunci când zona client este mai mult înaltă decât lată:

$
Reţineţi faptul că extensiile ferestrei şi ale vizorului nu implică nici o operaţie de decupare. La apelarea funcţiilor GDI
puteţi să folosiţi pentru coordonatele x şi y valori mai mici de -1000 sau mai mari de +1000. În funcţie de forma zonei client,
aceste puncte pot să fie sau să nu fie vizibile.
Folosind modul de mapare MM_ISOTROPIC puteţi să faceţi ca unităţile logice să fie mai mari decât pixelii. De exemplu,
să presupunem că doriţi să creaţi un sistem de coordonate în care punctul de coordonate (0, 0) se află în colţul din stânga-sus
al ecranului şi valorile de pe axa y cresc de sus în jos (ca în modul de mapare MM_TEXT) dar cu coordonate logice măsurate
în unităţi de 1/16 dintr-un inci. Acest mod de mapare v-ar permite să desenaţi o riglă având un capăt în colţul din stânga-sus al
zonei client, cu diviziuni de şaisprezecimi de inci:
SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExtEx(hdc, 160*GetDeviceCaps (hdc, HORZSIZE)/254,160*GetDeviceCaps(hdc, VERTSIZE)/254, NULL);
SetViewportExtEx(hdc, GetDeviceCaps(hdc, HORZRES),GetDeviceCaps(hdc, VERTRES), NULL);

În această secvenţă de cod extensiile vizorului sunt stabilite la dimensiunile în pixeli ale întregului ecran. Extensiile
ferestrei trebuie să fie stabilite la dimensiunile întregului ecran în unităţi de şaisprezecimi de inci. Indicii HORZSIZE şi
VERTSIZE ai funcţiei GetDeviceCaps returnează dimensiunile dispozitivului în milimetri. Dacă lucraţi cu numere în virgulă
mobilă, trebuie să transformaţi milimetrii în inci împărţind valorile obţinute la 2,54, şi apoi să transformaţi incii în
şaisprezecimi de inci înmulţind rezultatul operaţiei anterioare cu 16. Deoarece aici lucrăm cu numere întregi, am înmulţit
rezultatul cu 160 şi l-am împărţit la 254.
Pentru majoritatea dispozitivelor, acest cod face ca unităţile logice să fie mult mai mari decât unităţile fizice. Tot ce
desenaţi pe dispozitiv va avea pentru coordonate valori mapate la multipli de 1/16 inci. Nu puteţi să desenaţi două linii
orizontale aflate la distanţa de 1/32 de inci, deoarece pentru aceasta aţi avea nevoie de o coordonată logică fracţionară.
Modul de mapare MM_ANISOTROPIC sau ajustarea imaginii
Atunci când stabiliţi extensiile ferestrei şi pe ale vizorului în modul de mapare MM_ISOTROPIC, Windows ajustează
valorile astfel încât unităţile logice de pe cele două axe să aibă aceleaşi dimensiuni. În modul de mapare
MM_ANISOTROPIC, Windows nu face nici o ajustare a valorilor pe care le stabiliţi. Aceasta înseamnă că în modul de
mapare MM_ANISOTROPIC nu este obligatorie păstrarea raportului corect de afişare.
Pentru a folosi modul de mapare MM_ANISOTROPIC, stabiliţi un sistem arbitrar de coordonate pentru zona client, ca şi
pentru modul de mapare MM_ISOTROPIC. Codul de mai jos stabileşte punctul de coordonate (0, 0) în colţul din stânga-jos al
zonei client, iar axele de coordonate x şi y pot lua valori de la 0 la 32.767:
SetMapMode (hdc, MM_ANISOTROPIC) ;
SetWindowExtEx (hdc, 32767, 32767, NULL) ;
SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ;
SetViewportOrgEx (hdc, 0, cyClient, NULL) ;

$36

S-ar putea să vă placă și