Grafik med GDI

Tags:    c++
<< < 123 > >>
Skrevet af Bruger #8985 @ 12.05.2009
Artikel filer

Placér GDI objekter i din DC


Et kald til Rectangle-funktionen alene tegner blot en firkant; der er hverken support for kantens farve, kantens tykkelse, kantens type eller firkantens baggrundsfarve. Men det ville også være et helvede at specificere den slags oplysninger ved hvert kald til en GDI-funktion. I stedet gør man det, at man opretter et objekt, der styrer dele af det grafiske element (f. eks. en firkants kant), som man derefter putter ind i sin device context. På den måde ved GDI, hvordan resultatet skal se ud. Den forrige firkant var kedelig og monoton. En 1px bred sort kant på en hvid baggrund. Nu vil vi have en 3px bred rød kant på en grøn baggrund.
Fold kodeboks ind/udKode 


Det hele lysner straks op:



HPEN er en fjerde handle-type. En tommelfingerregel er, at en form tegnet med et kald til en GDI-funktion (f. eks. en firkant) består af to ting: En kant og en baggrund. Kanten styres af en HPEN, og baggrunden styres af en HBRUSH. Det bekræftes af WNDCLASS-typens hbrBackground medlem, der jo er af typen HBRUSH. Vi kan oprette en HPEN ved at kalde CreatePen-funktionen, der ser sådan ud:

HPEN CreatePen(
__in int fnPenStyle,
__in int nWidth,
__in COLORREF crColor
);


fnPenStyle er pennens stil, så at sige. Før brugte vi PS_SOLID, som er standardtypen. PS_SOLID er en solid pen, det vil sige, der er ingen mønstre eller huller, og den består af blot én farve. Du kan se en liste over pentyper her: http://msdn.microsoft.com/en-us/library/dd183509(VS.85).aspx . PS står for Pen Style.
Ønsker du at tegne en firkant uden kant, kan du med fordel bruge PS_NULL i fnPenStyle, eller du kan tage genvejen og kalde GetStockObject med NULL_PEN som argument.

nWidth er pennens bredde. Den måles i pixels. En firkant uden en pen specificeret har en kant på 1px.
crColor er af typen COLORREF, som ikke er en selvstændig type. COLORREF ser således ud:

typedef unsigned long DWORD;
typedef DWORD COLORREF;


Så COLORREF er synonym for unsigned long. Du kan benytte RGB-makroen til at oprette en farve af typen COLORREF. Den tager 3 argumenter: Rød, grøn og blå. Med 0 som det laveste og 255 som det højeste, kan vi lave en rød farve således:

COLORREF crKantFarve = RGB(255, 0, 0);

RGB
-makroen er nogenlunde defineret således:

#define RGB(r, g, b) (r | (g << 8) | (b << 16))

Hvis vi vil hente de enkelte farvekomponenter ud af en COLORREF, kan vi få henholdsvis rød, grøn og blå således:

R = GetRValue(rgb_value);
G = GetGValue(rgb_value);
B = GetBValue(rgb_value);


Disse 3 makroer er defineret således:

#define GetRValue(rgb) (rgb & 0xFF)
#define GetGValue(rgb) ((rgb >> 8) & 0xFF)
#define GetBValue(rgb) ((rgb >> 16) & 0xFF)


Denne gang bruger jeg et kald til Rectangle til et "viske" skærmen ren, inden jeg tegner firkanten med den røde kant. Det er, så du kan se, at der ikke er meget ved Rectangle, før vi har puttet en HPEN og en HBRUSH ind i vores DC, hvilket vi gør med et kald til SelectObject. Den tager to argumenter: En device context og et GDI-objekt. Nu er det en fordel, at alle GDI-typerne er ens, for så slipper vi for at caste hver gang, vi vil putte et objekt ind DC’en. Denne linje ...

SelectObject(dc, kant);

... putter variablen 'kant' – en HPEN, altså et GDI-objekt, der styrer en forms kant – ind i vores DC. På næste linje gør vi det samme med en HBRUSH, så vores firkant også får en ny baggrundsfarve. Nu hvor vi har specificeret kant og baggrund, er vi klar til at tegne firkanten med Rectangle-funktionen. Med lidt matematik sørger vi for, at firkanten altid står i midten af vinduet, og at den fylder halvdelen af bredden og halvdelen af højden.

DeleteObject-funktionen har jeg forklaret. Vi sletter vores HPEN og HBRUSH, da vi har ikke brug for dem yderligere i denne WM_SIZE.
ReleaseDC sletter ikke et GDI-objekt; den sletter en DC. Navnet er lidt misvisende (specielt fordi der også er en funktion, der hedder DeleteDC – mere om det snart), men det er altså det, den gør.

WM_PAINT


