Grafika w oknie2


Grafika w oknie - Bitmapy i blittowanie

Z tego tutoriala dowiesz się w jaki sposób odczytywać bitmapy z plików BMP i wyświetlać je w oknie. Bazą będzie dla nas program "Grafika w oknie - inicjalizacja".

Zacznijmy od napisania procedury odczytującej pliki BMP, która będzie jako parametr przyjmować nazwę pliku i zwracać adres do bitmapy oraz jej wymiary. Użyjemy do tego celu funkcji LoadImage oraz GetDIBits. Oto parametry funkcji LoadImage:

HINSTANCE hinst, // handle of the instance that contains the image

LPCTSTR lpszName, // name or identifier of image

UINT uType, // type of image

int cxDesired, // desired width

int cyDesired, // desired height

UINT fuLoad // load flags

Jeżeli chcemy odczytywać bitmapę z pliku, wystarczy że podamy jego nazwę jako lpszName, w uType użyjemy stałej IMAGE_BITMAP, która oznacza, że podany plik jest bitmapą, natomiast w fuLoad skorzystamy z flagi LR_LOADFROMFILE. W pozostałych parametrach możemy podać 0.

invoke LoadImage,0,lpFileName,IMAGE_BITMAP,0,0,LR_LOADFROMFILE

mov ebx,eax

Jeżeli wszystko pójdzie dobrze, LoadImage zwróci nam uchwyt do bitmapy (hBitmap), który zapamiętujemy w rejestrze ebx.

Mając już uchwyt, możemy skorzystać z GetDIBits. Wywołamy ją dwukrotnie: najpierw aby odczytać wymiary i inne właściwości bitmapy, a później aby uzyskać samą bitmapę jako ciąg bajtów. Funkcja ta wymaga następujących parametrów:

HDC hdc, // handle of device context

HBITMAP hbmp, // handle of bitmap

UINT uStartScan, // first scan line to set in destination bitmap

UINT cScanLines, // number of scan lines to copy

LPVOID lpvBits, // address of array for bitmap bits

LPBITMAPINFO lpbi, // address of structure with bitmap data

UINT uUsage // RGB or palette index

Widzimy, że funkcja wymaga podania HDC. Właściwie nie wiadomo po co, ale jak chce to jej to damy :).

invoke CreateCompatibleDC,0

mov esi,eax

Dzięki temu w rejestrze esi ląduje uchwyt device contextu kompatybilnego z pulpitem.

Z API helpa dowiadujemy się, że jeżeli parametr lpvBits jest ustawiony na 0, to funkcja GetDIBits wypełnia strukturę BITMAPINFO, której adres podamy jako lpbi. W tekście "Grafika w oknie - inicjalizacja" zadeklarowaliśmy już taką strukturę, teraz możemy ją wykorzystać. Dla pewności wypełnijmy ją zerami i ustawmy właściwą wartość biSize.

mov edi,offset bmi

assume edi:ptr BITMAPINFO

push edi

xor eax,eax

mov ecx,sizeof BITMAPINFO

rep stosb ;wypełnianie struktury zerami

pop edi

mov [edi].bmiHeader.biSize,sizeof BITMAPINFOHEADER

Teraz można już wywołać funkcję GetDIBits.

xor edx,edx

invoke GetDIBits,esi,ebx,edx,edx,edx,edi,edx

W rejstrze esi mamy hDC, w ebx - hBitmap, a w edi adres struktury BITMAPINFO. Edx wynosi zero. Gdy w strukturze bmi znajdują się już wszystkie własności bitmapy, przyjrzyjmy się ponownie parametrom funkcji GetDIBits.

Mamy więc już wszystkie potrzebne parametry oprócz lpvBits. Tę tablicę musimy utworzyć, na przykład przy pomocy funkcji GlobalAlloc. Tablica musi pomieścić całą bitmapę. Jak obliczyć jej wielkość w bajtach? Po prostu obliczamy ilość pixeli (zwyczajnie mnożąc wysokość przez szerokość) i mnożymy przez cztery, ponieważ każdy pixel jest zapisany jako cztery bajty.

mov eax,[edi].bmiHeader.biWidth ;eax=width

mul [edi].bmiHeader.biHeight ;eax=width*height

shl eax,2 ;eax=width*height*4

invoke GlobalAlloc,GMEM_FIXED,eax

push eax ;zapamietaj

test eax,eax

jz @F

