24
Tags:
c++
Skrevet af
Bruger #8985
@ 12.05.2009
Virtuelle og visuelle bitmaps
Måske blev du lidt skuffet, da du fandt ud af, at forrige brug af termet "bitmap" ikke involverede en bitmap-fil, der blev læst af programmet og vist i vinduet. Det kan også være, du ikke gjorde. Uanset hvad vil jeg råde bod på det nu, ved at vise, hvordan vi kan læse data fra en BMP-fil og få en HBITMAP ud af det.
Jeg sidder inde med følgende Bitmap (jeg kan af sikkerhedsmæssige årsager ikke oplyse navnet på det program, jeg har produceret grafikken i):
Den er gemt i filen Smiley.bmp, som ligger i samme mappe som mit program. Følgende er et eksempel, der læser filen og viser resultatet i vinduets indholdområde.
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HINSTANCE hInst;
static HBITMAP hSmiley;
PAINTSTRUCT ps;
HDC mem;
switch (message)
{
case WM_CREATE:
hInst = ((LPCREATESTRUCT)lParam)->hInstance;
hSmiley = (HBITMAP)LoadImage(hInst, TEXT("Smiley.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
return 0L;
case WM_PAINT:
BeginPaint(hwnd, &ps);
mem = CreateCompatibleDC(ps.hdc);
SelectObject(mem, hSmiley);
BitBlt(ps.hdc, 0, 0, 50, 50, mem, 0, 0, SRCCOPY);
DeleteDC(mem);
EndPaint(hwnd, &ps);
return 0L;
case WM_DESTROY:
DeleteObject(hSmiley);
DeleteObject(g_hbrBackground);
PostQuitMessage(0);
return 0L;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
Og lad os da endelig se resultatet:
Det eneste nye her er denne linje:
hSmiley = (HBITMAP)LoadImage(hInst, TEXT("Smiley.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
LoadImage-funktionen ser således ud:
HANDLE LoadImage(
HINSTANCE hinst,
LPCTSTR lpszName,
UINT uType,
int cxDesired,
int cyDesired,
UINT fuLoad
);hinst tager programmets "Instance Handle". På den måde kan funktionen finde ud af, hvor programmet ligger på harddisken, og altå lave den relative sti "Smiley.bmp" til en absolut sti. Hvis ikke du har en HINSTANCE ved hånden, kan du få den ved et kald til
GetModuleHandle(NULL).
lpszName er stien til billedet. Den kan være relativ såvel som absolut.
uType er billedets type.
LoadImage understøtter tre typer: Bitmaps (*.bmp), Ikoner (*.ico) og markører (*.cur).
cxDesired og
cyDesired er de ønskede dimensioner for billedet. Hvis begge er sat til 0, bliver billedets originale størrelse brugt.
fuLoad er måden, hvorpå billedet skal indlæses. LR_LOADFROMFILE læser direkte fra filen. Her er en liste over måder, billedet kan indlæses på:
http://msdn.microsoft.com/en-us/library/ms648045.aspx .
WM_PAINT er en smule anderledes denne gang. canvas-bufferen er væk, men vi behøver den heller ikke længere, for nu har vi jo allerede et bitmap – nemlig vores smiley (eller min, det er jo mig, der har tegnet den
). Overskriften "Virtuelle og visuelle bitmaps" er også mere prangende end præcis. Der findes hverken virtuelle eller kunstige bitmaps. 'hSmiley' er nøjagtig mage til 'canvas'. Eneste forskel er, at 'hSmiley' indeholder andre bits, nemlig dem, der er gemt i bmp-filen. Som du måske har gættet, betyder det også, at vi stadig kan tegne firkanter, streger, cirkler og tekst ovenpå vores smiley. Med lidt præcision kunne vi gøre øjnene grønne. Vi kan også lave lidt spas. Jeg fortalte før lidt om raster-operationer, og med en lille ændring, kan vi få smiley'en i negative farver. Hvis vi i stedet for SRCCOPY i
BitBlt bruger SRCINVERT, udebliver forskellen ikke:
Bitmapfiler har den fordel, at de understøtter et væld af farver, men de har også den ulempe, at de fylder langt mere end filer som GIF, JPEG og PNG. Hvis vi har et program med en masse billeder, ville det måske være smartere at have dem i et af de tre formater. Desværre er der ingen funktion i Win32-API'et, der kan indlæse filer i de formater. Det er dog i den grad muligt, og der findes flere online biblioteker til det formål, man frit kan downloade, men det bevæger vi os ikke ind på i denne artikel. GDI-kendere ved sikkert også, at der er noget, der hedder metafiler og DIB (Device Independent Bitmap) filer. Det vil jeg med vilje heller ikke tage med i denne artikel, da vi så bevæger os væk fra grundlæggende GDI, og det er ikke meningen. Derfor vil al billedgrafik i denne artikel være i form af bitmaps.
GetWindowDC
Indtil videre har vi arbejdet med DC'ere, der kun har berørt vinduets indholdområde. Men hvad med resten? Hvad med ikonet, det røde kryds, vinduets ramme og vinduets titel? Jo, der er naturligvis også en måde at få fat på dem. Løsningen er at bruge
GetWindowDC i stedet for GetDC. Jeg vil nu prøve at opsnappe hele vinduet, og ikke kun indholdområdet.
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HDC hScreenDC;
static RECT rcWindowArea;
PAINTSTRUCT ps;
HDC mem;
HBITMAP canvas;
static bool bSleep = true;
switch (message)
{
case WM_SIZE:
GetWindowRect(hwnd, &rcWindowArea);
if (bSleep)
{
bSleep = false;
Sleep(1000);
}
InvalidateRect(hwnd, &rcWindowArea, true);
return 0L;
case WM_PAINT:
BeginPaint(hwnd, &ps);
hScreenDC = GetWindowDC(hwnd);
mem = CreateCompatibleDC(ps.hdc);
canvas = CreateCompatibleBitmap(ps.hdc, rcWindowArea.right, rcWindowArea.bottom);
SelectObject(mem, canvas);
FillRect (mem, &rcWindowArea, g_hbrBackground);
BitBlt(mem, 0, 0, rcWindowArea.right, rcWindowArea.bottom, hScreenDC, 0, 0, SRCCOPY);
BitBlt(ps.hdc, 0, 0, rcWindowArea.right, rcWindowArea.bottom, mem, 0, 0, SRCCOPY);
DeleteObject(canvas);
DeleteDC(mem);
EndPaint(hwnd, &ps);
return 0L;
case WM_DESTROY:
DeleteObject(g_hbrBackground);
DeleteDC(hScreenDC);
PostQuitMessage(0);
return 0L;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
Og resultatet:
Som du kan se, bruger dette program to
BitBlt-kald. Det første overfører bittene fra hScreenDC, som, takket være
GetWindowDC fremfor
GetDC, er en DC, der kontrollerer grafikken for hele vinduet og ikke kun indholdområdet, over til mem, en hukommelses-DC. Det næste kald overfører bittene fra mem, vores hukommelses-DC, til ps.hdc, som er DC'en for indholdområdet. Derudover fryser jeg programmet i et sekund, efter det har opfanget vinduets position på skærmen. Det gør jeg, fordi jeg vil sikre mig, vinduet er vist på skærmen, inden vi kopierer bittene. Ellers kopierer programmet fra vinduet under vores vindue.
Vi er jo ved at blive ganske ferme. Vi kan læse data fra en bitmapfil og vise det på skærmen, vi kan tegne firkanter og cirkler og få dem til at bevæge sig, og vi kan kopiere dele af vores vindue, selv udenfor indholdområdet. I spil optræder der mange elementer på en gang. I stedet for at sidde og kalde
BitBlt ved hvert eneste element, kunne vi bruge en løkke. Følgende program kopierer et område af skærmen – ikke vinduet, skærmen – og fylder indholdsområdet med det gentagne gange.
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HDC hScreenDC;
static RECT rcClientArea;
static int cxCopy = 100; // Dimensionerne på det stykke af skærmen, vi vil kopiere
static int cyCopy = 100;
PAINTSTRUCT ps;
HDC mem;
HBITMAP canvas;
switch (message)
{
case WM_CREATE:
hScreenDC = GetDC(NULL);
return 0L;
case WM_SIZE:
GetClientRect(hwnd, &rcClientArea);
InvalidateRect(hwnd, &rcClientArea, false);
return 0L;
case WM_PAINT:
BeginPaint(hwnd, &ps);
mem = CreateCompatibleDC(ps.hdc);
canvas = CreateCompatibleBitmap(ps.hdc, rcClientArea.right, rcClientArea.bottom);
SelectObject(mem, canvas);
FillRect (mem, &rcClientArea, g_hbrBackground);
for (int y = 0; y < rcClientArea.bottom; y += cyCopy)
for (int x = 0; x < rcClientArea.right; x += cxCopy)
BitBlt(mem, x, y, cxCopy, cyCopy, hScreenDC, 0, 0, SRCCOPY);
BitBlt(ps.hdc, 0, 0, rcClientArea.right, rcClientArea.bottom, mem, 0, 0, SRCCOPY);
DeleteObject(canvas);
DeleteDC(mem);
EndPaint(hwnd, &ps);
return 0L;
case WM_DESTROY:
DeleteObject(g_hbrBackground);
DeleteDC(hScreenDC);
PostQuitMessage(0);
return 0L;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
Resultat:
Tricket er at kalde
GetDC med NULL som argument. Så får du en DC for hele skærmen.
Opgaver
At kigge koder igennem og læse tilhørende forklaringer er en god måde at lære på, men tingene går endnu hurtigere, når man forsøger selv. Prøv at løse nogle af disse opgaver.
- Lav et program, der tegner en cirkel, som følger musen.
- Lav et program, der udskriver musens position på skærmen.
- Lav et program, der tegner en firkant, hvis position man kan ændre med piltasterne.
- Lav et program, der tager et screenshot af skærmen og viser det i indholdområdet i negative farver.
- Lav et program, der trækker en streg fra midten af indholdområdet ud til musens position. Hint: MoveToEx(), LineTo().
- Lav et program, der udskriver tekst, hvis farve skifter hvert sekund. Hint: SetTextColor().
- Lav et program, der via en OPENFILENAME-struktur tillader brugeren at vælge en bitmapfil, der bliver vist i indholdområdet.
- Lav et program, der tillader brugeren at lave firkanter med musen, nøjagtig som i Paint.
- Lav et program, der udskriver R, G og B værdierne for farven på den pixel, musen hviler på. Hint: GetPixel().
- Lav et program, der farver den pixel af indholdområdet, man klikker på, en bestemt farve. Hint: SetPixel().
Til slut
Det er alt, jeg vil fortælle om GDI for denne gang. Hvis artiklen bliver tilpas populær, kommer der med garanti flere af slagsen. Som jeg nævnte tidligere, er der visse ting, jeg bevidst har udeladt, for GDI er faktisk temmelig omfattende, og det ville være urimeligt og endeløst at presse det hele sammen i én artikel. Så vil jeg langt hellere skrive flere artikler, der hver i sær fokuserer på et specifikt emne indenfor GDI. I kodeeksemplerne har jeg kun indsat
WndProc-funktionen, for det er den eneste, der varierer fra program til program. Det betyder dog, at du ikke bare kan nøjes med at kopiere koden i en kodeboks, indsætte den i din IDE og forvente, at den kan kompileres – men igen, det er jo heller ikke meningen. Jeg håber, du kunne lide artiklen og drage nytte af den. Jeg har for så vidt muligt forsøgt at rette alle stave- og trykfejl, men hvis et par enkelte skulle forekomme, så bedes du bære over med mig.
For at 'peake' din lyst til at gå i gang med at lave fede GDI-applikationer, har jeg taget to screenshots af et lille spil, jeg har lavet i GDI.
Spillet går ud på at samle alle diamanterne ved at gå ind i dem med Smiley'en. Man bevæger sig med piltasterne, og man hopper med mellemrumstasten. Alle bevægelser starter og slutter blødt. De røde felter er trampoliner, der tillader smiley'en at hoppe dobbelt så højt, så man også kan nå de øverste diamanter. Når man har samlet alle diamanterne, kommer der en "Game Over"-boks, der fortæller en, hvor længe man var om at samle dem. Herfra kan man trykke 'J' for at spille igen, eller 'N' for at afslutte programmet.
Vedhæftede filer:
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)
Virker dette kun i Windows?
Så vidt jeg ved, ja. Desværre
Men jeg er overbevist om, at der findes alternativer til andre operativsystemer.
Ikke lige mit fagområde
Men det ser rigtig godt ud!
Tak!
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?
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.
Sakl lige teste noget håber det er ok jeg er nu på siden
#include <iostream>
std::cout << "test" << std::endl;
[code][#include <iostream>]
Download: " Qt Creator "
- for at lave din egen Gui/ Graphical user interface med c++
Nemt og lækkert
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.