Indtil videre har jeg bedt GDI om at arbejde, når programmet ændrer størrelse, men der findes faktik en WM_-besked, der ligefrem er beregnet til at tegne i. Den hedder WM_PAINT, og den bliver sendt på to tidspunker: Når Windows "tegner" dit program, og når programmøren vælger det. Som sædvanlig er der fordele og ulemper ved det hele. Hvis vi tegner i WM_PAINT, kan vi ikke længere bruge GetDC eller ReleaseDC. Tilgengæld kan vi nu tegne i hukommelsen, hvilket er en kæmpe fordel, for så kan vi forberede vores billede, inden vi smider det op på skærmen. Det hedder "double buffering", og det kan for eksempel forhindre billedet i at "blinke" når det opdateres.

Herunder er der to eksempler, der begge tegner det samme på skærmen. De bruger begge en 'timer', men det første eksempel tegner direkte i WM_TIMER-beskeden, hvorimod det sidste eksempel tegner i WM_PAINT. Jeg synes, du skal prøve at kompilere begge koder, så du kan fornemme forskellen. Jeg kan jo ikke rigtig illustrere den med et billede.

WM_TIMER
Fold kodeboks ind/udKode 


WM_PAINT
Fold kodeboks ind/udKode 


Hvis man i WM_TIMER-eksemplet undlader at slette sine GDI-objekter med DeleteObject, undgår man, at skærmen blinker, men går der lang nok tid, vil animationen gå i stå og grafikken høre op, så WM_PAINT er klart den mest hensigtmæssige metode.

Som sagt kan man ikke bruge GetDC/ReleaseDC i WM_PAINT. I stedet bruger vi BeginPaint/EndPaint. Ved at kalde BeginPaint på en PAINTSTRUCT, får vi ikke kun en DC (PAINTSTRUCT::hdc), vi får også nogle informationer om vores vindue. Disse gemmes i strukturens medlemmer. Du kan læse mere om PAINTSTRUCT her: http://msdn.microsoft.com/en-us/library/dd162768(VS.85).aspx .

BeginPaint returnerer en HDC identisk med PAINTSTRUCT::hdc. Altså kunne vi have skrevet:

HDC dc = BeginPaint(hwnd, &ps);

... Men vi kan ligeså godt bruge HDC-medlemmet. Det næste nye element er et par linjer længere nede. 'mem' er en DC ligesom ps.hdc (og variablen 'dc' fra forrige eksempler), men der er det særlige ved den, at dens 'device' ikke er skærmen; det er hukommelsen. Et kald til CreateCompatibleDC-funktionen returnerer en hukommelses-DC, der er kompatibel med den DC, vi bruger som argument. Man kan godt oprette en hukommelses-DC, der er kompatibel med en anden hukommelses-DC, men så står vi lige pludselig med flere buffer'e ("triple buffering"), end vi har brug for, og det er der ingen grund til. Jeg siger det lige igen, i fald du skulle have glemt det: Vi opretter en hukommelses-DC, så vi kan tegne billedet, inden vi putter det på skærmen. I WM_TIMER eksemplet tegner computeren samtidig med at den viser. Det kan forklares i trin således:

  • Computeren farver vinduets baggrund helt hvid, så forrige røde firkant bliver tegnet over

  • Computeren tegner den røde firkant



Selvom en computer er skidehurtig, kan man godt nå at se første trin inden andet trin – den røde firkant – bliver tegnet. Med WM_PAINT eksemplet foregår det således:

  • Computeren forestiller sig den hvide baggrund ved at tegne den i et bitmap, der kun eksisterer i hukommelsen

  • Computeren forestiller sig den røde firkant ovenpå den hvide baggrund, stadig ved at tegne i det kunstige bitmap

  • Computeren putter alle pixels fra bitmap'et over på skærmen, så baggrund og firkant kommer på nøjagtig samme tid (da de nu er ét billede)



Det kunstige bitmap er med andre ord vores buffer, og vores hukommelses-DC har kontrol over den, som variablen 'dc' før havde kontrol over vinduet.

Jeg kalder altid det kunstige bitmap for 'canvas', da det er det – og ikke skærmen – vi tegner på. Som du kan se, tager CreateCompatibleBitmap tre argumenter: En DC, en bredde og en højde. Bredden og højden er dimensionerne på bitmap'et. Det er fuldstændig samme koncept, som når du laver et bitmap med Paint; det har også en bredde og en højde. I dette program er bredden lig med vinduets indholdområdes bredde og højden lig med indholdområdes højde.

'BitBlt' står for "Bit Block Transfer" (udtales "Bitblit" – i hvert fald af amerikanere; jeg udtaler det "bitblt", og ja, det er svært at udtale!). BitBlt er funktionen, der overfører bittene fra det kunstige bitmap til skærmens DC (ps.hdc). Den ser således ud:

BOOL BitBlt(
__in HDC hdcDest,
__in int nXDest,
__in int nYDest,
__in int nWidth,
__in int nHeight,
__in HDC hdcSrc,
__in int nXSrc,
__in int nYSrc,
__in DWORD dwRop
);


