Documente Academic
Documente Profesional
Documente Cultură
Î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.
Din acest punct de vedere, interfaţa Windows GDI este pentru limbajele grafice de interfaţa
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ă 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ţă 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.
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.
Î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 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ă:
· 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).
· 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.
· 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.
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:
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:
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.
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:
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:
DeleteDC (hdclnfo);
Atunci când lucraţi cu imagini bitmap, poate fi uneori utilă obţinerea unui „context de dispozitiv în
memorie":
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);
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).
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
contextului de dispozitiv pentru o imprimantă, funcţia GetDeviceCaps returnează înălţimea şi lăţimea
zonei pe care imprimantă o poate tipări.
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.
#include <windows.h>
#include <string.h>
struct
int iIndex ;
char *szLabel ;
char *szDesc ;
devcaps [] =
};
HWND hwnd ;
MSG msg ;
WNDCLASSEX wndclass ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.lpszClassName = szAppName ;
RegisterClassEx (&wndclass) ;
UpdateWindow (hwnd) ;
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
return msg.wParam ;
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
char szBuffer[10] ;
HDC hdc ;
int i ;
PAINTSTRUCT ps ;
TEXTMETRIC tm ;
switch (iMsg)
case WM_CREATE:
cxChar = tm.tmAveCharWidth ;
return 0 ;
case WM_PAINT:
devcaps[i].szLabel,
strlen (devcaps[i].szLabel)) ;
devcaps[i].szDesc,
strlen (devcaps[i].szDesc)) ;
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
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.
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 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.
Apelul următor returnează numărul de biţi de culoare folosiţi pentru fiecare pixel:
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:
Această valoare poate să nu fie identică cu numărul de culori obţinut prin apelarea
funcţiei GetDeviceCaps cu parametrul 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 2 24 culori (aproximativ 16 milioane de
culori).
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:
((WORD)(g)<<8))|\
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.
Puteţi să determinaţi cea mai apropiată culoare pură pentru o anumită valoare apelând
funcţia GetNearestColor:
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.
mapare
Originea (0,0) SetWindowOrgEx GetWindowOrgEx
ferestrei OffsetWindowOrgEx
Originea (0,0) SetViewportOrgEx GetViewportOrgEx
vizorului OffsetViewportOrgEx
Extensia (1,1) SetWindowExtEx GetWindowExtEx
ferestrei
SetMapMode
ScaleWindowExtEx
Extensia (1,1) SetViewportExtEx GetViewportExExt
vizorului 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 OPAQUE SetBkMode GetBkMode
afişare a
fondului
Culoarea Alb SetBkColor GetBkColor
fondului
Culoarea Negru SetTextColor GetTextColor
textului
Mod de R2COPYPEN SetROPZ GetROP2
desenare
Mod de BLACKONWHIT SetStretchBltMode GetStretchBltMode
E
întindere
Mod de umplere ALTERNATE SetPolyFillMode GetPolyFillMode
a prigoanelor
Spaţiu între 0 SetTextCharacterExtra GetTextCharacterExtr
a
caractere
Originea (0,0) în coordonate SetBrushOrgEx GetBrushOrgEx
pensulei ecran
Regiune de Nu există SelectObject GetClipBox
decupare SelectClipRgn
Î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 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:
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:
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:
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ă.
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:
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:
Desenarea liniilor
Î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.
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.
unde pt este o structură de tip POINT, definită în fişierele antet din Windows astfel:
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:
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:
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.
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:
}
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:
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:
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:
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.
/*-----------------------------------------
-----------------------------------------*/
#include <windows.h>
#include <math.h>
#define NUM 1000
{
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
WS_OVERLAPPEDWINDOW,
{
}
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
{
int i ;
PAINTSTRUCT ps ;
{
return 0 ;
{
}
return 0 ;
return 0 ;
}
}
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.
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.
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ă:
Î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:
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:
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.
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ă.
/*--------------------------------------------------
--------------------------------------------------*/
#include <windows.h>
{
wndclass.cbWndExtra = 0 ;
WS_OVERLAPPEDWINDOW,
{
}
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
{
static int cxClient, cyClient ;
PAINTSTRUCT ps ;
{
return 0 ;
return 0 ;
return 0 ;
}
}
Figura 4-12. Programul LINEDEMO.
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.)
Î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.)
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.
/*---------------------------------------
---------------------------------------*/
#include <windows.h>
{
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
WS_OVERLAPPEDWINDOW,
{
}
}
{
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
{
PAINTSTRUCT ps ;
{
return 0 ;
{
hdc = GetDC (hwnd) ;
{
}
{
}
}
return 0 ;
return 0 ;
return 0 ;
}
}
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.
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.
x(t) = (1-t)3x0 + 3t(1-t)2x1 + 3t2(1-t)x2 + t3x3
Î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:
sau instrucţiunea:
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:
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:
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.
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:
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.
Ø 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.
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.
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:
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:
Î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:
Î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:
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:
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;
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:
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:
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.
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.
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:
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 elipsă.
Polygon 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 ;
hBrush = GetStoCkObject (GRAY_BRUSH) ;
Dacă vreţi să desenaţi o figură fără contur, selectaţi în contextul de dispozitiv peniţa 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:
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
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:
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:
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.
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.
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:
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) ;
DeleteObject (hBrush) ;
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:
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.
Windows defineşte opt moduri de mapare. Acestea sunt prezentate în tabelul următor, folosind
identificatorii definiţi în fişierele antet din Windows:
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:
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:
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:
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.
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.
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 funcţiei BeginPaint, atunci coordonatele logice
specificate Ia apelarea funcţiilor GDI vor fi mapate la coordonatele zonei client.
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.
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.
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 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:
GetClientRect (hwnd, &rect);
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
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.
TextOut (hdc, -cxClient/2, -cyClient/2, "Hello", 5) ;
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ă:
SetWindowOrgEx (hdc, -cxClient/2, -cyClient/2, NULL) ;
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 :
for 0 = 0; i<NUMLINES ; i++)
y = cyChar * (1 - iVscrollPos + 1) ;
[afişează textul]
return 0 ;
case WM_PAINT :
y = cyChar * (1 + i) ;
[afişează textul]
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.
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:
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.
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.
SetMapMode (hdc, MM_LOENGLISH) ;
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:
O altă soluţie este să mutaţi punctul de coordonate (0,0) în centrul zonei client:
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 ;
Moduri de mapare proprii
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.
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.
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:
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) ;
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) ;
Dacă zona client este mai mult lată decât înaltă, sistemul logic de coordonate arată astfel:
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:
Î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.