Wartość zwróconą przez GlobalAlloc (czyli adres obszaru pamięci, w którym zostanie umieszczona nasza bitmapa) zapamiętujemy na stosie - przyda nam się później. Sprawdzamy również czy pamięć została zaalokowana poprawnie, a jeśli wystąpił błąd skaczemy do miejsca w którym zostaną zwolnione zasoby i nastąpi zakończenie procedury.

Teraz możemy ponownie wywołać funkcję GetDIBits. Musimy jeszcze pamiętać o jednej rzeczy - parametr biHeight w strukturze bmi musi być liczbą przeciwną do wysokości bitmapy (w innym przypadku otrzymamy bitmapę odwróconą do góry nogami).

push DIB_RGB_COLORS ;uUsage

push edi ;struktura bmi

push eax ;tablica w której będzie zapisana bitmapa

push [edi].bmiHeader.biHeight ;wysokość bitmapy

push 0 ;pierwsza linia do odczytania

push ebx ;hBitmap

push esi ;hDC

mov [edi].bmiHeader.biBitCount,32

;ustawiamy głębię kolorów na 32 bity

neg [edi].bmiHeader.biHeight ;odwracamy parametr biHeight

call GetDIBits

Gdy mamy już wszystko co potrzeba, należy jeszcze po sobie posprzątać. Usuwamy więc device context oraz uchwyt do bitmapy (hBitmap), który nie będzie nam już potrzebny.

@@:

invoke DeleteDC,esi

invoke DeleteObject,ebx

Teraz wystarczy już tylko zwrócić potrzebne wartości i zakończyć procedurę. Zwracamy w eax adres do pamięci zawierającej bitmapę oraz jej szerokość i wysokość (odpowiednio w ecx i edx).

pop eax ;lpBitmap

mov ecx,[edi].bmiHeader.biWidth ;szerokość

mov edx,[edi].bmiHeader.biHeight ;wysokość

assume edi:nothing

_ret:

ret

Gdy mamy już procedurę odczytującą bitmapy z plików, spróbujmy wyświetlić jedną z nich w oknie. W archiwum dołączonym do tego tekstu znajdują się pliki galaxy.bmp oraz kolo.bmp. Aby je odczytać w programie, wystarczy dopisać ich nazwy w sekcji .data...

Galaxy db "galaxy.bmp",0

Kolo db "kolo.bmp",0

... oraz zadeklarować zmienne w których będziemy trzymać adresy tych bitmap.

lpKolo dd ?

lpGalaxy dd ?

Podczas inicjalizacji (w procedurze DoGfx) możemy użyć funkcji LoadBitmapFromFile.

invoke LoadBitmapFromFile,addr Galaxy

mov lpGalaxy,eax

invoke LoadBitmapFromFile,addr Kolo

mov lpKolo,eax

Pamiętajmy o tym aby zwolnić pamięć zajętą przez te bitmapy, gdy już nie będziemy ich potrzebować. Najlepiej zrobić to podczas deinicjalizacji. Używamy funkcji GlobalFree, gdyż pamięć na bitmapy została zaalokowana za pomocą GlobalAlloc.

invoke GlobalFree,lpKolo

invoke GlobalFree,lpGalaxy

Jak wyświetlić bitmapę w oknie? Wiemy, że bitmapy są jedynie fragmentami pamięci zawierającymi ciąg bajtów, w których jest zapisana grafika. Całą grafikę rysujemy w bitmapie, której adres mamy zapamiętany w zmiennej lpBackBitmap. A zatem skoro zarówno lpGalaxy jak i lpBackBitmap są bitmapami i to w dodatku o takich samych rozmiarach (320x200), wystarczy skopiować bitmapę lpGalaxy do lpBackBitmap, tak jak byśmy kopiowali zwykły obszar pamięci.

mov esi,lpGalaxy

mov edi,lpBackBitmap

mov ecx,320*200

rep movsd

W ten sposób można postępować tylko jeżeli obydwie bitmapy (źródłowa i docelowa) mają takie same rozmiary.

Spróbujmy teraz nałożyć bitmapę lpKolo na lpGalaxy (proces ten nazywa się blittowaniem). Wiemy, że mają one różne rozmiary (lpKolo - 64x64, lpGalaxy - 320x200). Popatrzmy na rysunek.

0x01 graphic