hdcDest er vores destinations-DC, det vil sige, den DC, bittene skal overføres til.
nXDest og nYDest er koordinaterne for, hvor bittene skal starte. Hvis de er sat til 0, 0, skal billedet placeres i øverste venstre hjørne.
nWidth og nHeight er bitmap’ets dimensioner. Dimensionerne defineres i CreateCompatibleBitmap-funktionen.
hdcSrc er en handle til den DC, bittene kommer fra.
nXSrc og nYSrc er koordinatet for det punkt, bittene skal kopieres fra. Hvis koordinatet er 0, 0, skal den kopiere fra øverste venstre hjørne af hdcSrc.
dwRop er en såkaldt "Raster Operation". Du kan vælge en af et sæt binære operationer, du vil have udført ved hvert pixel i bitmap'et. Hvis du vælger SRCCOPY, kopierer den bittene som de er. Du kan se en liste over raster-operationer her: http://msdn.microsoft.com/en-us/library/dd183370(VS.85).aspx .

Så har vi et kald til DeleteDC. Kan du huske, jeg fortalte, at hver måde at anskaffe en DC på havde sin egen måde at komme af med den på? GetDC har ReleaseDC, og CreateCompatibleDC har DeleteDC. Som altid gør vi det for, at vi ikke efterlader noget af vores program i hukommelsen, efter det er lukket.

EndPaint-funktionen sletter den DC, BeginPaint gav os.

Det sidste nye er et kald til funktionen InvalidateRect. Det er den ene af de to højtideligheder, WM_PAINT sendes på. Den tager tre argumenter:

BOOL InvalidateRect(
HWND hWnd,
const RECT* lpRect,
BOOL bErase
);


hWnd er en handle til det vindue, der skal modtage WM_PAINT-beskeden.
lpRect er en pointer til en RECT-struktur, der udgør den del af vinduet, der skal invalideres. Hvis lpRect er sat til NULL, udvides området til hele vinduet.
bErase afgør, om området indenfor lpRect skal slettes eller ej.

Funktionen gør det, at den fortæller Windows, at en region af vinduet er forældet (invalidt), og at det skal fornys. Hvis bErase er true, bliver indholdet i den invalide region slettet. I forrige eksempler simulerede jeg denne effekt med Rectangle og FillRect.


Vedhæftede filer:


<< < 123 > >>

Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.

Del også gerne artiklen med dine Facebook venner:  

Kommentarer (13)

User
Bruger #10113 @ 25.05.09 16:32
Virker dette kun i Windows?
User
Bruger #8985 @ 25.05.09 17:32
Så vidt jeg ved, ja. Desværre :( Men jeg er overbevist om, at der findes alternativer til andre operativsystemer.
User
Bruger #11750 @ 02.06.09 23:04
Ikke lige mit fagområde :D Men det ser rigtig godt ud!
User
Bruger #8985 @ 04.06.09 21:17
Tak!
User
Bruger #9646 @ 23.09.09 14:07
windows.h - duer kun på windows?

Jeg har prøvet at smide ind i CodeBlocks og compiled på en Mac, og får en hel del errors :)

Hvordan kan man omskrive så den virker på mac?
User
Bruger #8985 @ 09.10.09 19:25
Jeg ville gerne hjælpe dig, men jeg har ingen idé om det. :( Jeg har aldrig skrevet programmer til andet end Windows. Faktisk har jeg aldrig ejet et operativsystem andet end Windows (eksempelvis Linux på en Virtual Machine for en kort stund). Jeg er bange for, der skal nogle helt andre biblioteker til, hvis du skal kode til Mac. Altså jeg tror ikke, der findes noget der hedder GDI ved Mac.
User
Bruger #15732 @ 04.03.10 17:09
Sakl lige teste noget håber det er ok jeg er nu på siden
Fold kodeboks ind/udKode 
#include <iostream>
std::cout << "test" << std::endl;
User
Bruger #15732 @ 04.03.10 17:10
Fold kodeboks ind/udKode 
#include <iostream>
User
Bruger #15732 @ 04.03.10 17:10
[code][#include <iostream>]
User
Bruger #15732 @ 04.03.10 17:12
Fold kodeboks ind/udKode 
[#include <iostream>]
User
Bruger #15732 @ 04.03.10 17:13
Fold kodeboks ind/udKode 
User
Bruger #15938 @ 22.06.10 15:44
Download: " Qt Creator "

- for at lave din egen Gui/ Graphical user interface med c++

Nemt og lækkert ;)
User
Bruger #14633 @ 13.09.10 15:00
Med Wine kan programmet linkes og koeres i Mac og mange POSIX-systemer.

Bemaerk at GDI er foraeldet af Microsoft til fordel for Direct2D.
Du skal være logget ind for at skrive en kommentar.
t