Rozdział 10.
Bitmapy, palety, DIB-y oraz podwójne buforowanie
W tym rozdziale:
Pamięć bitmapy i pamięć obrazu
Obiekty CBitmap
Ładowanie danych bitmapy z pliku zasobów
Wyświetlanie obiektów CBitmap
Operacje rastrowe z użyciem obiektów CBitmap
Program BlitDemo
Palety i kolor
Ładowanie, zapisywanie i wyświetlanie bitmap niezależnych od sprzętu
Redukowanie migotania ekranu przez zastosowanie podwójnego buforowania
W tym rozdziale dowiesz się, jak w swojej aplikacji możesz wykorzystać różne rodzaje bitmap. Będziesz mógł wyświetlać bitmapy w różnych miejscach i używając sposobów, których wcześniej unikałeś, gdyż wydawały Ci się zbyt trudne. W tym rozdziale rozwiążemy te problemy. Już za chwilę będziesz mógł bez problemu wyświetlać obrazki.
Pamięć bitmapy i pamięć obrazu
W sercu systemu graficznego komputera leży pamięć obrazu. Ta pamięć zawiera dane reprezentujące wzory wyświetlane na monitorze. Za każdym razem gdy przesuwasz wskaźnik myszy, zmienia się niewielka część pamięci obrazu, dzięki czemu na ekranie widzisz przesuwający się kształt wskaźnika myszy. Każda - bez wyjątku - operacja graficzna przeprowadzana przez GDI sprowadza się do przeprowadzenia odpowiednich zmian w pamięci obrazu.
Bitmapy reprezentują obszary pamięci RAM o strukturze podobnej do pamięci obrazu. Są podobne, ponieważ każda bitmapa reprezentuje adres pamięci przechowującej dane. Różnica polega na tym, że pamięć bitmapy nie jest widoczna, a pamięć obrazu jest. Pamięć RAM zajmowana przez bitmapę reprezentuje prostokątny obrazek. W Windows API uchwyt do pamięci zaalokowanej dla bitmapy jest znany jako HBITMAP. W tej pamięci znajdują się dane, które, po przeniesieniu do pamięci obrazu, staną się obrazkiem wyświetlonym na ekranie. Weźmy na przykład ikony pulpitu, które są ładowane z dysku do pamięci. Ta pamięć jest następnie przenoszona pod odpowiedni adres do pamięci obrazu tak, że gdy karta graficzna zinterpretuje pamięć obrazu, na ekranie pojawi się obraz ikony.
Bitmapy to obiekty graficzne różniące się od rysowanych obiektów graficznych takich jak linie czy elipsy. Linie i elipsy są obliczane matematycznie w celu ustawienia tych pikseli pamięci obrazu, które stworzą kształt zadanego obiektu. Bitmapy nie są obliczane. Wzór już istnieje w pamięci bitmapy, więc wyświetlenie go sprowadza się po prostu do odpowiedniego skopiowania bloku pamięci RAM do pamięci obrazu. W związku z tym bitmapy doskonale nadają się do zachowania rezultatu serii operacji graficznych tworzących kompletny obraz. Zastanów się, jak wielu operacji graficznych wymagałoby narysowanie na ekranie fotografii - być może nawet wielu tysięcy. Korzystając z bitmapy, złożony obraz fotograficzny może w całości rezydować w pamięci i w odpowiedniej chwili, pojedynczą operacją, zostać skopiowany do odpowiedniego obszaru pamięci obrazu.
W tym rozdziale omawiamy dwa rodzaje obiektów bitmap. Pierwszy z nich jest reprezentowany przez klasę MFC CBitmap. Bitmapy tego typu są nazywane bitmapami zależnymi od urządzenia (ang. device-dependent bitmap), gdyż mogą być poprawnie narysowane tylko na urządzeniu, które ma konfigurację zgodną z konfiguracją bitmapy. Na przykład, zależna od urządzenia bitmapa o 24 bitach na piksel może być wyświetlona tylko na karcie graficznej pracującej w trybie 24 bitów na piksel. Jedynym wyjątkiem od tej reguły są bitmapy monochromatyczne o jednym bitplanie z głębokością koloru jednego piksela. Takie bitmapy mogą być rysowane na urządzeniach o dowolnej konfiguracji.
Zanim zaczniemy dyskusję, powinniśmy uściślić parę terminów. W tym rozdziale jako bitmapę będziemy traktować obiekt GDI Windows znany jako HBITMAP. Klasa MFC reprezentująca ten obiekt to klasa CBitmap. Paleta logiczna jest także obiektem GDI, a w tym rozdziale będziemy nazywać japo prostu paletą. Klasa MFC reprezentująca paletę to klasa cpalette. Bitmapa niezależna od urządzenia, na przykład zapisana w pliku .BMP, będzie nazywana jako bitmapa DIB (ang. device-independent bitmap). Bitmap DIB nie da się znaleźć w dokumentacji Windows API ani MFC jako oddzielnych obiektów. Zamiast tego, programy w tym rozdziale będą ładować je jako obiekty pamięci dostępne poprzez uchwyt HGLOBAL. Będą jednak wciąż nazywane bitmapami DIB, dzięki czemu będziesz wiedział, co znajduje się w takiej pamięci. W dalszej części rozdziału stworzymy klasę reprezentującą bitmapy DIB, której nadamy nazwę coib.
Bitplany, bity na piksel oraz głębokość koloru
Dawno temu, gdy standardem była karta CGA, do wyboru były tylko cztery kolory: czarny, błękitny, fioletowy i biały. (Rzadko używana odmiana posiadała kolory czarny, żółty, zielony i czerwony). Karta CGA przechowywała kolor każdego piksela w dwóch kolejnych bitach. Każdy bajt pamięci obrazu posiadał miejsce na cztery piksele ekranu. Co ciekawe, dane były przechowywane w dwóch bankach pamięci. Pierwszy z nich zawierał dane dla linii nieparzystych, a drugi dla linii parzystych.
W momencie pojawienia się monitorów EGA i VGA, zwiększyła się także ilość dostępnych kolorów. W EGA/YGA można było korzystać z czterech bitów koloru: czerwonego, zielonego i niebieskiego oraz bitu powodującego zwiększenie jasności koloru. Przez połączenie tych bitów można było otrzymać szesnaście różnych kolorów (jasnozielony, ciemnozielony, jasnoczerwony, ciemnoczerwony itd.). Te cztery zestawy bitów były zwane bitplanami. W przypadku kart EGA/VGA każdy z bitplanów znajdował się w osobnym banku pamięci. Na przykład dla trybu EGA 320x200 wszystkie bity koloru czerwonego zajmowały osobny obszar pamięci o rozmiarze 8 000 bajtów.
System MCGA posiadał już osiem bitów informacji dla każdego piksela. Ponieważ koncepcja bitplanów nie najlepiej nadaje się dla tego rodzaju obrazu, osiem bitów informacji o kolorze zaczęto nazywać bitami na piksel. Pamięć obrazu karty MCGA posiadała po jednym bajcie dla każdego piksela na ekranie. Aby otrzymać wartość RGB (termin RGB opiszemy za chwilę) reprezentowaną przez dany piksel, korzystano ze specjalnej tablicy wyszukiwania, zawierającej opis palety kolorów. Palety MCGA posiadały jedynie 256 pozycji, więc karty MCGA mogły wyświetlić jednocześnie tylko 256 różnych kolorów.
Wszystkie karty graficzne obsługują starsze tryby, takie jak CGA czy EGA, lecz większość z nich obsługuje także nowsze tryby, w których w pamięci obrazu są przechowywane pełne wartości RGB. Najbardziej popularnym trybem jest 24-bitowy tryb Truecolor. W tym trybie każdy piksel ekranu jest reprezentowany przez trzy kolejne bajty. Każdy z tych bajtów określa wartość składową barw czerwonej, zielonej oraz niebieskiej. Dwa inne popularne tryby przechowujące wartości RGB to tryby z 16-i 32-bitami na piksel.
Aby wyznaczyć bieżącą głębokość koloru (ilość bitów składających się na piksel), musisz sprawdzić zarówno ilość bitów na piksel, jak i ilość bitplanów. W celu wyznaczenia ilości bitów na piksel możesz wywołać funkcję API GetoeviceCaps o ze stałą BITSPIKEL. Aby wyznaczyć ilość bitplanów, wywołaj tę samą funkcję ze stałą PLANES.
Prostym sposobem wyznaczenia głębokości koloru jest odczytanie obu wartości i przemnożenie ich przez siebie. Obliczenia będą poprawne, gdyż systemy EGA/YGA zwracają jeden jako ilość bitów na piksel, zaś systemy truecolor zwracają jeden jako ilość bitplanów.
Dyskusja o bitmapach nie byłaby kompletna bez omówienia palety. W tym rozdziale wszystkie operacje na palecie będą odbywać się z udziałem obiektu MFC cpalette. W przypadku kart graficznych pracujących w trybach graficznych o ośmiu lub mniej bitach koloru na piksel, efektywne użycie palet jest zasadniczą sprawą decydującą o wyglądzie bitmapy.
Jak już wspomnieliśmy, drugim rodzajem bitmap opisywanym w tym rozdziale są bitmapy niezależne od urządzenia (DIB, device-independent bitmap). Te bitmapy nie są reprezentowane przez żadną klasę MFC. Jednak pod koniec rozdziału stworzymy klasę podobną do klasy CBitmap, ułatwiającą operowanie bitmapami DIB. Zaletą bitmap DIB nad zwykłymi bitmapami jest to, że do poprawnego wykonania operacji graficznej nie jest potrzebna zgodność konfiguracji bitmapy z konfiguracją pamięci obrazu. Przy rysowaniu bitmapy na ekranie bierze się pod uwagę konfigurację zarówno bitmapy, jak i pamięci obrazu. Na przykład, 24-bitowa bitmapa DIB może być poprawnie wyświetlona w systemie z 8-, 16-, 24- oraz 32-bitami na piksel obrazu. Gdy wystąpi niezgodność głębokości kolorów, stosowany jest algorytm zapewniający jak najlepsze dopasowanie kolorów. Na przykład, 24-bitowa bitmapa DIB rysowana w systemie z 8-bitami na piksel musi zostać zredukowana do zestawu 256 kolorów dostępnych w tym systemie. Podczas wyświetlania bitmapy DIB na ekranie konwersja kolorów następuje dopiero podczas operacji graficznej i nie wpływa na oryginalne dane bitmapy DIB w pamięci. Wadą jest znaczne obniżenie wydajności. Z tego powodu, jeśli wydajność ma duże znaczenie, nie powinno się stosować bitmap DIB. Zamiast tego zaleca się stosowanie wtedy bitmap zależnych od urządzenia.
Ostatnim zagadnieniem, o którym opowiemy w tym rozdziale, jest zastosowanie obiektów CBitmap w celu zredukowania lub wyeliminowania migotania ekranu podczas odświeżania zawartości okna.
Tworzenie obiektów CBitmap
Po zainicjowaniu obiekty CBitmap są puste. Aby można było skorzystać z reprezentowanej przez nie bitmapy, konieczne jest jej utworzenie. Istnieje kilka sposobów utworzenia bitmapy w klasie CBitmap. Pierwszy z nich wykorzystuje funkcję CreateBitmap (), zadeklarowaną następująco:
BOOL CBitmap::CreateBitmap( int nWidth, int nHeight,
UINT nPlanes, UINT nBitcount, const void* 1IpBits );
Parametry nwidth oraz nHeight określają szerokość i wysokość bitmapy w pikselach. Parametr nPlanes określa ilość bitplanów bitmapy i prawie zawsze przyjmuje wartość 1. Parametr nBitcount określa ilość bitów na piksel. Ostatni argument, 1pBits, umożliwia zainicjowanie bitmapy wzorcem bitowym. Wzorzec jest kopiowany do pamięci bitmapy, jednak do obowiązku programisty należy utrzymanie i późniejsze usunięcie tego wzorca. Zwykle podczas tworzenia bitmap nie stosuje się wzorca bitowego i wtedy parametr 1pBits ma wartość NULL. Bez zainicjowania wzorca bitmapy zawiera ona przypadkowy zbiór danych, stanowiący zwykle przypadkową mieszaninę kolorów.
Poniższy przykład tworzy bitmapę o szerokości 50 i wysokości 60 pikseli, z jednym bitplanem, 24 bitami na piksel i bez inicjowania wzorca bitowego:
CBitmap Bitmap;
Bitmap.CreateBitmap( 50, 60, 1, 24, NULL ) ;
W wielu przypadkach konieczne jest sprawdzenie już istniejącej bitmapy. Musisz wtedy użyć funkcji Getobject () w celu otrzymania struktury BITMAP wypełnionej informacjami o bitmapie. Podczas odczytywania informacji o obiekcie CBitmap, składowa bmBits będzie miała wartość NULL, gdyż nie możesz otrzymać bezpośredniego wskaźnika do pamięci bitmapy. Jeśli jednak użyjesz wskaźnika do struktury BITMAP w wywołaniu funkcji CBitmap: : CreateBitmapIndirect (), W polu bmBits możesz przekazać wskaźnik do inicjalizacyjnego wzorca bitowego. Składowa bmType zawsze ma wartość zero. Poniżej, po definicji struktury BITMAP, znajduje się przykład wypełnienia jej informacjami za pomocą funkcji Getobject ():
typedef struct tagBITMAP { /* bm */
int bmType;
int bmWidth;
int bmHeight;
int bmWidthBytes;
BYTE bmPlanes;
BYTE bmBitsPixel;
LPVOID bmBits; } BITMAP;
// Bitmap to obiekt klasy CBitmap
BITMAP bm;
Bitmap.GetObject( sizeoff BITMAP ), &bm );
Innym sposobem utworzenia bitmapy jest użycie funkcji CreateBitmapIndirect (). Aby to zrobić, należy wcześniej wypełnić strukturę BITMAP wartościami opisującymi tworzoną bitmapę. Poniżej znajduje się przykład użycia funkcji CreateBitmapIndirect () w celu utworzenia bitmapy o szerokości 100 pikseli, wysokości 200, jednym bitplanie i 16 bitach głębokości koloru:
BITMAP bm; bmType = O bmWidth = 100; bmHeight = 200; bmWidthBytes = 200; bmPlanes = 1; bmBitsPixel = 16; LPVOID bmBits;
CBitmap Bitmap;
Bitmap.Createbitmaplndirect( &bm ) ;
Choć trójka kolorów RGB jest powszechnie używana, jednak kolejność kolorów w pamięci obrazu jest odwrócona. Innymi słowy, jeśli na każdy piksel obrazu przypadaj ą trzy bajty, pierwszy z nich określa składową niebieską, drugi zieloną, zaś trzeci czerwoną.
W wielu przypadkach zechcesz stworzyć bitmapę zgodną po prostu z bieżącym trybem graficznym ekranu. Możesz to uczynić, bez konieczności podawania ilości bitplanów i bitów na piksel, wywołując funkcję CreateCompatibieBitmap (). Ta funkcja otrzymuje wskaźnik do kontekstu urządzenia i na podstawie tego kontekstu sama określa wymaganą konfigurację bitmapy. Oto przykład użycia tego wywołania w funkcji OnDraw ():
void CBitmapView::OnDraw(CDC* pDC)
CBitmapDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc);
CBitmap Bitmap;
Bitmap.CreateCompatibieBitmap( pDC);
Ładowanie i ustawianie zawartości bitmapy
Zanim użyjesz bitmapy, z pewnością zechcesz wypełnić ją sensownym obrazkiem. Tuż po utworzeniu bitmapy jej pamięć zawiera przypadkowe dane. Jeśli wyświetlisz taką bitmapę na ekranie, ujrzysz obraz podobny do ekranu telewizora z odłączoną anteną. Istnieją dwa różne sposoby wypełnienia bitmapy zawartością. Jeden z nich polega na użyciu funkcji SetBitmapBits (), a drugi na użyciu funkcji LoadBitmap ().
Funkcja SetBitmapBits () kopiuje zawartość bufora w pamięci do pamięci bitmapy. Obszar źródłowy musi zostać utworzony, a następnie zniszczony przez program wywołujący. Istnieją dwa powody, dla których mógłbyś kopiować zawartość bitmapy z bufora. Po pierwsze, możesz stworzyć obraz algorytmicznie w pamięci, a dopiero potem przenieść go do bitmapy. Po drugie, możesz najpierw załadować dane z dysku do bufora. Poniższy przykład tworzy 24-bitową bitmapę, tworzy bufor w pamięci zawierający dane reprezentujące czerwony prostokąt, po czym kopiuje zawartość bufora do bitmapy:
// Zainicjowanie obiektu CBitmap i utworzenie bitmapy
CBitmap Bitmap;
Bitmap.CreateBitmap( 50, 50, l, 24, NULL) ;
// Pobranie rozmiarów bitmapy i stworzenie bufora // o odpowiedniej wielkości BITMAP bm;
Bitmap.GetObject( sizeoft BITMAP ), &bm ); unsigned char pData = new unsigned char [bm.bmHeight * bmWidthBytes];
// Zainicjowanie wartości w buforze, tak by odpowiadały // jednolitej czerwieni for( int y = 0; y < bm.bmHeight; y++){ for( int x = 0; x < bm.bmWidth; x++){
pData[x*3+y*bm.bmWidthBytes] = 0;
pData[x*3+l+y*bm.bmWidthBytes] = 0;
pData[x*3+2+y*bm.bmWidthBytes] = 255;
}
}
// Wypełnienie bitmapy "czerwonymi" danymi
Bitmap.SetBitmapBits( bm.bmHeight * bm.bmWidthBytes, pData );
// Usunięcie bufora danych delete [] pData;
W przypadku bitmap 16-kolorowych (tj. o czterech bitach na piksel), istnieje jeszcze jedna możliwość. Możesz umieścić bitmapę jako zasób programu a następnie za pomocą funkcji LoadBitmap () przenieść dane do bitmapy. Korzyścią płynącą z tego rozwiązania jest brak konieczności użycia funkcji CreateBitmap () ani żadnej innej funkcji do tworzenia bitmap. Inną dobrą cechą jest to, że LoadBitmap () tworzy bitmapę zgodną z aktualnym trybem graficznym, bez względu na format bitmapy w pliku zasobów. Oto przykład załadowania bitmapy o identyfikatorze zasobu IDB_BITMAPI do obiektu klasy
CBitmap:
CBitmap Bitmap; Bitmap.LoadBitmap(IDB BITMAP1)
Jedną z funkcji, które także mogą Ci się przydać, jest LoadMappedBitmap (). Pobiera ona ten sam argument co funkcja LoadBitmap (), z tym że kolory ładowanej bitmapy są konwertowane zgodnie z kolorami systemu. Na przykład kolor czarny jest zamieniany na kolor tekstu na przyciskach czy kolor szary na kolor cieni przycisków.
Gdy posiadasz obiekt CBitmap z odpowiednią zawartością bitmapy, jesteś gotów do wyrysowania go na ekranie. W większości przypadków użyjesz do tego funkcji BitBlt (). Ta funkcja jest szybka i jeśli chodzi o transfer danych pomiędzy bitmapą a pamięcią obrazu, jest praktycznie nie do pobicia. Microsoft z pewnością położył duży nacisk na szybki algorytm operacji blitowania - i biorąc pod uwagę wagę szybkości tej operacji w środowisku graficznym, była to mądra decyzja. Blitting to ogólne określenie odnoszące się do operacji kopiowania danych bitmapy w docelowe miejsce (do pamięci obrazu lub do innej bitmapy w pamięci RAM).
Pierwsze, co musisz zrozumieć, to fakt, że z technicznego punktu widzenia funkcja BitBlt () kopiuje dane z jednego kontekstu urządzenia do innego. Oznacza to, że zanim bitmapą będzie mogła zostać przerzucona w inne miejsce, konieczne jest utworzenie odpowiedniego kontekstu urządzenia. Oprócz tego, przed operacją przerzucenia bitmapą musi zostać wybrana w kontekście urządzenia. Funkcja BitBlt () jest składową klasy CDC i jest zdefiniowana następująco:
BOOL CDC::BitBlt(int x, int y, int nWidth, int nHeight,
CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop );
Parametry x i y to współrzędne, w których bitmapą zostanie narysowana. Parametry nWidth oraz nHeight określają szerokość i wysokość docelowego prostokąta i muszą odpowiadać szerokości i wysokości bitmapy. Parametr pSrcDc jest wskaźnikiem do źródłowego kontekstu urządzenia - aby operacja się powiodła, wcześniej w tym kontekście
musi zostać wybrana źródłowa bitmapa. Parametry xSrc oraz ySrc to lewy górny róg kopiowanej bitmapy źródłowej. Zmieniając parametry xSrc, ySrc oraz nwidth i nHeight możesz rysować na ekranie fragmenty bitmapy. Parametr dwRop określa rodzaj wykonywanej operacji rastrowej. Bardziej szczegółowa dyskusja na temat kodów operacji rastrowych znajduje się w sekcji "Operacje rastrowe" w dalszej części rozdziału.
Poniższy przykład, za pomocą funkcji LoadBitmap (), ładuje bitmapę w konstruktorze klasy widoku. Następnie w funkcji OnDrawO wyświetla bitmapę na ekranie. I jeszcze jedna ważna uwaga: wybierając bitmapę w nowo utworzonym kontekście urządzenia, otrzymujesz wskaźnik do starej bitmapy. Dzięki temu po narysowaniu bitmapy możesz wybrać w kontekście urządzenia starą bitmapę.
CMyYiew::CMyView()
{
m_Bitmap.LoadBitmap( IDC_BITMAP1 ); }
CMyView::OnDraw(CDC *pDC)
{
CBitmapDoc *pDoc = GetDocument(); ASSERT_VALID(pDoc);
CDC MemDC;
MemDC.CreateCompatibleDC( pDC) ;
CBitmap *p01dBitmap = MemDC.SelectObject( &m_Bitmap );
BITMAP bm;
m_Bitmap.GetObject( sizeof( BITMAP ), &bm );
pDC->BitBlt(10, 10, bm.bmWidth, bm.bmHeight, SMemDC,
O, O, SRCCOPY ); MemDC.SelectObject( pOldBitmap );
Rysowanie bitmap
Proces wyświetlania obiektu CBitmap opisują poniższe kroki:
1. Utwórz kontekst urządzenia dla bitmapy, zgodny z kontekstem urządzenia graficznego.
2. W nowo utworzonym kontekście wybierz obiekt CBitmap; zachowaj wskaźnik do poprzednio wybranej bitmapy.
3. Za pomocą funkcji Getobject (} wypełnij strukturę BITMAP, tak aby poznać wymiary bitmapy. (Pomiń ten krok, jeśli już wcześniej znasz te wymiary).
4. Wykonaj funkcję Bit Bit ().
5. Wybierz starą bitmapę w nowo utworzonym kontekście urządzenia.
W omawianych przykładach nie braliśmy pod uwagę trybu odwzorowania (opis trybów odwzorowania znajdziesz w rozdziale 9.) oraz różnicy pomiędzy współrzędnymi logicznymi a współrzędnymi urządzenia. Większość aplikacji, które będziesz tworzył, nie będzie zmieniać trybu odwzorowania i będzie korzystać ze współrzędnych logicznych odpowiadających współrzędnym urządzenia.
Jeśli jednak zdarzy Ci się coś innego, musisz zastosować dodatkowy kod. Poniżej podajemy przykład funkcji DrawBitmap (), która przy rysowaniu bitmapy bierze pod uwagę te dodatkowe czynniki:
void DrawBitmap{ int x, int y, CDC *pDC, CBitmap *pBitmap ) {
BITMAP bm;
pBitmap->GetObject( sizeof( BITMAP), &bm );
CPoint size(bm.bmWidth, bm.bmHeight ); pDC->DPtoLP( &size );
CPoint org (O, O ); pDC->DPtoLP( &org );
CDC MemDC;
MemDC.CreateCompatibleDC( pDC);
CBitmap *p01dBitmap = MemDC.SelectObject( pBitmap ); MemDC.SetMapMode( pDC-> GetMapMode() );
pDC->BitBlt ( x, y, size.x, size.y, SMemDC, org.x, org.y, SRCCOPY ) ;
MemDC.SelectObject( pOldBitmap );
}
Tworzenie i wyświetlanie bitmap
Tworząc tę aplikację nauczysz się tworzyć i wyświetlać bitmapy. Z siedmiu bitmap jedynie trzy zostaną wyświetlone na ekranie. Pozostałe cztery nie wyświetlą się, ponieważ nie pasują do konfiguracji trybu graficznego Twojej karty. Bitmapy monochromatyczne ( l -bitowe) mogą być wyświetlane we wszystkich trybach graficznych.
1. Utwórz aplikację jednodokumantową.
2. Zadeklaruj w klasie widoku tablicę obiektów CBitmap.
3. Zaimportuj bitmapę jako obiekt zasobów i nadaj jej identyfikator IDB_BITMAPI.
4. W konstruktorze klasy widoku zainicjalizuj siedem bitmap:
static int nBits[] = { l, 4, 8, 16, 24, 32 } ; for (int i = 0; i < 6; i++)
m_Bitmap[i] . CreateBitmap ( 40, 40, l, nBits[i], NULL ) ; m_Bitmap[6] .LoadBitmap( IDB_BITMAP1 ) ;
5. W funkcji onDraw ( ) narysuj bitmapy:
for (int i = 0; i < 7; i++) { CDC MemDC;
CBitmap *p01dBitmap; MemDC.CreateCompatibleDC ( pDC ) ; pOldBitmap = MemDC . SelectObject ( &m_Bitmap [i] ) ;
pDC->BitBlt (20 + i * 50, 30, 40, 40, &MemDC, O, O, SRCCOPY ) ; MemDC. SelectObject ( pOldBitmap );
Czasem konieczne jest skopiowanie pamięci obrazu do bitmapy. Ponieważ BitBlt() nie dba o to, która pamięć jest źródłowa, a która docelowa, zwykle nie sprawia to żadnego problemu. Wszystko co musisz zrobić w celu skopiowania obrazka z ekranu do bitmapy, to zamienić rolę obu kontekstów urządzeń.
Na przykład, w poprzednich przykładach wskaźnik do kontekstu urządzenia poć zawsze wskazywał kontekst docelowy, zaś wywołanie funkcji miało postać poc->BitBit o. Aby wskazać drugi kontekst urządzenia, do funkcji Bit Bit () był przekazywany adres kontekstu MemDC. Aby zamienić kierunek rysowania, docelowym kontekstem uczyń MemDC i wywołaj funkcję MemDC->BitBit (). Jako argument funkcji przekaż kontekst wskazywany przez poć. Jako przykład podajemy funkcję GetimageO, odczytującą fragment ekranu do bitmapy:
void Getlmage( int x, int y, CDC *pDC, CBitmap *pBitmap ) {
BITMAP bm;
pBitmap->GetObject( sizeof( BITMAP), &bm );
CDC MemDC;
MemDC.CreateCompatibleDC( pDC) ;
CBitmap *p01dBitmap = MemDC.SelectObject( pBitmap MemDC.BitBlt( O, O, bm.bmWidth, bm.bmHeight, pDC, MemDC.SelectObject( pOldBitmap };
}
Operacje rastrowe
Ostatni argument funkcji BitBlt () określa, w jaki sposób bitmapa źródłowa łączy się z bitmapa docelową. W większości przypadków zechcesz, by bitmapa wyglądała na ekranie dokładnie tak samo jak w pamięci. W przeciwnym przypadku zwykła operacja kopiowania nie wystarczy. Jednym z przykładów może być wyświetlanie bitmapy tak, aby jeden z kolorów został potraktowany jak przezroczysty. Jeśli w bitmapie wystąpi taki przezroczysty kolor, zostanie całkowicie zignorowany, zaś odpowiadające mu docelowe piksele ekranu pozostaną nie zmienione.
Aby zastosować tę technikę, konieczne są dwa osobne wywołania funkcji BitBlt u . W pierwszej części procesu tworzymy maskę opartą na źródłowej bitmapie. Wyszukujemy w bitmapie piksele o przezroczystym kolorze i na ich podstawie tworzymy maskę. Następnie maskujemy obszar docelowy w taki sposób, aby wyeliminować wszystkie piksele docelowe, dla których będą rysowane nieprzezroczyste piksele źródłowe. Ta sama maska jest używana następnie do zamaskowania bitmapy źródłowej i usunięcia niechcianych pikseli w kolorze przezroczystym. Następnie z użyciem operacji OR (lub XOR) bitmapa źródłowa jest kopiowana do docelowego obszaru. Następnie oryginalna bitmapa musi zostać przywrócona do oryginalnego stanu, gdyż w przeciwnym razie kolor przezroczysty byłby już nieobecny i następna operacja rysowania z użyciem koloru przezroczystego nie powiodłaby się.
Szybszym sposobem przeprowadzenia tej operacji podwójnego maskowania jest utworzenie maski już zawczasu. W ten sposób nie będzie trzeba jej tworzyć za każdym razem, gdy będzie potrzebna. Jeśli liczy się szybkość działania programu, musisz poważnie rozważyć to rozwiązanie. Program BlitDemo przy rysowaniu maskowanej muchy wykorzystuje właśnie tę technikę. Gdy dobrze przyjrzysz się temu programowi, z pewnością zrozumiesz, na czym to polega.
Kolejnym przykładem, w którym mógłbyś skorzystać z innych operacji rastrowych, jest prosta animacja. Popularną techniką przenoszenia bitmap w inne miejsce ekranu jest XOR-owanie ich przed przemieszczeniem w nowe miejsce. XOR-owanie obrazu z samym sobą powoduje całkowite jego usunięcie. Dzieje się tak, ponieważ podczas pierwszego XOR-owania obraz źródłowy łączy się z obrazem już istniejącym na ekranie. W każdym miejscu, gdzie bitmapa źródłowa ma ustawiony bit, bit odpowiedniego punktu ekranu zmienia się na przeciwny. Na przykład, jeśli bit źródłowy jest ustawiony (równy 1), zaś bit docelowy nie jest ustawiony (równy 0), w rezultacie otrzymujemy jedynkę. Jeśli bitmapę trzeba przesunąć, XOR-owanie tej samej bitmapy w tym samym miejscu powoduje zmianę stanu tych samych bitów, czyli przywrócenie ich do stanu oryginalnego. W tym momencie bitmapa może zostać wyświetlona w innym miejscu ekranu, także z użyciem operacji XOR. Kody operacji rastrowych zebrano w tabeli 10.1.
Tabela 10.1. Operacje rastrowe i ich definicje Kod operacji rastrowej Opis
BLACKNESS-Wynikiem jest czerń. DSTINYERT-Tworzy negatyw bitmapy docelowej.
MERGECOPY-Łączy deseń i bitmapę źródłową, używając operacji AND.
MERGEPAINT-Łączy negatyw bitmapy źródłowej z bitmapa docelową, używając operacji OR.
NOTSRCCOPY-Tworzy negatyw bitmapy źródłowej.
NOTSRCERASE-Tworzy negatyw rezultatu połączenia bitmapy źródłowej i docelowej, połączonych z użyciem operacji OR.
PATCOPY-Kopiuje deseń do bitmapy docelowej.
PATINVERT-Łączy bitmapę docelową z deseniem, używając operacji XOR.
PATPAINT-Łączy negatyw bitmapy źródłowej z deseniem, używając operacji OR. Wynik tej operacji łączy z bitmapa docelową używając operacji OR.
SRCAND-Łączy bitmapę źródłową z bitmapa docelową używając operacji AND.
SRCCOPY-Kopiuje bitmapę źródłową do bitmapy docelowej.
SRCERASE-Tworzy negatyw bitmapy docelowej i łączy wynik z bitmapa źródłową używając do połączenia operacji AND
SRCINVERT-Łączy bitmapę źródłową z docelową, używając operacji XOR.
SRCPAINT-Łączy bitmapę źródłową z docelową, używając operacji OR.
WHITENESS-Wynikiem jest biel.
.
Jeśli potrzebujesz funkcji rysującej na ekranie bitmapę z pominięciem jednego z kolorów, który ma być traktowany jako przezroczysty, z pewnością przyda Ci się funkcja DrawTransparent () przedstawiona na listingu 10.1. Możesz ją dodać do swojego programu lub, jeszcze lepiej, wyprowadzić własną klasę z klasy CBitmap.
Kod źródłowy funkcji DrawTransparent () znajduje się w pliku Transparent.c w kartotece dla rozdziału 10. na dołączonej do książki płytce CD-ROM.
Listing 10.1. Funkcja DrawTransparent()________________________________
void DrawTransparent(int x, int y, CDC *pDC, CBitmap *pBitmap, COLORREF Color )
BITMAP bm;
pBitmap->GetObject( sizeof( BITMAP ), &bm );
CDC ImageDC;
ImageDC.CreateCompatibleDC( pDC );
CBitmap *p01dImageBitmap = ImageDC.SelectObject{ pBitmap };
CDC MaskDC; MaskDC.CreateCompatibleDC( pDC );
CBitmap MaskBitmap;
MaskBitmap.CreateBitmap( bm.bmWidth, bm.bmHeight, l, l, NULL );
CBitmap *p01dMaskBitmap = MaskDC.SelectObject( &MaskBitmap );
ImageDC.SetBkColor( Color );
MaskDC.BitBlt (O, O, bm.bmWidth, bm.bmHeight, &ImageDC,
O, O, SRCCOPY );
CDC OrDC; OrDC.CreateCompatibleDC( pDC );
CBitmap OrBitmap;
OrBitmap.CreateCompatibleBitmap(&ImageDC, bm.bmWidth,
bm.bmHeight ); CBitmap *p01dOrBitmap = OrDC.SelectObject( &OrBitmap };
OrDC.BitBlt(O, O, bm.bmWidth, bm.bmHeight, &ImageDC,
O, O, SRCCOPY );
OrDC.BitBlt(O, O, bm.bmWidth, bm.bmHeight, &MaskDC,
O, O, 0x220326 );
CDC TempDC; TempDC.CreateCompatibleDC( pDC );
CBitmap TempBitmap;
TempBitmap.CreateCompatibleBitmap(&ImageDC, bm.bmWidth,
bm.bmHeight ); CBitmap *pO1dTempBitmap = TempDC.SelectObject( &TempBitmap };
TempDC.BitBlt( O, O, bm.bmWidth, bm.bmHeight, pDC,
x, y, SRCCOPY );
TempDC.BitBlt( O, O, bm.bmWidth, bm.bmHeight, &MaskDC,
O, O, SRCAND );
TempDC.BitBlt( O, O, bm.bmWidth, bm.bmHeight, &OrDC,
O, O, SRCPAINT );
pDC->BitBlt( x, y, bm.bmWidth, bm.bmHeight, &TempDC, O, O, SRCCOPY );
TempDC.SelectObject( pOldTempBitmap ); OrDC.SelectObject( pOldOrBitmap ); MaskDC'. SelectObject ( pOldMaskBitmap ); ImageDC.SelectObject( pOldlmageBitmap);
Program BlitDemo
Program BlitDemo zawarty na płytce CD-ROM ilustruje wykonywanie popularnych operacji z użyciem obiektów CBitmap. Same bitmapy są zawarte jako obiekty zasobów i ładowane przy użyciu funkcji składowej LoadBitmap ().
Program rozpoczyna działanie od wyświetlenia jednego z trzech obrazków unoszących się bąbelków, tak jak pokazano na rysunku 10.1. Kliknięcie lewym przyciskiem myszy w obszarze roboczym okna powoduje wyświetlenie następnego obrazka w sekwencji. Po narysowaniu trzeciego obrazka sekwencja zaczyna się od początku.
Otwierając menu Operacje, możesz wybrać jedną z czterech różnych sekwencji obrazków. Drugą sekwencjąjest zestaw czterech obrazków przedstawiających człowieczka w drezynie. Trzecia i czwarta sekwencja obrazują lot muchy. Po kliknięciu lewym przyciskiem myszy mucha przemieszcza się w dół, tak jakby leciała.
Muchy są rysowana przy użyciu dwóch różnych technik, wybieranych w menu. Trzecia pozycja menu powoduje rysowanie muchy jako połączenia operacji AND z operacją OR. Na początku programu ładowane są maski dla każdego obrazka muchy; są one wykorzystane do wykonania operacji AND.
Czwarta pozycja menu powoduje rysowanie muchy z użyciem operacji XOR. Zwróć uwagę, że kolor muchy zależy od koloru obszaru roboczego okna. Fragmenty pliku BlitDemoView.cpp zostały przedstawione na listingu 10.2.
BlitDemo
Położenie na płytce: Rozdz10\BlitDemo
Nazwa programu: BlitDemo.exe
Moduły kodu źródłowego w tekście: BlitDemoVie\v.cpp
Listing 10.2. Fragmenty kodu źródłowego z pliku BlitDemoView.cpp
// BlitDemoView.cpp:implementatlon of CBlitDemoView class
//
//////////////////////////////////////////////////////
// CBlitDemoView construction/destruction
CBlitDemoView::CBlitDemoView()
{
// Zaczynamy od wyświetlania bąbelków m_nOperation = OPERATION_BUBBLES;
// Indeksy dla sekwencji bąbelków, drezyny i muchy m_nCurrentFly = 2; m_nCurrentBubble = 0; m_nCurrentTrolley = 0;
// Pozycja muchy m_nFlyX =20; m_nFlyY = 10;
static int nBubblef] = {
IDB_BUBBLE1, IDB_BUBBLE2, IDB_BUBBLE3 };
static int nFly[] = {
IDB_FLY1, IDB_FLY2, IDB_FLY3, IDB_FLY4 };
static int nFlyMaskf] = { IDB_FLYMASK1, IDB_FLYMASK2, IDB_FLYMASK3, IDB_FLYMASK4 };
static int nTrolley[] = { IDB_TROLLEYl, IDB_TROLLEY2, IDB_TROLLEY3, IDB_TROLLEY4 };
// Załadowanie bitmap z zasobów for( int i=0; i<4; i++ ){
if( i < 3 }
m_Bubble[i].LoadBitmap( nBubblefi] );
m_Fly[i].LoadBitmap{ nFlyfi] };
m_FlyMask[i].LoadBitmap( nFlyMask[i] };
m_Trolley[i] .LoadBitmap( nTrolley[i] ) ;
CBlitDemoView::-CBlitDemoView()
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//CBlitDemoView drawing
void CBlitDemoYiew::OnDraw(CDC* pDC) {
CBlitDemoDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// Przy odrysowywaniu po prostu wywołaj DrawBitmaps DrawBitmaps( pDC );
}
void CBlitDemoYiew::DrawBitmaps( CDC *pDC
{
CDC MemDC;
MemDC.CreateCompatibleDC( pDC ); CBitmap *p01dBitmap;
BITMAP bm;
switch( m_nOperation ){
case OPERATION_BUBBLES: pOldBitmap =
(CBitmap *) MemDC.SelectObject(
&m_Bubble[m_nCurrentBubble] );
// Szerokość i wysokość bitmapy jest potrzebna // dla funkcji BitBltO m_Bubble[m_nCurrentBubble].GetObject(
sizeof( BITMAP ), &bm }; // Rysujemy bąbelki pDC->BitBlt( O, O, bm.bmWidth, bm.bmHeight, &MemDC,
O, O, SRCCOPY ); break;
case OPERATION_TROLLEY: pOldBitmap =
(CBitmap *) MemDC.SelectObject(
&m_Trolley[m_nCurrentTrolley] ); // Szerokość i wysokość bitmapy jest potrzebna // dla funkcji BitBltO m_Trolley[m_nCurrentTrolley].GetObject(
sizeof ( BITMAP ), &bm ); // Rysujemy drezynę pDC->BitBlt( O, O, bm.bmWidth, bm.bmHeight, &MemDC,
O, O, SRCCOPY ); break;
case OPERATION_XORFLY: pOldBitmap =
(CBitmap *) MemDC.SelectObject(
&m_Fly[m_nCurrentFly] );
// Szerokość i wysokość bitmapy jest potrzebna // dla funkcji BitBltO m_Fly[m_nCurrentFly].GetObject( sizeof( BITMAP ), &bm );
// Współrzędne środka obiektu są w zmiennych
// m_nFlyX, m_nFlyY.
// Szerokość i wysokość służą do wyśrodkowania obiektu.
pDC->BitBlt(m_nFlyX - ( bm.bmWidth/2 ), m_nFlyY,
bm.bmWidth, bm.bmHeight,&MemDC,
O, O, SRCINVERT ) ;
break;
case OPERAT I ON_MASKFLY: pOldBitmap =
(CBitmap *) MemDC. SelectObject (
&m_FlyMask[m_nCurrentFly] ) ;
// Szerokość i wysokość bitmapy jest potrzebna // dla funkcji BitBlt() m_FlyMask[m_nCurrentFly] .GetObject (
sizeof( BITMAP ), &bm );
pDC->BitBlt( m_nFlyX - ( bm.bmWidth / 2,m_nFlyY,
bm.bmWidth, bm.bmHeight, &MemDC, O, O, SRCAND ) ;
MemDC. SelectObject ( &m_Fly [m_nCurrentFly] ) ; // Współrzędne środka obiektu są w zmiennych // m_nFlyX, m_nFlyY.
// Szerokość i wysokość służą do wyśrodkowania obiektu. pDC->BitBlt( m_nFlyX - ( bm.bmWidth / 2 ), m_nFlyY, bm.bmWidth, bm.bmHeight, &MemDC, O, O, SRCPAINT ) ; break;
}
MemDC.SelectObject( pOldBitmap );
}
///////////////////////////////////////////////. / / CBlitDemoView message handlers
void CBlitDemoView: :OnLButtonDown (UINT nFlags, CPoint point)
{
CClientDC ClientDC( this ) ;
switchf m_nOperation ){
case OPERATION_BUBBLES:
// Następny bąbelek
m_nCurrentBubble++;
// W razie potrzeby wracamy do początku sekwencji
if( m_nCurrentBubble >= 3 } m_nCurrentBubble = 0;
break; case OPERATION_TROLLEY:
// Następny obrazek drezyny
m_nCur rent Troi ley++;
// W razie potrzeby wracamy do początku sekwencji
if( m_nCurrentTrolley >= 4 ) m_nCurrentTrolley = 0;
break; case OPERATION_XORFLY:
// Mucha rysowana operacją KOR porusza się // tak samo jak mucha maskowana. Jedyna różnica // polega na usuwaniu poprzedniego obrazka // muchy KOR funkcja DrawBitmaps(), a muchy maskowanej // za pomocą prostokąta. DrawBitmaps( &ClientDC ); case OPERATION_MASKFLY:
if( m_nOperation == OPERATION_MASKFLY ){
// Rozmiar bitmapy przyda się przy
// usuwaniu obrazka muchy.
BITMAP bm;
m_FlyMask[m_nCurrentFly].GetObject( sizeof( BITMAP ), &bm );
RECT Rect;
Rect.left = m_nFlyK - ( bm.bmWidth / 2 );
Rect.top = m_nFlyY;
Rect.right = Rect.left + bm.bmWidth;
Rect.bottom = Rect.top + bm.bmHeight;
// Wymalowanie prostokąta pędzlem w kolorze okna.
CBrush Brush( GetSysColor( COLOR_WINDOW ) );
ClientDC.FillRect( &Rect, &Brush );
}
// Przełączamy pomiędzy 2 i 3 idąc w dół // oraz pomiędzy O i l idąc w górę. W tym celu // używamy A 1. Jeśli mucha dojdzie do granicy, // zmieniamy kierunek. m_nCurrentFly A= 1; if( m_nCurrentFly >= 2 ) {
m_nFlyY += 10;
if( m_nFlyY > 300 )
m_nCurrentFly -= 2;
} else{
m_nFlyY -= 10;
if( m_nFlyY <= 10 )
m_nCurrentFly += 2;
} break;
}
DrawBitmaps ( &ClientDC );
CView: : OnLButtonDown (nFlags, point) ;
}
void CBlitDemoView: : OnOperationsMaskf ly ( )
{
// Rysujemy maskowaną muchę
m_nOperation = OPERATION_MASKFLY;
// Odświeżamy obszar roboczy w celu narysowania muchy
InvalidateRect ( NULL, TRUE };
UpdateWindow ( ) ;
}
void CBlitDemoView::OnOperationsShowbubbles()
{
// Rysujemy bąbelki
m_nOperation = OPERATION_BUBBLES;
// Odświeżamy obszar roboczy w celu narysowania bąbelków
InvalidateRect( NULL, TRUE );
UpdateWindow();
}
void CBlitDemoYiew : : OnOperationsShowtrolley ( )
{
// Rysujemy drezynę
m_nOperation = OPERATIONJTROLLEY;
// Odświeżamy obszar roboczy w celu narysowania drezyny
InvalidateRect ( NULL, TRUE ) ;
UpdateWindow ( ) ;
}
void CBlitDemoView: : OnOperationsXorf ly ( )
{
// Rysujemy XOR-owaną muchę
m_nOperation = OPERATION_XORFLY;
// Odświeżamy obszar roboczy w celu narysowania muchy
InvalidateRect ( NULL, TRUE );
UpdateWindow ( ) ;
}
Palety i kolor
Kolor w systemach zgodnych z IBM PC jest reprezentowany za pomocą trójek RGB Każda z trójek RGB zawiera wartości składowe barwy czerwonej (ang. red), zielonei (ang. greeri) oraz niebieskiej (ang. blue). Połączenie tych trzech składowych określa kolor widoczny na ekranie.
RGB jest jedną z powszechnie stosowanych przestrzeni (definicji) koloru. Kolory czerwony, zielony i niebieski są uważane z podstawowe i nierozkładalne. Systemy kolorów można podzielić na dwie kategorie: systemy addytywne oraz systemy subtraktywne. Kolory w systemach addytywnych, takich jak RGB, są tworzone przez dodawanie barw do czerni w celu otrzymania nowego koloru. Im więcej barwy zostanie dodane, tym bardziej wynikowy kolor zbliża się do bieli. Obecność wszystkich barw składowych w odpowiednim natężeniu daje czystą biel, podczas gdy całkowity brak barw składowych daje czy sta czerń.
W tej sekcji do tworzenia trójek RGB użyjemy makra RGB. To makro zamienia trzy wartości barw składowych, należących do zakresu od O do 255, na pojedynczą wartość typu COLORREF. Pierwszym argumentem makra jest natężenie barwy czerwonej, drugim natężenie barwy zielonej, zaś trzecim natężenie barwy niebieskiej.
Dwie inne przestrzenie kolorów, z którymi możesz się zetknąć: Przestrzeń kolorów CMY
CMY (cyan - cyjan, magenta - fiolet, yellow - żółty) to subtraktywny system kolorów używany przez drukarzy i fotografów w celu otrzymania kolorów w wyniku mieszania pigmentów, zwykle na białej powierzchni. Jest stosowany w większości urządzeń drukujących, umieszczających kolorowy barwnik na białym papierze, na przykład w kolorowych drukarkach laserowych lub atramentowych. Przy oświetleniu każdy z trzech barwników pochłania światło w kolorze dopełniającym. Cyjan absorbuje światło czerwone, magenta absorbuje światło zielone, zaś żółć absorbuje światło niebieskie. Zwiększając na przykład ilość żółtego barwnika, zmniejsza się ilość odbijanego światła niebieskiego, czyli obraz staje się mniej niebieski.
W systemie CMY, podobnie jak w innych systemach subtraktywnych, nowe kolory otrzymuje się przez pochłonięcie przez barwnik odpowiedniej części światła białego. Nowe kolory są wynikiem połączenia odbitych fal światła, a nie tych pochłoniętych przez barwnik. Na przykład, jeśli w białym świetle zostaną pochłonięte cyjan i magenta, otrzymanym kolorem będzie żółty. Mówimy więc, że żółty pigment pochłania z białego światła cyjan i magentę. Gdy wszystkie komponenty CMY zostaną odjęte (czyli pochłonięte) od światła białego, otrzymanym kolorem będzie czerń.
W rzeczywistości jednak, w systemie CMY dość trudno jest uzyskać przy istniejących barwnikach czystą czerń. Powstała więc odmiana systemu CMY, znana jako CMYK. W systemie CMYK stosuje się dodatkowy barwnik, reprezentujący czerń.
Na podstawie wartości RGB można obliczyć wartość CMY. Aby obliczyć wartość dla cyjanu, odejmij wartość RGB czerwieni od 255; dla magenty odejmij wartość RGB zieleni od 255, zaś dla żółci odejmij wartość RGB niebieskiego od 255. Na przykład odpowiednikiem wartości RGB(240, 12, 135) jest wartość CMY(15, 243, 120).
HSV
HSV jest jednym z wielu systemów kolorów, w których zamiast mieszania samych kolorów opisuje się właściwości koloru. Odcień (ang. hue) określa kolor w ogólnym tego słowa znaczeniu, taki jak czerwień, oranż, błękit itd. Nasycenie (ang. saturation) odnosi się do ilości bieli w odcieniu: w pełni nasycony odcień nie zawiera żadnej bieli i wydaje się być czysty. Ekstrapolując, częściowo nasycony odcień wydaje się jaśniejszy z powodu obecności bieli. Na przykład odcień czerwony nasycony w pięćdziesięciu procentach staje się kolorem różowym. Jasność (ang. value) określa stopień samoiluminacji koloru - tj. ilości emitowanego światła. Odcień o dużej jasności jest bardzo jaskrawy, podczas gdy odcień o niskiej jasności jest ciemny.
System HSV przypomina system kolorów używanych przez malarzy i innych artystów tworzących kolory przez dodawanie do czystych pigmentów bieli i czerni w celu uzyskania zabarwień, cieni i tonów.
Zabarwienie to czysty, w pełni nasycony kolor połączony z bielą, zaś cień to w pełni nasycony kolor połączony z czernią. Ton to w pełni nasycony kolor połączony zarówno z czernią, jak i z bielą. Odnosząc system HSV do tego mieszania kolorów, można stwierdzić, że nasycenie to ilość bieli, jasność to ilość czerni, zaś odcień to kolor, z którym ta czerń i biel jest mieszana.
Możesz dokonać konwersji koloru z przestrzeni RGB do przestrzeni HSV, używając podanej poniżej funkcji, którą znajdziesz w pliku HSV.c w folderze rozdziału 10. na dołączonej do książki płytce CD-ROM:
#define mid( a, b, c ) \ (a>=b&&a<=c?\ a : ( b >= a && b <= c ?b : c ) )
void RGB2HSV( int nRed, int nGreen, int nBlue,
int *nH, int *nS, int *nV ) {
int nLow, nMid, nHigh;
if( nRed == nGreen && nGreen == nBlue ) {
*nH = 0;
*nS = 0;
*nV = nRed; return;
}
nLow = min ( nRed, min ( nGreen, nBlue ) ) nHigh = max ( nRed, max ( nGreen, nBlue )) nMid = mid ( nRed, nGreen, nBlue );
*nV = ( nLow + nHigh) /2;
*nS = nHigh - nLow;
int nCommon = (int)
( 60.0 * (double) ( nMid - nLow ) / (double) ( nHigh - nLow ) );
if( nRed == nLow && nBlue == nHigh }
*nH = 240 - nCommon; else if( nRed == nLow && nGreen == nHigh )
*nH = 120 + nCommon; else if( nGreen == nLow && nRed == nHigh )
*nH = 360 - nCommon; else if( nGreen == nLow && nBlue == nHigh
*nH = 240 - nCommon; else if( nBlue == nLow && nGreen == nHigh
*nH = 120 - nCommon; else if( nBlue == nLow && nRed == nHigh )
*nH = nCommon;
Na przykład wartość RGB o natężeniu czerwieni równym 244, zieleni równym 142 i koloru niebieskiego równym 34 można zapisać jako RGB (24 4, 142, 3 4). Tabela 10.2 zawiera definicje kilku kolorów z domyślnej palety Windows.
Tabela 10.2. Kolory RGB w domyślnej palecie Windows
Wartość RGB Kolor
RGB(0, 0, 0)-Czarny
RGB(255, 255, 255)-Biały
RGB(255, 0, 0)-Czerwony
RGB(0, 255, 0)-Zielony
RGB(0, 0, 255)-Niebieski
RGB(255, 255, 0)-Żółty
RGB(255, 0, 255)-Fiolet
RGB(0, 255, 255)-Cyjan
RGB(128, 128, 128)-Ciemnoszary
RGB(192, 192, 192)-Jasnoszary
Palety logiczne
Karty graficzne pracujące w trybach 16, 24 i 32 bitów na piksel określają kolory poszczególnych pikseli poprzez wartości RGB przechowywane w pamięci obrazu. Karty graficzne działające w trybach 8 lub mniej bitów na piksel działają w sposób bardziej pośredni i często mówi się, że korzy stają z palety. Obrazy zawierające dane posiadające 8 lub mniej bitów na piksel nazywane są obrazami paletowymi.
W trybach graficznych wykorzystujących palety pamięć obrazu nie zawiera wartości RGB, lecz indeksy do tablicy 256 wartości RGB. Ta tablica znajduje się w części pamięci obrazu znanej jako LUT (Look-Up Table). W Windows nie możesz bezpośrednio ustawiać jej wartości, gdyż jest ona jednym ze wspólnych zasobów. Zamiast tego w celu ustawienia palety musisz użyć funkcji GDI.
Menedżer palety w GDI pełni rolę arbitra decydującego o zawartości palety sprzętowej. Jednym z jego ważniejszych zadań jest utrzymanie dwudziestu kolorów (zwanych kolorami statycznymi), za pomocą których Windows rysuje standardowe elementy interfejsu użytkownika, takie jak okna dialogowe czy ikony. Możesz zmusić GDI do modyfikacji tych dwudziestu statycznych kolorów, ale zwykle nie jest to dobrym pomysłem. Więcej na ten temat powiemy sobie w dalszej części rozdziału.
Menedżer palety zabezpiecza także przed powielonymi wartościami palety sprzętowej. Powielone pozycje powodują zmniejszenie efektywności aplikacji, jeśli ma ona wyświetlać obraz w optymalny sposób. Ta cecha jest bardzo cenna dla trybów graficznych korzystających z palety. Podobnie jak w przypadku możliwości zmiany kolorów statycznych, można wymusić także umożliwienie wystąpienia powielonych pozycji palety. Więcej na ten temat w dalszej części rozdziału.
Logiczna paleta jest tworzona z użyciem struktury LOGPALETTE. Wewnątrz tej struktury występuje lista struktur PALETTEENTRY. Lista PALETTEENTRY zawiera wartości RGB, na podstawie których Windows może ustawić paletę sprzętową. Ilość struktur PALETTEENTRY mogących wystąpić na liście waha się pomiędzy l a 256. Jeśli karta graficzna obsługuje więcej niż 256 pozycji palety, paleta logiczna także może być większa. Rzeczywistym ograniczeniem wielkości palety jest rozmiar palety sprzętowej. W większości przypadków wynosi on właśnie 256 pozycji. Oto definicja struktury LOGPALETTE:
typedef struct tagLOGPALETTE { // 1gpl
WORD palVers.ion;
WORD palNumEntries;
PALETTEENTRY palPalEntry[l]; } LOGPALETTE;
Składowa paiversion określa wersję struktury i we wszystkich obecnych wersjach Windows wynosi 0x300. Składowa palNumEntries zawiera ilość pozycji palety zawartych na liście struktur PALETTEENTRY. Definicja struktury PALETTEENTRY jest następująca:
typedef struct tagPALETTEENTRY {// pe
BYTE peRed;
BYTE peGreen;
BYTE peBlue;
BYTE peFlags; } PALETTEENTRY;
Składowe peRed, peGreen oraz peBlue zawierają wartości składowe RGB. Składowa peFlags zawiera wartość jednego lub kilku znaczników opisujących rodzaj pozycji palety. Znaczniki te opisano w tabeli 10.3.
Tabela 10.3. Znaczniki pozycji palety
Znacznik
Opis
PC_EXPLICIT Określa, że pozycja palety stanowi indeks do palety systemowej, a nie kolor RGB. Używane przez programy wyświetlające zawartość palety systemowej.
PC_NOCOLLAPSE Określa, że pozycja palety ma zostać umieszczona w nieużywanej pozycji palety systemowej, nawet jeśli taka pozycja palety już istnieje. Używane w celu zapewnienia unikalności pozycji palety w przypadku, gdy kilka pozycji odnosi się do tego samego koloru.
PC_RESERVED Określa, że pozycja palety ma być prywatna dla aplikacji. Gdy do palety systemowej zostanie dodana pozycja ze znacznikiem PC_RESERVED, nie zostaje odwzorowana na kolory w innych paletach logicznych, nawet jeśli kolory się zgadzają. Używane przez programy stosujące animację palety.
Oto w jaki sposób możesz utworzyć paletę logiczną składającą się z szesnastu pozycji, w których kolory RGB są obliczane na podstawie licznika pętli:
LOGPALETTE *pLogPal;
pLogPal = (LOGPALETTE*) new char [sizeof(LOGPALETTE + 16 *
sizeof(PALETTEENTRY)];
pLogPal->palVersion = 0x300; pLogPal->palNumEntries = 16;
for(int i = 0; i < 16;
pLogPal->palPalEntry [i] .peRed = (unsigned char) ( i * 16 ) pLogPal->palPalEntry [i] .peGreen = (unsigned char) ( i * 12 pLogPal->palPalEntry [i] .peBlue = (unsigned char) ( i * 8 ) pLogPal->palPalEntry [i] .peFlags = 0;
Struktura LOGPALETTE może zostać użyta w wywołaniach funkcji Windows API. Do stworzenia palety lepiej jednak jest użyć klasy MFC CPalette. Ta klasa znacznie ułatwia operowanie paletą, ponieważ klasa coc reprezentująca GDI Windows oczekuje właśnie obiektów typu CPalette. Jeśli nie użyjesz obiektu klasy CPalette, klasa coc będzie bezużyteczna, jeśli chodzi o manipulowanie paletą.
Gdy masz już strukturę LOGPALETTE, utworzenie obiektu klasy CPalette jest bardzo proste. Poniższy kod wykorzystuje strukturę LOGPALETTE (pLogPai utworzoną w poprzednim przykładzie) i na jej podstawie tworzy obiekt klasy CPalette. Po utworzeniu obiektu CPalette pamięć zajmowana przez strukturę LOGPALETTE może zostać usunięta.
CPalette Palette;
Palette . CreatePalette ( pLogPai ) ;
delete [] pLogPai;
Po utworzeniu obiektu CPalette musisz wybrać paletę w kontekście urządzenia, a następnie ją zrealizować. Realizacja palety polega na skopiowaniu przez Windows pozycji palety do rejestrów sprzętowych. Tak jak w przypadku większości obiektów GDI, po zakończeniu pracy konieczne jest wybranie w kontekście urządzenia poprzednio wybranej palety. Oto fragment kodu z funkcji onDraw ( ) pobierający obiekt CPalette utworzony w ostatnim przykładzie i używający go w celu ustawienia palety Windows:
CPalette ^pOldPalette; pOldPalette = pDC->SelectPalette pDC->RealizePalette(); pDC->SelectPalette( pOldPalette,
SPalette, FALSE
F AL S E
Możesz stworzyć obiekty klasy CPalette zawierające ogólne i dość równomiernie rozłożone kolory. Takie palety są nazywane paletami półtonów i mogą być tworzone przy użyciu funkcji CreateHalftonePalette (). Oto przykład tworzenia takiego obiektu CPalette:
CPalette Palette;
Palette.CreateHalftonePalette
p DC
Jeśli funkcji CreateHalftonePalette () przekażesz wskaźnik o wartości NULL, zostanie utworzona 256-pozycyjna paleta półtonów niezależna od urządzenia wyjściowego.
Zdarzenia dotyczące palety
Pisząc aplikację ustawiającą paletę, pamiętaj, że inne aplikacje także mogą znaleźć się w ognisku wprowadzania i mogą ustawić paletę w inny sposób. Gdy tak się stanie, po przełączeniu do swojej aplikacji może okazać się, że kolory nie wyglądają tak, jak tego oczekujesz. Z tego powodu konieczne jest dodanie funkcji obsługi zdarzeń, które zapewnią ewentualne odtworzenie palety.
Komunikat WM_QUERYNEWPALETTE jest wysyłany do głównego okna aplikacji (w aplikacjach utworzonych przez ClassWizarda jest to zwykle obiekt CMainFrame) w momencie gdy realizacja palety powoduje zmianę w palecie systemowej. Realizacja palety polega na wysłaniu przez Windows danych z wybranej palety do rejestrów karty graficznej, co powoduje natychmiastową zmianę kolorów na ekranie. Komunikat WM_PALETTECHANGED jest wysyłany do głównego okna aplikacji w momencie, gdy znajdzie się ono w ognisku wprowadzania. W przypadku aplikacji z interfejsem wielodokumentowym, jeśli każde z okien dokumentów korzysta z własnej palety, każde okno potomne powinno reagować na komunikat WM_SETFOCUS, aby mieć okazję do ustawienia własnej palety.
W poniższym przykładzie zakładamy, że został już utworzony obiekt m_Palette należący do klasy GPaiette. Przykład ilustruje sposób obsługi komunikatu WM_QUERYNEWPALETTE.
Obsługa komunikatu WM_QUERYNEWPALETTE
Komunikat: WM_QUERYNEWPALETTE Utworzona funkcja: OnQueryNewPalette () Moduł kodu źródłowego: MainFrm.cpp
BOOL CMainFrame::OnQueryNewPalette()
{
CClientDC ClientDC{ this );
CPalette *p01dPalette = ClientDC.SelectPalette(&m Palette, FALSE);
if( ClientDC.RealizePalette()>0) Invalidate();
ClientDC.SelectPalette( pOldPalette, FALSE);
}
Obsługa komunikatu WM_PALETTECHANGED Komunikat: WM_PALETTECHANGED Utworzona funkcja: OnPaletteChanged() Moduł kodu źródłowego: MainFrm.cpp
BOOL CMainFrame::OnPaletteChanged(CWnd *pFocusWnd)
{
if( pFocusWnd != this ){
CClientDC ClientDC( this ); CPalette *p01dPalette =
ClientDC.SelectPalette( &m_Palette, FALSE);
if( ClientDC.RealizePalette() > O ) Invalidate();
ClientDC.SelectPalette( pOldPalette, FALSE );
} }
Funkcja SetSystemPaletteUse()
Od czasu do czasu możesz chcieć odwołać się do pełnej palety systemowej, łącznie z dwudziestoma statycznymi kolorami. Jeśli znajdziesz się w takiej sytuacji, za pomocą funkcji SetsystemPaletteUse () możesz zmienić ilość statycznych kolorów rezerwowanych dla siebie przez Windows. Poniższy kod redukuje ilość kolorów statycznych do dwóch: białego i czarnego:
CClientDC ClientDC(this ); SetsystemPaletteUse(ClientDC.m hDC,SYSPAL NOSTATIC);
Bitmapy niezależne od urządzenia
Jedną z największych wad bitmap zależnych od sprzętu jest niemożność wyświetlania ich w kontekstach urządzeń o innej głębokości koloru. Ten problem można rozwiązać, korzystając z niezależnych od urządzenia bitmap DIB (device-independent bitmap). Niestety, bitmapy DIB nie są reprezentowane przez żadną z klas MFC. Zmusza to programistów do tworzenia dodatkowego kodu, gdyż muszą samodzielnie ładować, zapisywać, rysować, manipulować i obsługiwać takie bitmapy.
Aby temu zaradzić, w tej sekcji utworzymy klasę o nazwie CDib, która będzie reprezentowała bitmapę niezależną od urządzenia. Stworzymy także program demonstracyjny wykorzystujący tę klasę, pokazujący jak łatwe dzięki niej jest posługiwanie się bitmapami DIB.
Anatomia pliku DIB
Pliki zawierające bitmapy DIB zawsze zaczynają się od struktury BITMAPFILEHEADER. Ta struktura nie zawiera zbyt wielu przydatnych informacji poza rozmiarem pliku oraz adresem początku danych bitmapy DIB w pliku. Ten adres określa miejsce, w którym zaczynają się dane obrazu bitmapy, a nie nagłówka tych danych. Pierwsze dwa bajty pliku zawierają sygnaturę "BM", która może być przydatna przy sprawdzaniu poprawności pliku. (Przy sprawdzaniu tej sygnatury pamiętaj, że procesory Intela przechowują zmienne w pamięci w kolejności od najmłodszego do najstarszego bajtu). Struktura BITMAPFILEHEADER jest zadeklarowana następująco:
typedef struct tagBITMAPFILEHEADER { // bmfh
WORD bfType;
DWORD bfSize;
WORD bfReservedl;
WORD bfReserved2;
DWORD bfOffBits;
}BITMAPFILEHEADER;
Składowa bfType ma zawsze wartość "BM" w celu wskazania, że plik jest plikiem bitmapy. Składowa bfSize zawiera wartość równą rozmiarowi pliku. Zarezerwowane składowe nie są używane i powinny mieć wartość zero. Składowa bfoffBits zawiera przesunięcie danych obrazu względem początku pliku.
Tuż za tym w pliku DIB znajduje się struktura BITMAPINFOHEADER. W tej strukturze mieszczą się zasadnicze informacje dotyczące bitmapy, takie jak jej wysokość, szerokość oraz głębia kolorów. Struktura BITMAPINFOHEADER jest zadeklarowana następująco:
typedef struct tagBITMAPINFOHEADER{ // bmih
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount
DWORD biCompression;
DWORD biSizelmage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant; } BITMAPINFOHEADER;
Pole biSize zawiera rozmiar struktury BITMAPINFOHEADER. Pola biwidth oraz biHeight określają szerokość i wysokość bitmapy. Pola biPlanes oraz biBitCount opisują głębię kolorów obrazu. Pole biCompression prawie zawsze ma wartość zero, lecz czasem może mieć inną wartość, reprezentującą różne sposoby kompresji danych. Pole bisizeiIage zawiera rozmiar obrazu w bajtach. Następne dwa pola, biXPelsPerMeter oraz biYPelsPerMeter, odnoszą się do rozdzielczości obrazu i są rzadko używane. Pole biclrusea zwykle ma wartość zero -jedyny wyjątek występuje wtedy, gdy ilość użytych kolorów jest mniejsza od maksymalnej ilości kolorów dla danej głębokości koloru. Pole bidrIim-portant wskazuje, jak wiele z kolorów jest ważnych - zwykle używana wartość zero oznacza, że ważne są wszystkie kolory.
Ostatnią informacją w pliku DIB poprzedzającą same dane obrazu jest ewentualna paleta kolorów. W przypadku bitmap DIB o głębokości koloru większej niż 8., nie występują dane palety. W przypadku bitmap DIB o głębokości koloru mniejszej lub równej 8, ilość pozycji palety może być obliczona za pomocą następującego wzoru:
int nNumPaletteEntries = l << BitmapInfoHeader.biBitCount;
Pliki DIB mogą zawierać mniej pozycji palety niż wynosi maksymalna ilość kolorów w palecie. Na przykład 8-bitowe bitmapy DIB posiada, maksymalnie 256 kolorów; aplikacja zapisująca takąbitmapę na dysku może zapisać mniej niż 256 pozycji palety.
Do określenia, czy bitmapa została zapisana z maksymalną ilością pozycji kolorów w palecie, można wykorzystać pole biClrUsed struktury BITMAPINFOHEADER. Jeśli pole biClrUsed ma wartość zero, plik DIB zawiera maksymalną ilość pozycji palety. Jeśli wartość tego pola jest niezerowa, plik DIB zawiera dokładnie taką ilość pozycji palety. Pozycje palety są przechowywane w strukturach RGBQUAD. Format tej struktury jest następujący:
typedef struct tagRGBQUAD{// rgbg
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved; } RGBQUAD;
Na koniec, po strukturach BITMAPFILEHEADER, BITMAPINFOHEADER oraz po pozycjach palety następują dane bitmapy DIB. W przypadku bitmap 8-bitowych każdy bajt reprezentuje jeden piksel; w przypadku bitmap 16-bitowych każdy piksel jest reprezentowany przez dwa bajty; w bitmapach 24-bitowych przez trzy bajty, zaś w bitmapach 32-bitowych przez cztery bajty.
Obliczanie ilości bajtów przypadających na linię bitmapy DIB
Gdy zostaniesz zmuszony do obliczenia jakiegoś adresu w danych bitmapy DIB, pamiętaj o jednej ważnej rzeczy. Aby wyznaczyć ilość bajtów w linii bitmapy DIB, większość programów po prostu przemnożyłoby szerokość bitmapy w pikselach przez ilość bajtów na piksel. Niestety, otrzymany wynik mógłby nie być prawidłowy, ponieważ ilość bajtów w każdej linii bitmapy DIB musi być wielokrotnością czterech. Do obliczenia ilości bajtów w linii bitmapy DIB możesz użyć poniższego kodu:
int nWidthBytes = BitmaplnfoHeader.biWidth; if( BitmapInfoHeader.biBitCount == 16 )
nWidthBytes *= 2; else if( BitmapInfoHeader.biBitCount == 24 )
nWidthBytes *= 3; else if( BitmapInfoHeader.biBitCount == 32 )
nWidthBytes *= 4;
while( ( nWidthBytes & 3 ) != O )
nWidthBytes++;
Klasa CDib
Klasa CDib może zaoszczędzić Ci sporo czasu 5 pomóc w utrzymaniu porządku w swoim programie. W tej sekcji opiszemy po kolei każdą z funkcji składowych tej klasy.
CDib::Load
Funkcja Load () posiada pojedynczy argument będący wskaźnikiem do łańcucha znaków ASCIIZ zawierającego nazwę pliku bitmapy. Jeśli wywołanie funkcji się powiedzie, zwracana jest wartość TRUE. Jeśli z jakiegoś powodu wczytanie pliku się nie powiedzie, funkcja zwraca wartość FALSE. Jeśli plik DIB jest już załadowany do pamięci, funkcja Load () usuwa go stamtąd w przypadku pomyślnego załadowania nowego pliku. Kod źródłowy tej funkcji został przedstawiony na listingu 10.3.
Listing 10.3. Kod źródłowy funkcji CDib::Load()_
__________________________
BOOL CDib::Load( const char *pszFilename )
{
CFile cf;
// Próba otwarcia pliku DIB do odczytu, if( !cf.0pen( pszFilename, CFile::modeRead return( FALSE );
// Przechowanie rozmiaru pliku.
// Odjmujemy rozmiar struktury
// BITMAPFILEHEADER gdyż nie będziemy
// przechowywać jej w pamięci.
DWORD dwDibSize;
dwDibSize =
cf .GetLengthO - sizeof( BITMAPFILEHEADER ) ;
// Próba alokacji pamięci dla bitmapy DIB. unsigned char *pDib;
pDib = new unsigned char [dwDibSize]; if( pDib == NULL ) return{ FALSE ) ;
BITMAPFILEHEADER BFH;
// Odczyt nagłówka i danych DIB. try{
// Czy wczytaliśmy cały nagłówek BITMAPFILEHEADER? if( cf.Read( &BFH, sizeof( BITMAPFILEHEADER ) ) != sizeof( BITMAPFILEHEADER ) ||
// Czy plik typu 'MB'? BFH.bfType != 'MB' ||
// Czy odczytaliśmy pozostałe dane? cf.Read( pDib, dwDibSize ) != dwDibSize ){
// W przypadku błędów zwolnienie pamięci // i zwrócenie wartości FALSE. delete [] pDib; return( FALSE );
}
}
// Jeśli zgłoszono przerwanie, usuwamy je, // zwalniamy pamięć i zwracamy FALSE. catch( CFileException *e ) {
e->Delete ( ) ;
delete [] pDib;
return ( FALSE ) ;
}
// Jeśli doszliśmy do tego miejsca, oznacza to, // że bitmapa DIB została poprawnie załadowana. // Jeśli w tej kalsie bitmapa już była załadowana, // musimy ja teraz usunąć. if( m_pDib != NULL ) delete rnjpDib;
// Przechowanie wskaźnika do lokalnych danych DIB
// oraz rozmiarów bitmapy w zmiennych składowych
// klasy.
m_pDib = pDib;
m dwDibSize = dwDibSize;
// Aktualizacja adresów nagłówka i palety bitmapy m_pBIH = (BITMAPINFOHEADER *) m_pDib; m_pPalette =
(RGBQUAD *) &m_pDib[sizeof(BITMAPINFOHEADER)];
// Obliczenie ilości pozycji palety. m_nPaletteEntries = l m_pBIH->biBitCount; if( m_pBIH->biBitCount > 8 )
m_nPaletteEntries = 0; else if( m_pBIH->biClrUsed != O )
m_nPaletteEntries = m_pBIH->biClrUsed;
// Aktualizacja wskaźnika m_pDibBits do danych bitmapy. m_pDibBits =
&m_pDib[sizeof(BITMAPINFOHEADER)+
m_nPaletteEntries*sizeof(RGBQUAD)];
// Jeśli mamy poprawną paletę, usuńmy ją. if( m_Palette.GetSafeHandle() != NULL ) m_Palette.DeleteObject() ;
// Jeśli w pliku występują pozycje palety,
// tworzymy strukturę LOGPALETTE i z jej pomocą
// tworzymy obiekt palety CPalette.
if( m_nPaletteEntries != O ){
// Miejsce na strukturę LOGPALETTE. LOGPALETTE *pLogPal = (LOGPALETTE *) new char
[sizeof(LOGPALETTE)+
m_nPaletteEntries*sizeof(PALETTEENTRY)];
if( pLogPal != NULL ){
// Numer wersji to 0x300; ilość pozycji // palety odpowiada ilości pozycji w pliku. pLogPal->palVersion = 0x300; pLogPal->palNumEntries = m_nPaletteEntries;
// Zapisanie wartości RGB w każdej // z pozycji palety.
for( int i=0; i
palPalEntry[i].peRed =
m_pPalette[i].rgbRed; pLogPal->palPalEntry[i].peGreen =
m_pPalette[i].rgbGreen; pLogPal->palPalEntry[i].peBlue = m_pPalette[i].rgbBlue;
}
// Tworzenie obiektu CPalette i usunięcie // z pamięci struktury LOGPALETTE. m_Palette.CreatePalette( pLogPal ); delete [] pLogPal;
}
}
return( TRUE);
Aby użyć funkcji Load (), po prostu wywołaj ją z odpowiednią nazwą pliku. Jeśli funkcja poprawnie załaduje plik .BMP, zwróci wartość TRUE. Jeśli z jakiegoś powodu wczytanie pliku się nie powiedzie, funkcja zwróci wartość FALSE. Oto przykład użycia tej funkcji:
CDib Dib;
BOOL bRet;
bRet = Dib.Load( "C:\\Windows\\Clouds.bmp" );
if( bRet )
AfxMessageBox(
"Wywołanie funkcji Load() zakończyło się sukcesem." ); else
AfxMessageBox(
"Wywołanie funkcji Load() zakończyło się niepowodzeniem." );
CDib::Save
Czasem może okazać się konieczny zapis pliku DIB na dysku. Taka sytuacja zdarza się, gdy zmodyfikujesz dane bitmapy DIB i zechcesz zapisać ją na dysku. Funkcja save () zapisuje do pliku dane bitmapy DIB zawarte w pamięci. Jednym argumentem funkcji jest wskaźnik do nazwy zapisywanego pliku. Jeśli zapis pliku się powiedzie, funkcja zwraca wartość TRUE, w przeciwnym razie zwraca wartość FALSE. Kod źródłowy funkcji został przedstawiony na listingu 10.4.
Listing 10.4. Kod źródłowy funkcji CDib::Save()_____________________
BOOL CDib::Save( const char *pszFilename )
{
// Jeśli nie ma danych, nie możemy niczego zapisać if( m_pDib == NULL ) return( FALSE );
CFile cf;
// Próba utworzenia pliku, if( !cf.0pen( pszFilename,
CFile::modeCreate | CFile::modeWrite ) )
return( FALSE );
// Zapis danych. try{
// Utworzenie struktury BITMAPFILEHEADER
// z odpowiednią zawartością.
BITMAPFILEHEADER BFH;
memset( &BFH, O, sizeof( BITMAPFILEHEADER ) );
BFH.bfType = 'MB1;
BFH.bfSize = sizeof( BITMAPFILEHEADER ) + m_dwDibSize;
BFH.bfOffBits = sizeof( BITMAPFILEHEADER ) + Sizeoff BITMAPINFOHEADER ) + m_nPaletteEntries * sizeof( RGBQUAD );
// Zapis struktury BITMAPFILEHEADER i danych DIB. cf.Write( &BFH, sizeof( BITMAPFILEHEADER ) ); cf.Write( m_pDib, m_dwDibSize ) ;
}
// Jeśli zdarzył się wyjątek, usuwamy go i zwracamy
// wartość FALSE.
catch( CFileException *e ){
e->Delete();
return( FALSE );
}
return( TRUE);
}
Aby użyć funkcji save(), po prostu wywołaj ją z odpowiednią nazwą pliku. Jednak pamiętaj, że jeśli plik o podanej nazwie już istnieje, zostanie zastąpiony przez zapisywany plik. Dobrym pomysłem jest więc wcześniejsze sprawdzenie obecności pliku o podanej nazwie. Jeśli funkcja poprawnie zapisze plik .BMP, zwróci wartość TRUE. Jeśli z jakiegoś powodu zapis pliku się nie powiedzie, funkcja zwróci wartość FALSE. Oto przykład użycia tej funkcji:
CDib Dib;
BOOL bRet;
bRet = Dib.Save( "C:\\Windows\\NewClouds.bmp" );
if( bRet )
AfxMessageBox(
"Wywołanie funkcji Save() zakończyło się sukcesem." ); else
Af xMessageBox(
"Wywołanie funkcji Save() zakończyło się niepowodzeniem." );
CDib::Draw
Oczywiście, klasa CDib nie byłaby zbyt przydatna, gdybyś nie mógł narysować bitmapy na ekranie. Jedną z najważniejszych funkcji tej klasy jest funkcja rysująca bitmapę DIB w kontekście urządzenia. Służy do tego właśnie funkcja Draw(). Posiada tylko jeden wymagany parametr, wskaźnik do obiektu klasy c DC. Następne dwa argumenty, nx i n Y (współrzędne x i y) są opcjonalne. Jeśli ich nie podasz, bitmapa zostanie narysowana w miejscu o współrzędnych O, 0. Czwarty i piąty parametr, nwidth oraz nHeight (szerokość i wysokość bitmapy), także są opcjonalne. Jeśli nie podasz któregoś z nich, zostanie przyjęta oryginalna szerokość lub wysokość bitmapy. W przeciwnym razie funkcja zmniejszy lub rozciągnie bitmapę do podanej szerokości i wysokości. Kod źródłowy funkcji CDib: : Draw () znajduje się na listingu 10.5.
Listing 10.5. Kod źródłowy funkcji CDib::Dra\v()_
__________________________
BOOL CDib::Draw( CDC *pDC, int nX, int nY, int nWidth, int nHeight )
{
// Jeśli nie ma danych, nie możemy niczego rysować. if( m_pDib == NULL ) return( FALSE );
// Sprawdzenie, czy w parametrach nWidth lub
// nHeight występuje wartość -1. Jeśli tak, użyjemy
// odpowiedniej wartości ze struktury
// BITMAPINFOHEADER.
if( nWidth == -l )
nWidth = m_pBIH->biWidth; if( nHeight == -l )
nHeight = m_pBIH->biHeight;
// Rysujemy bitmapę funkcją StretchDIBits(). StretchDIBits( pDC->m_hDC, nX, nY,
nWidth, nHeight,
O, O,
m_pBIH->biWidth, m_pBIH->biHeight,
m_pDibBits,
(BITMAPINFO *) m_pBIH,
BI_RGB, SRCCOPY );
return ( TRUE );
}
Aby użyć funkcji Draw() w jej najprostszej postaci, po prostu przekaż jej wskaźnik do obiektu klasy CDC. Jeśli wywołanie funkcji się powiedzie, zostanie zwrócona wartość TRUE. W przeciwnym wypadku funkcja zwróci wartość FALSE. Poniżej znajdują się trzy przykłady użycia tej funkcji. W pierwszym z nich przekazujemy jedynie wskaźnik do obiektu klasy CDC. W drugim przykładzie przekazujemy wskaźnik oraz współrzędne x i y. W trzecim przykładzie do funkcji jest przekazywany wskaźnik, współrzędne oraz wartości szerokości i wysokości.
CDib Dib;
Dib.Load( "C:\\Windows\\Clouds.bmp" );
Użycie jedynie wskaźnika:
Dib.Draw( pDC );
Użycie wskaźnika i współrzędnych:
Dib.Draw( pDC, 10, 25 );
Użycie wskaźnika, współrzędnych oraz rozmiarów:
Dib.Draw( pDC, 10, 25, 100, 150 ) ;
CDib::SetPalette
Ostatnia funkcja potrzebna do poprawnego rysowania bitmap DIB na ekranie to funkcja ustawiająca paletę. W przypadku bitmap DIB o głębokości koloru większej niż 8., ta funkcja nie robi niczego. Dla bitmap DIB o głębokości koloru mniejszej lub równej 8., z danych pliku DIB pobierane są pozycje palety, na podstawie których tworzony jest obiekt palety CPalette należący do klasy CDib. Kod źródłowy funkcji CDib: : SetPalette () znajduje się na listingu 10.6.
BOOL CDib: :SetPalette ( CDC *pDC )
{
// Jeśli nie ma danych, // nie możemy ustawić palety. if ( m_pDib == NULL ) return ( FALSE ) ;
// Sprawdzamy, czy mamy uchwyt palety. // W przypadku bitmap DIB o głębokości // kolorów większej niż osiem bitów // otrzymamy wartość NULL. if{ m_Palette.GetSafeHandle () == NULL } return ( TRUE ) ;
// Wybranie, realizacja oraz odtworzenie
// poprzedniej palety.
CPalette *p01dPalette;
pOldPalette = pDC->SelectPalette ( &m_Palette, FALSE )
pDC->RealizePalette ( ) ;
pDC->SelectPalette ( pOldPalette, FALSE ) ;
return ( TRUE ) ;
}
Program demonstracyjny ShowDIB
Na płytce CD-ROM dołączonej do książki znajduje się program demonstracyjny pokazujący sposób użycia klasy CDib. Program nosi nazwę ShowDIB i umożliwia ładowanie, zapisywanie i wyświetlanie plików DIB. Ponieważ większość pracy wykonuje klasa CDib, kod źródłowy programu ogranicza się głównie do pliku ShowDIBYiew.cpp i jest naprawdę niewielki. Działanie programu zostało przedstawione na rysunku 10.2. Fragmenty kodu źródłowego programu przedstawiono na listingu 10.7.
ShowDIB
Położenie na płytce: RozdzlO\ShowDIB Nazwa programu: ShowDIB.exe
Moduły kodu źródłowego w tekście: ShowDIBWew.cpp
Listing 10.7. Fragmenty kodu źródłowego programu ShowDIB_
_________________
// ShowDIBYiew.cpp : implementation of the CShowDIBYiew class
//
#include "stdafx.h"
#include "ShowDIB.h"
ttinclude "ShowDIBDoc.h"
#include "ShowDIBYiew.h"
#tifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = _FILE_;
#endif
///////////////////////////////////////////////////////////////////////////////////////////////
// CShowDIBView
IMPLEMENT_DYNCREATE(CShowDIBView, CView)
BEGIN_MESSAGE_MAP(CShowDIBView, CYiew) //{{AFX_MSG_MAP(CShowDIBView) ON_COMMAND(ID_FILE_OPEN, OnFileOpen) //}}AFX_MSG_MAP
ENDMESSAGEMAP()
////////////////
// CShowDIBYiew construction/destruction
CShowDIBView::CShowDIBView()
{
}
CShowDIBView::-CShowDIBView()
{
}
BOOL CShowDIBYiew::PreCreateWindow(CREATESTRUCT& es) {
return CYiew::PreCreateWindow(es);
}
//////////////////////////////////////////////////////////////////////////////
// CShowDIBView drawing
void CShowDIBView: :OnDraw(CDC* pDC) {
CShowDIBDoc* pDoc = GetDocument ();
ASSERT_VALID(pDoc) ;
RECT Rect;
GetClientRect ( &Rect ) ; m_Dib.SetPalette ( pDC ); m_Dib.Draw( pDC, O, O,
Rect.right, Rect.bottom ) ;
}
///////////////////////////////////////////////////////////////////////////////////////////////////// // CShowDIBView diagnostics
#ifdef _DEBUG
void CShowDIBView::AssertValid() const
{
CView::AssertYalid();
}
void CShowDIBYiew::Dump(CDumpContext& dc) const
{
CView::Dump(dc);
}
CShowDIBDoc* CShowDIBYiew: : GetDocument ( )
// non-debug version is inline {
ASSERT(
m_pDocument->IsKindOf(RUNTIME_CLASS (CShowDIBDoc) ) ) ;
return ( CShowDIBDoc* ) m_pDocument ; } #endif //DEBUG
// CShowDIBYiew message handlers
void CShowDIBYiew: :OnFileOpen ( ) {
static char szFilter[] = "Pliki BMP (* .BMP) | * . BMP||";
CFileDialog FileDlgt TRUE, NULL, NULL, OFN_HIDEREADONLY, szFilter ) ;
if( FileDlg.DoModal () == IDOK &&
m_Dib.Load( FileDlg.GetPathName ( ) ) ){ IrwalidateRect ( NULL, TRUE ); UpdateWindow ( ) ;
}
}
Podwójne buforowanie
Gdy Twoja aplikacja korzysta przy rysowaniu z dużej ilości wywołań GDI, często powoduje to zauważalne migotanie w momencie odrysowywania zawartości okna. Za pomocą klasy CBitmap można temu łatwo zapobiec. Stwórz po prostu obiekt klasy CBitmap oraz kontekst urządzenia, wybierz bitmapę w kontekście urządzenia, operacje rysunkowe wykonaj w tym nowym kontekście, po czym narysuj bitmapę na ekranie. Istnieje sposób rysowania na bitmapie w pamięci, a następnie przerzucenie jej na ekran jednym wywołaniem szybkiej funkcji BitBit o . Prawie we wszystkich przypadkach powoduje to wyeliminowanie denerwującego migotania.
Tworzenie migotającej aplikacji
Tworząc tę aplikację, ujrzysz doskonały przykład migotania przy rysowaniu zawartości okna. Nieco później zmodyfikujemy ten program, korzystając z klasy CBitmap. Najlepiej będzie, jeśli stworzysz projekt samodzielnie od początku, gdyż w ten sposób więcej się nauczysz. Jeśli jednak nie chce Ci się tego robić, pełny projekt znajdziesz na dołączonej do książki płytce CD-ROM, w folderze RozdzlO\Flicker.
1. Utwórz aplikację jednodokumentową.
2. Ustaw timer tak by "tykał" co sekundę.
3. W funkcji obsługi timera wywołaj funkcjeInwalidateRect ( NULL, TRUE ) oraz UpdateWindow () w celu odświeżenia zawartości okna widoku.
4. Do funkcji OnDraw () dodaj poniższy kod:
RECT Rect;
GetClientRect(&Rect};
static BOOL bColor = FALSE;
if( bColor )
pDC->SelectStockObject ( WHITE_PEN) else
pDC->SelectStockObject ( BLACK_PEN) bColor = IbColor;
f or ( int i=0; KRect . right ; i + + ){ pDC->MoveTo ( i, O ); pDC->LineTo( i, Rect.bottom );
5. Uruchom program i zmaksymalizuj go.
Gdy program działa, na ekranie co sekundę widać migotanie. Dzieje się tak, ponieważ występuje spory odstęp czasu pomiędzy narysowaniem pierwszej i ostatniej linii. Ponieważ linie są rysowane w kontrastowych kolorach (czarnym i białym), migotanie jest szczególnie wyraźne.
Oto jak pozbyć się migotania w utworzonym przed chwilą programie. W funkcji OnDraw ( ) , przed rysowaniem linii, użyj funkcji CreateCompatibleDCO oraz CreateCompati-bleBitmapO w celu stworzenia kontekstu urządzenia oraz bitmapy, na której będziesz rysował. Wykonaj wszystkie operacje rysunkowe. Na koniec za pomocą funkcji BitBit ( ) przerzuć bitmapę z pamięci do obszaru roboczego okna. Nowa wersja funkcji OnDraw ( ) znajduje się na listingu 10.8.
Listing 10.8. Przeniesienie bitmapy z pamięci do obszaru roboczego okna
_____________
RECT Rect; GetClientRect ( &Rect ) ;
static BOOL bColor = FALSE;
CDC MemDC; MemDC.CreateCompatibleDC( pDC );
CBitmap MemBitmap;
MemBitmap.CreateCompatibleBitmap ( pDC, Rect.right, Rect.bottom ) ;
CBitmap *p01dBitmap = MemDC. SelectObject ( &MemBitmap );
if( bColor )
MemDC. SelectStockObject ( WHITE_PEN ); else
MemDC. SelectStockObject ( BLACK_PEN ) ; bColor = ! bColor;
for( int i=0; KRect . right; i++ ){ MemDC. MoveTo ( i, O ) ; MemDC. LineTo ( i, Rect.bottom ) ;
}
pDC->BitBlt( O, O, Rect.right, Rect.bottom, SMemDC, O, O, SRCCOPY ) ;
MemDC. SelectObject ( pOldBitmap );
Podwójne buforowanie
Poniższe kroki podsumowuj ą proces buforowania serii operacji rysunkowych:
1. Utwórz kontekst urządzenia coc zgodny z kontekstem obszaru roboczego okna.
2. Utwórz bitmapę klasy CBitmap zgodną z kontekstem urządzenia.
3. W kontekście CDC wybierz bitmapę CBitmap; zachowaj wskaźnik do poprzednio wybranej bitmapy, abyś mógł ją później odtworzyć.
4. Wszystkie operacje graficzne wykonaj w nowo utworzonym kontekście CDC.
5. Za pomocą funkcji BitBit () szybko przerzuć nowo utworzoną bitmapę CBitmap do kontekstu obszaru roboczego okna.
6. W kontekście urządzenia CDC wybierz poprzednią bitmapę. Obiekty CDC i CBitmap zostaną usunięte przez ich destruktory.
Podsumowanie
W tym rozdziale nauczyłeś się, jak tworzyć i używać w klasie CBitmap bitmap zależnych od urządzenia. Poznałeś także palety oraz klasę CPalette oraz ich znaczenie w przypadku trybów graficznych o ośmiu bitach na piksel. Wiesz już także, w jaki sposób ułatwić sobie manipulowanie bitmapami niezależnymi od sprzętu, wykorzystując w tym celu stworzoną przez nas klasę CDib.
Bitmapy zależne i niezależne od sprzętu są wydajnym i elastycznym narzędziem. W czasach gdy wszystkie aplikacje działają w środowisku graficznym, trudno nie doceniać ich przydatności. Teraz, gdy już przeczytałeś ten rozdział, powinieneś wziąć się samemu do roboty i trochę poeksperymentować.
Wyszukiwarka
Podobne podstrony:
10) Ilościowe oznaczenie glikogenu oraz badanie niektórych jego właściwości
Rozwój religii greckiej oraz świat bogów greckich wg Mit~102
110 USTAWA o autostradach płatnych oraz o K F D [27 10 19
TEMAT 10 Gaszenie pożarów oraz środki gaśnicze
10 Lomnicki, Dobor naturalny Dostosowanie i wspolczynnik doboru oraz inne miary dostosowania (2009)
10 ROZPOZNAWANIE ZAPOBIEGANIE ORAZ WYKRYWANIE PRZESTĘPSTW I ICH SPRAWCÓW W RAMACH PROWADZONEJ PRA
WSM 10 52 pl(1)
VA US Top 40 Singles Chart 2015 10 10 Debuts Top 100
10 35
więcej podobnych podstron