3.7 Bitmapy
Z poprzedniej lekcji wiemy jak rysować różne figury na okienkach, ale spróbujmy narysować drzewo z samych linni i elips. No właśnie, będzie to co najmniej trudne. Na szczęście GDI to nie same figury, ale także operacje na skomplikowanych obrazach rastrowych, czyli bitmapach. Przejdźmy do rzeczy.
Na ten temat już sobie nieco wcześniej powiedzieliśmy. Obiekt bitmapy to poprostu bitmapa, która egzystuje sobie gdzieś w pamięc. Tutaj także trzeba sobie powiedzieć, że do samej bitmapy nie mamy dostępu, możemy się do niej odwoływać poprzez uchwyt(HBITMAP).
Nasz program może sobie stworzyć bitmapę. Robi się to funkcją CreateCompatibleBitmap().
Może zacznę od dwóch ostatnich parametrów, otóż są to wymiary bitmapy, kolejno szerokość i wysokość. Pierwszym parametrem jest tutaj kontekst urządzenia, z jakim ma być kompatybilna bitmapa, tzn. bitmapa pobierze parametry z kontkestu, jest to ważne, tutaj powinniśmy podać kontekst tego urządzenia, na którym ma być wyświatlana bitmapa, zwykle będzie to uchwyt do kontekstu okna.
Mamy także możliwość utworzenia bitmapy z parametrami, które sami określimy, ale trzeba pamiętać, że aby było można wyświetlić daną bitmapę, musi ona mieć takie parametry jak kontekst urządzenia na której będzie wyświetlona. Oto funkcja:
Jak widać funkcją tworzymy bitmapę od zera, z uwzględnieniem jej wszystkich parametrów(cech). Pierwsze dwa parametry są jak w przypadku poprzedniej funkcji, odpowiednio szerokość i wysokość. Parametr cPlanes określa przestarzałą ilość płatów, obecnie coś takiego jest nieużywane i zawsze podajemy tu wartość 1. W parametrze cBitsPerPel podajemy głębię kolorów(format piksela) bitmapy, możemy podać 8, 16, 24, czy 32, jeżeli chcemy używać kanału alpha. Ostatni parametr jest ciekawy, to wskaźnik do struktury bitów, którymi ma zostać wypełniona bitmapa, innymi słowy jest to binarna zawartość bitmapy, jeżeli narazie nie chcemy jej inicjować, możemy podać 0. Jeszcze raz przypominam, że tworząc bitmapę tą funkcją, parametry które podamy, muszą się zgadzać z kontekstem urządzenia, w przeciwnym razie mogą wystąpić trudności z wyświetleniem bitmapy w danym kontekście.
COLORREF zawartosc[
HBITMAP bitmapa=CreateBitmap(
...
Teraz zajmiemy się otwieraniem bitmapy. Przy otwieraniu mamy dwie możliwości, mianowicie możemy otwierać bitmapę z zasobów programu lub z pliku na dysku. Funkcja otwierająca, którą poznamy to LoadImage().
HINSTANCE hInst
W tym parametrze podajemty uchwyt do programu, jeżeli otwieramy bitmapę z zasobów, należy tutaj podać uchwyt do programu, w którym się znajduje otwierana bitmapa.
LPCTSTR lpszName
W tym parametrze podajemy ścieżkę do pliku, jeżeli otwieramy z dysku, a jeżeli z zasobów nazwę zasobu.
UINT uType
Funkcja ta jest unierwsalna i oprócz wczytywania bitmapy, obsługije także wczytywanie ikon i kursorów. Tutaj określamy rodzaj wczytywanego obiektu: IMAGE_BITMAP dla bitmpay, IMAGE_ICON dla ikony, IMAGE_CURSOR dla kursora.
INT cxDesired i INT cyDesired
Przy wczytywaniu kursora lub ikony, należy tutaj podać wymiary jakie chcemy uzyskać, przy wczytywaniu bitmapy parametry te są ignorowane, możemy podać 0.
UINT fuLoad
Tutaj podajemy dodatkowe flagi, każdą z nich możemy połączyć operatorem sumy bitowej:
LR_CREATEDIBSECTION - podając ją wczytywana bitmapa, będzie miała orginalne kolory, gdyż normlanie funkcja ta dopasowuje format kolorów bitmapy do ekranu.
LR_MONOCHROME - flaga ta zredykuje głębię kolorów bitmapy do tryby monochromatycznego(czarno-biały)
LR_LOADFROMFILE - gdy chcemy odczytać bitmapę z pliku, należy użyć tej flagi.
LR_LOADTRANSPARENT - funkcja ta sprawdza pierwszy piksel w obrazku, a następnie każdy taki sam kolor piksela wczytuje jako przezroczysty.
LR_LOADDEFAULTSIZE - w przypadku ikony i kursora, flaga ta powoduje, że rozmiary ich będą niezmienione(domyślne).
LR_SHARED - przy kolejnym załadowniu tej samej bitmapy, otrzymujemy uchwyt na ten sam obiekt w pamięci.
Zwracana wartość
Funkcja zwraca uchwyt wczytanego obiektu, a jeżeli się nie powiedzie zwraca 0. Uchwyt obiektu trzeba rzutować, gdyż jest zwracany w uchwycie uniwersalnym.
Wczytanie lub utworzenie bitmapy to dopiero początek. Mając bitmapę w pamięci, aby cokolwiek z nią zrobić musimy stworzyć dla niej kontekst. Kontekst to miejsce po jakim rysujemy oraz jego narzędzia. Tworząc kontekst od okna miejscem po jakim rysujemy jest właśnie powierzchnia tego okna, aby rysować na bitmapie, trzeba także stworzyć dla niej kontekst, w którym będziemy rysować właśnie na niej przy pomocy narzędzi w tym kontekście. Taki kontkest, który nie jest związany z żadnym oknem, tzn. nie jest nigdzie wyświetlany, nazywa się kontkestem pamięciowym, gdyż jest tylko w pamięci.
Kontekst pamięciowy stworzymy sobie funkcją CreateCompatibleDC():
Nasz kontekst pamięciowy z bitmapą, możemy utworzyć od innego istniejacego kontekstu, gdyż musi byc on kompatybilny z nim, najlepiej podać tutaj uchwyt do kontekstu okna, na którym będzie ta bitmapa w przyszłości wyświetlana.
Po utworzeniu kontekstu pamięciowego i załadowaniu bitmapy do pamięci, trzeba je połączyć, tzn. umieścić bitmapę w naszym kontekście pamięciowym. Robimy to doskonale znaną funkcją SelectObject().
Po powiązaniu bitmapy z kontekstem pamieciowym, nie trzeba jej zwalniać jako obiektu, gdyż zostanie ona zwolniona razem z całym kontekstem, gdy będziemy niszczyć nasz kontekst pamięciowy.
Oczywiście po zakończeniu pracy z bitmapą należy zwolnić kontkest pamięciowy funkcją DeleteDC(HDC hdc).
Tutaj już mówiąc o operacjach na bitmapie, mam na myśli bitmapę jak cały kontekst, gdyż przed chwilą powiązaliśmy ją z kontekstem pamięciowym, dlatego, że w GDI wszelkie operacje, dokonuje się poprzez kontekst urządzenia i właśnie dlatego utworzyliśmy dla naszej bitmapy kontekst. W kontekście tym obszar po, którym rysujemy jest bitmapa, odwołując się do kontekstu, możemy edytować bitmapę tak jak edytujemy okno.
W poprzedniej lekcji poznaliśmy funkcje rysujące na oknie, otóż okno to w rzeczywistości bitmapa, która jest wyświetlana na ekranie monitora. Teraz nasz kontkest z bitmapą różni się od kontekstu pobranego z okna, tylko tym, że mu w kontekście mamy bitmapę.
Zauważmy, że we wszystkiech funkcjach rysujących z poprzedniej lekcji podajemy uchwyt do kontekstu urządzenia, myślę, że wiesz do czego zmierzam, otóż gdy podabmy w tych funkcjach uchwyt do naszego kontekstu pamieciowego z bitmapą, będziemy rysować właśnie na naszej bitmapie. Na tym właśnie polegają operacje na bitmapie, możemy sobie na niej narysować kwadrat, trójkąt czy cokolwiek innego, przy użyciu funkcji, które poznaliśmy w poprzedniej lekcji.
Myślę, że każdy spotkał się z systemowym programem graficznym Paint. Otóż spójrzmy na niego, jest to program doskonale prezętujący możliwości GDI, ale nie o tym chciałem mówić, otóż, chyba każdy zna narzędzie w paincie, które służy do wylewania koloru, ma iknokę pojemniczka, z którego wylewa się farba. Polega to na tym, że wybierany jest piksel na danej bitmapie, zmieniany jest jego kolor i teraz wszystkie sąsiednie piksele o takim samym kolorze, także zostaną zmienione na dany kolor.
Teraz przedstawię funkcję, która to robi, efekt może być używany oprócz bitmap, także na oknach(bo okno to bitmapa), ale jest to raczej operacja typowa dla bitmap.
HDC hdc
Pierwszy parametr to tradycyjnie uchwyt do kontekstu, może być to uchwyt do kontekstu pamięciowego bitmapy, lub zwyczajny konteklst okna.
INT nXStart i INT nYStart
Są to współrzędne punktu, od którego ma zacząć się cała operacja wypełnienia.
COLORREF crColor i UNIT fuFillType
Najpierw zająłem się ostatnim parametrem, jest to rodzaj wypełnienia:
FLOODFILESURFACE - ten tryb działa tak jak w paincie, wszystkie sąsiednie piksele zostają wypełnione, kolorem aktualnego pędzla(może to byc pędzel z bitmapy lub wzór). W tym trybie zmienna crColor, musi mieć takie sam kolor, jak piksel, który jest początkiem operacji, można go pobrać funkcją GetPixel().
FLOODFILLBORDER - Jest to tryb odwrotny do poprzedniego, w tamtym trybie kolor w parametrze crColor, był warunkiem kontynuowania jego wypełnienia, natomiast w tym trybie jest on kryretium zatrzymania wypełniania. W tym trybie funkcja maluje całą bitmapę, aż nie zostanie zamknięta obramowaniu danego koloru, podanego w parametrze crColor. Tej funkcji paint nie posiada.
Stworzyliśmy już kontkest pamięciowy z bitmapą, wykonywaliśmy na niej operacje graficzne, tylko co z tego skoro jeszcze nie potrafimy wyświetlić efektów naszej pracy.
Technicznie wyświetlenie bitmapy polega na przekopiowniu zawartości bitmapy z kontekstu pamięciowego do kontekstu okna, które juz jest wyświetlane w windowsie. Możemy do tego użyć funkcji BitBlt():
Na pierwszy rzut okna, funkcja może przyprawić o zawrót głowy, ale w rzeczywistości jest bardzo prosta.
HDC hdcDes
Pierwszy z kontekstów, określa przeznaczenie(destiantion) bitmapy, czyli miejsce, gdzie ma zostać przekopiowana, zwykle podamy tutaj uchwyt do okna, na którym ma zostać wyświetlona.
INT nXDes i INT nYDes
Te dwa parametry to współrzędne lewego górnego wierzchołka bitmapy, określają położenie bitmapy na nowym celu, czyli oknie.
INT nWidth i INT nHeight
Są to rozmiary kopiowanego fragmentu bitmapy, oczywiste chyba, że jeżeli chcemy przekopiować całą bitmapę, podajemy jej wymiary, rozmiar ten będzie jednocześnie wielkością wyświetoną na oknie.
HDC hdcSrc
Skoro pierwszy określa cel, to ten musi określać źródło bitmapy, czyli kontekst pamięciowy, z którego zostanie przkopiowana.
INT nXSrc i INT nYSrc
Te parametry określają to co nXDes i nYDes,z tym, że te określają położenie źródła kopiowanej bitmapy, jeżeli chcemy skopiować cały obraz, podajemy 0,0.
DWORD dwRop
Ten parametr określa sposób łączenia bitów bitmapy, z bitami powierzchni, która zostanie przykryta na kontekście przeznaczenia(oknie). Można tu podać wiele róznych kombinacji łączenia pikseli obu kontekstów, ja podam tylko jeden, który jest zwykle wykorzystywany, wykorzystywanie reszty to raczej rzadkość. Otóż to flaga SRCCOPY, czyli przykrycie powierzchni okna bitmapą.
W GDI jest jeszcze jedna funkcja, która kopiuje bitmape na okno, różni sie ona tym, że potrafi przesklaować bitmapę podczas kopiownaia.
HDC hdcSrc,INT nXScr,INT nYSrc,INT nWidthSrc,INT nHeightSrc,DWORD dwRop);
Większość parametrów odpowiada funkcji BitBlt(), różnią się jedynie wymiary bitmap, otóż tam podawaliśmy wymiar w jendym miejscu, tutaj oddzielnie podajemy wymairy obszaru kopiowanego z bitmapy, a oddzielnie wymairy obrazu wklejanrgo na okno, gdyż funkcja potafi przeskalować bitmapę, jednak nie należy się spodziewać oszałamiających efektów skalowania.
W GDI jest jeszcze jedna funkcja do kopiowania bitmapy z kontekstu pamięciowego do kontekstu okna wyświetlanego. Jak można się domyślać, po nagłówku, że funkcja ta potrafi kopiować przezroczyście, czyli nie kopiować pewnych pikseli, zostawiając w ich miejscu te, które są na oknie. Funkcja ta TransparentBlt(), została wprowadzona dopiero w Windows 98.
Aby było można skorzystać z tej funkcji, trzeba dodać bibliotekę do linkowanych modułów w projekcie, w dziale "Kontrolki", jest dokładnie opisane jak to zrobić, bibliotekę dodajemy poprzez dopisanie:
-lmsimg32
Dodatkowo w pliku nagłówkowym, jest wymóg, aby WINVER był większy lub równy 0x0500, dlatego musimy jeszcze zadeklarować na samym początku kodu, jeszcze przed dołączeniem plików nagłówkowych:
#define WINVER 0x0600
HDC hdcSrc,INT nXScr,INT nYSrc,INT nWidthSrc,INT nHeightSrc,COLORREF crTransparent);
Funkcja w budowie bardzo podobna do StretchBlt(), co oznacza, że także potrafi skalować bitmapę, jedynie zmienił się ostatni parametr. Teraz zamiast podawać rodzaju operacji bitowej, podajemy wartość koloru. Kolor ten to taki jakby wyznacznik koloru przezroczystego, otóż gdy funkcja, znajdzie piksel o tym kolorze zostanie on potraktowany jako przezroczysty i nie zostanie przekopiowany, tak właśnie powstanie efekt przezroczystości.
Ci, którzy mieli ochotę zaszaleć z bitmapami wykorzystując kanały alpha(przezroczystość), muszę niestety zmartwić, GDI jest generalnie przygotowane do pracy z 24 bitowymi bitmapami, jendak posiada ono małe wsparcie dla kanału alpha, jednak efekty stosowania tychże funkcji są mizerne, często bitmapy po takich operacjach mają zły kontrast, dlatego lepiej jest uzywać 24 bitowego foramtu, a efekty przezroczystości wykorzystywać takimi funkcjami jak TransparentBlt().
Gdy otwieramy bitmapę z pliku na dysku, nie zawsze wiemy jakie ma ona wymairy, aby się dowiedzieć trzeba utworzyć strukturę BITMAP i umiescić w niej informacje.
{
INT bmType;
INT bmWidth;
INT bmHeight;
INT bmWidthBytes;
BYTE bmPlanes;
BYTE bmBitsPixel;
LPVOID bmBits;
}
Do pobrania informacji do tej struktury możemy użyć funkcji GetObject():
HBITMAP bitmapa=(HBITMAP)LoadImage(
BITMAP dane_bitmapy;
GetObject(bitmapa,sizeof(BITMAP),&dane_bitmapy);
...
To wiemy już teoretycznie jak pracuje się z bitmapami, teraz spójrzmy jak wygląda to w kodzie, program, który otwora bitmapę z pliku i wyświetla ją na oknie:
HBITMAP bitmapa;
BITMAP info_bitmapy;
LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
INT WINAPI WinMain(HINSTANCE hInst,HINSTANCE,LPSTR lStart,INT nShow)
{
bitmapa=(HBITMAP)LoadImage(
if(bitmapa==
{
MessageBox(
return
}
GetObject(bitmapa,sizeof(BITMAP),&info_bitmapy);
WNDCLASSEX wc;
wc.hInstance=hInst;
wc.lpszClassName=
wc.lpfnWndProc=WndProc;
wc.style=CS_DBLCLKS;
wc.cbSize=sizeof(WNDCLASSEX);
wc.hIcon=LoadIcon(
wc.hIconSm=LoadIcon(
wc.hCursor=LoadCursor(
wc.lpszMenuName=
wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
wc.cbWndExtra=
wc.cbClsExtra=
if(RegisterClassEx(&wc)==
HWND Okno=CreateWindowEx(
MSG msgs;
ShowWindow(Okno,nShow);
UpdateWindow(Okno);
while(GetMessage(&msgs,
{
TranslateMessage(&msgs);
DispatchMessage(&msgs);
}
DeleteObject(bitmapa);
return msgs.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd,UINT msg,WPARAM wPar,LPARAM lPar)
{
HDC hdc,hdcBitmapy;
PAINTSTRUCT ps;
switch(msg)
{
case WM_PAINT:
hdc=BeginPaint(hwnd,&ps);
hdcBitmapy=CreateCompatibleDC(hdc);
bitmapa=(HBITMAP)SelectObject(hdcBitmapy,bitmapa);
//tutaj możemy przeprowadzać jakieś operacje graficzne z naszym kontekstem bitmapy
//uwaga te operacje będą przeprowadzane co każde odświeżenie okna
//wyświetlenie bitmapy na oknie, operacja przekopiowania
BitBlt(hdc,
bitmapa=(HBITMAP)SelectObject(hdcBitmapy,bitmapa);
DeleteDC(hdcBitmapy);
EndPaint(hwnd,&ps);
break;
case WM_DESTROY:
PostQuitMessage(
break;
default:
return DefWindowProc(hwnd,msg,wPar,lPar);
}
return
}
Aby ten program poprawnie działał, potrzebuje w swoim folderze bitmapy o nazwie "bitmapa.bmp". Jeżeli będzie poprawnie otwierany przez nasz program, powinniśmy zobaczyć ta bitmapę na naszym oknie.
Tutaj niestety muszę wielu zmartwić, ale GDI samo w sobie nie obsługuje tych formatów, trzeba użyć jakiejś innej zewnętrznej biblioteki lub samemu obsłużyć te formaty plików. Być może na ten temat kiedyś zrobię lekcję.