Zastanówmy się jak to zrobić. Najpierw trzeba obliczyć miejsce w pamięci w którym znajduje się punkt A (korzystając ze wzoru podanego w artykule "Grafika w oknie - inicjalizacja"), następnie skopiować tam pierwsze 64 pixele z bitmapy lpKolo (jak widzimy, 64 to szerokość tej bitmapy). W ten sposób znajdziemy się w punkcie B. Aby przejść do kolejnej linii musimy dodać 320-64, czyli 256 pixeli, czyli 1024 bajty. Potem znowu skopiować 64 pixele, i tak dalej, w sumie 64 razy (bo 64 to wysokość lpKolo). Zapiszmy to jako kod.

mov esi,lpKolo ;bitmapa źródłowa

mov edi,lpGalaxy ;bitmapa docelowa

add esi,(68*320+10)*4 ;przechodzimy do punktu A

;o współrzędnych (10,68)

mov eax,64 ;licznik linii czyli wysokość lpKolo

_blt:

mov ecx,64 ;szerokość lpKolo

rep movsd ;kopiujemy 64 pixele

add edi,(320-64)*4 ;przechodzimy do następnej linii

dec eax ;zmniejszamy licznik linii...

jnz _blt ;...i powtarzamy operacje tak długo

;aż nie skopiujemy całej bitmapy

Mam nadzieję, że rozumiecie cały ten proces. Teraz zapiszmy go jako uniwersalną procedurę.

BitBlt32 proc uses esi edi

lpDstBitmap:dword, ;adres docelowej bitmapy

lpSrcBitmap:dword, ;adres źródłowej bitmapy

SrcW:dword, ;szerokość źródłowej bitmapy

SrcH:dword, ;wysokość źródłowej bitmapy

DstX:dword, ;

DstY:dword, ;współrzędne punktu A

DstW:dword, ;szerokość docelowej bitmapy

DstH:dword ;wysokość docelowej bitmapy

mov esi,lpSrcBitmap

mov edi,DstY ;przechodzimy do punktu A

imul edi,DstW

add edi,DstX

shl edi,2

add edi,lpDstBitmap

mov edx,DstW ;obliczanie wartości którą należy

sub edx,SrcW ;dodać do edi w celu przejścia

shl edx,2 ;do następnej linii

mov eax,SrcH ;licznik linii

_blt:

mov ecx,SrcW ;szerokość bitmapy źródłowej

rep movsd ;kopiowanie

add edi,edx ;przejście do następnej linii

dec eax ;zmniejszenie licznika linii

jnz _blt ;powtarzanie operacji aż

;skopiujemy wszystkie linie

ret

BitBlt32 endp

Z tej procedury możemy bardzo łatwo skorzystać podczas rysowania.

invoke BitBlt32,lpBackBitmap,lpKolo,64,64,10,68,WND_WIDTH,WND_HEIGHT

Widzmy, że koło które dzięki temu wyświetliło się w oknie posiada jaskrawozieloną ramkę. Dobrze byłoby mieć możliwość wyświetlania bitmap z niewidocznym jednym kolorem. Nic prostszego! Wystarczy przy blittowaniu pomijać pixele o kolorze, który uznamy za przezroczysty (w naszym przypadku jest to kolor zielony, 00FF0000h).

Musimy zamienić linijkę "rep movsd" w powyższej procedurze na kod, który odczytuje pixel po pixelu i porównuje je z kolorem przezroczystym, a jeżeli są równe, po prostu pominie ten pixel. Oto przykład takiego kodu:

_wew: lodsd ;odczytaj pixel

and eax,00FFFFFFh ;wyczyść bajt alpha

cmp eax,ColorKey ;czy pixel ma być przezroczysty?

je @F ;jeśli tak, pomiń go

mov [edi],eax ;zapisz pixel do bitmapy docelowej

@@: add edi,4 ;przejście do następnego pixela

dec ecx

jnz _wew ;powtórz dla wszystkich pixeli



Wyszukiwarka

Podobne podstrony:
Grafika 2
Grafika komputerowa 2
Grafika 11
6 Grafika
Wykład I Grafika inżynierska cz2
Grafika komputerowa i OpenGL
lab grafika3D 7 Zadania
08 GIMP tworzenie grafiki na potrzeby WWW (cz1)
02 grafika inzynierska
12 GIMP tworzenie grafiki na potrzeby WWW (cz5)
ściąga grafika, PW Transport, Grafika inżynierska II
Automatyczne formatowanie dokumentu, informatyka, grafika
zadanie pl2, SGSP, I ROK, Grafika
egzamin-co-ma-byc, Semestr 3, Grafika i przetwarzanie obrazów
GIMP, SZKOŁA, Informatyka, Grafika Komputerowa

więcej podobnych podstron