Ćwiczenie 1 OBSŁUGA OBRAZÓW W FORMACIE BMP Zakres pracy W ramach ćwiczenia należy do dostarczonego interfejsu dodać możliwość wyświetlania wczytanych z pliku obrazów typu BMP, konwertowania ich na format 8-bitowy o 256 poziomach szarości, a następnie wyświetlania i zapisywania wyniku przekształcenia. Lewe okno ma przedstawiać obraz źródłowy, a prawe - wynikowy. Wyświetlane obrazy mają maksymalnie wypełniać okno, zachowując jednak proporcje boków. Implementacja musi być wykonana w języku C++ bez użycia zewnętrznych bibliotek, przy wykorzystaniu metod podanych w sekcji Wskazówki implementacyjne. Do obsługi plików BMP należy stworzyć klasę przechowującą zawartość obrazu i udostępniającą następujące metody: bool LoadDIB(CString sciezka_do_pliku) do wczytywania obrazów z plików BMP, bool PaintDIB(HDC kontekst, CRect prost_docelowy, CRect prost_zrodlowy) do wyświetlania wczytanych obrazów, bool CreateGreyscaleDIB(CRect rozmiar_obrazu, int xppm, int yppm) do tworzenia pustych 8-bitowych bitmap w odcieniach szarości o zadanym rozmiarze (PPM oznacza rozdzielczość pixels per meter), bool GetPixel1(int x, int y) do odczytywania wartości pikseli w bitmapach 1-bitowych, BYTE GetPixel8(int x, int y) do odczytywania wartości pikseli w bitmapach 8-bitowych, RGBTRIPLE GetPixel24(int x, int y) do odczytywania wartości pikseli w bitmapach 24-bitowych, bool SetPixel8(int x, int y, BYTE val) do ustawiania wartości pikseli w bitmapach 8-bitowych, bool SaveDIB(CString sciezka_do_pliku) do zapisywania plików BMP. Informacje pomocnicze Format BMP służy do przechowywania map bitowych niezależnych od urządzenia (ang. device independent bitmap, DIB) i występuje w wielu wariantach. Typowy plik BMP ma strukturę opisaną poniżej. 1. Nagłówek pliku BMP, zdefiniowany następująco: tagbitmapfileheader{ UINT bftype; DWORD bfsize; UNIT bfreserved1; UINT bfreserved2; DWORD bfoffbits; } BITMAPFILEHEADER
bftype (2B) typ pliku - to pole jest równe "BM" (dwa znaki w kodzie ASCII) bfsize (4B) rozmiar pliku w bajtach bfreserved1 (2B) zarezerwowane - powinno mieć wartość 0 bfreserved2 (2B) zarezerwowane - powinno mieć wartość 0 bfoffbits (4B) odległość (offset) od początku pliku do właściwej mapy bitowej 2. Struktura BITMAPINFO definiująca rozmiary i kolory mapy bitowej: tagbitmapinfo{ BITMAPINFOHEADER bmiheader; RGBQUAD bmicolors[1]; } BITMAPINFO; bmiheader bmicolors struktura BITMAPINFOHEADER, która zawiera informacje o rozmiarach i formacie kolorów mapy bitowej DIB tablica struktur RGBQUAD definiująca kolory w mapie bitowej Uwaga! Rozmiar struktury BITMAPINFO nie uwzględnia rzeczywistego rozmiaru palety kolorów - bmicolors zawiera tylko jeden element z uwagi na brak możliwości definiowania struktur o zmiennej długości. BITMAPINFO wskazuje jedynie, że paleta (o rozmiarze wynikającym z liczby użytych kolorów) musi następować bezpośrednio po nagłówku BITMAPINFOHEADER. tagbitmapinfoheader{ DWORD bisize; LONG biwidth; LONG biheight; WORD biplanes; WORD bibitcount; DWORD bicompression; DWORD bisizeimage; LONG bixpelspermeter; LONG biypelspermeter; DWORD biclrused; DWORD biclrimportant; } BITMAPINFOHEADER; bfsize (4B) liczba bajtów używanych przez strukturę BITMAPINFOHEADER biwidth (4B) szerokość mapy bitowej w pikselach biheight (4B) wysokość mapy bitowej w pikselach biplanes (2B) to pole powinno być ustawione na 1 bibitcount (2B) liczba bitów na piksel (1, 4, 8 lub 24) bicompression (4B) typ kompresji w przypadku skompresowanej mapy bitowej, dopuszczalne są następujące wartości: BI_RGB (0) - mapa bitowa nie skompresowana, BI_RLE8, BI_RLE4 bisizeimage (4B) rozmiar obrazu w bajtach (powinna być 0 w przypadku formatu BI_RGB) bixpelspermeter (4B) pozioma rozdzielczość w pikselach na metr biypelspermeter (4B) pionowa rozdzielczość w pikselach na metr biclrused (4B) liczba pozycji w tablicy kolorów (gdy wartość tego pola równa się 0, mapa bitowa używa maksymalnej liczby kolorów, odpowiednio do wartości pola bibitcount) biclrimportant (4B) liczba kolorów niezbędnych do wyświetlenia mapy bitowej (gdy pole to jest równe 0, wszystkie kolory są niezbędne) tagrgbquad{ BYTE rgbblue; BYTE rgbgreen; BYTE rgbred; BYTE rgbreserved; } RGBQUAD; rgbblue (1B) intensywność koloru niebieskiego rgbgreen (1B) intensywność koloru zielonego rgbred (1B) intensywność koloru czerwonego rgbreserved (1B) Nieużywane - powinno być równe 0
3. Właściwa mapa bitowa - tablica bajtów Bity w tej tablicy są ułożone kolejno, jednak każda linia obrazu jest w razie potrzeby wydłużona, tak aby jej rozmiar był wielokrotnością typu DWORD (4 bajty). Punkt początkowy mapy bitowej to dolny lewy róg obrazu. BITMAPFILEHEADER BM rozmiar pliku 0 0 odległość od początku pliku do tablicy pikseli BITMAPINFOHEADER rozmiar BITMAPINFOHEADER szerokość wysokość 0 bity na piksel kompresja rozmiar obrazu rozdzielczość pozioma rozdzielczość pionowa liczba użytych kolorów liczba ważnych kolorów TABLICA KOLORÓW (PALETA) kolor 1 kolor n przerwa (opcjonalnie) TABLICA PIKSELI (BITMAPA) ostatnia linia obrazu pierwsza linia obrazu Liczbę bajtów, którą w pamięci zajmuje jedna linia mapy bitowej, można obliczyć korzystając z następującego wzoru: bity _ na _ piksel szerokosc_ obrazu 31 liczba _ bajtow 4 32. W obrazach 24-bitowych nie występuje tablica kolorów (paleta). Odpowiadające pojedynczemu pikselowi 3 kolejne bajty opisują, kolejno, składową niebieską, zieloną i czerwoną (BGR). Pomiędzy tablicą kolorów a właściwą mapą bitową może występować przerwa. Dlatego zawsze należy korzystać z wartości zapisanej w polu bfoffbits struktury BITMAPINFOHEADER.
Wskazówki implementacyjne 1. Zasadniczy kod przykładowego programu (np. reakcje na przyciski) zawiera klasa CPODlg. 2. Wykorzystując tzw. Class View (Ctrl+Shift+C) można łatwo dodać nową klasę (prawy przycisk Add Class). 3. Obsługę operacji na plikach zapewnia dostępna w MFC klasa CFile. Przykładowo: CFile f; f.open(nazwa_pliku, CFile::modeReadWrite)) f.write(wskaznik_na_bufor, liczba_bajtow); f.read(wskaznik_na_bufor, liczba_bajtow); 4. Do przydzielania i zwalniania pamięci należy wykorzystać odpowiednie funkcje systemu Windows: wskaznik_na_bufor = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, rozmiar_bufora); HeapFree(GetProcessHeap(), 0, wskaznik_na_bufor); 5. Do wyświetlania obrazów służy klasa CImgWnd - należy obsłużyć metodę OnPaint(). W interfejsie programu lewe i prawe okno to obiekty klasy CImgWnd (zmienne m_imgin i m_imgout). 6. Aby wymusić odświeżenie zawartości okna typu CImgWnd, czyli wymusić odpowiednie wywołanie metody OnPaint(), należy z poziomu klasy CPODlg użyć metody InvalidateRect, np.: HDC kontekst = dc.getsafehdc(); 7. Obsługując metodę OnPaint() kontekst urządzenia pobieramy w następujący sposób: HDC kontekst = dc.getsafehdc(); 8. Tak zwany sposób rozciągania (ang. stretching mode) definiuje to, jak system łączy dane bitmapy z pikselami już istniejącymi w kontekście urządzenia wyświetlającego. W naszym przypadku ustawiamy tryb, który kopiuje bitmapę do kontekstu usuwając tło: SetStretchBltMode(kontekst, COLORONCOLOR); 9. Do wyświetlania zawartości bitmapy służy poniższa funkcja: StretchDIBits(kontekst, Xdocelowy, Ydocelowy, szer_docelowa, wys_docelowa, Xzrodlowy, Yzrodlowy, szer_zrodlowa, wys_zrodlowa, wskaznik_na_tablice_bajtow, wskaznik_na_bitmapinfo, DIB_RGB_COLORS, SRCCOPY); 10. Aby wyznaczyć rozdzielczość pionową i poziomą (PPM), należy skorzystać z funkcji GetDeviceCaps(), np.: liczba_pikseli_w_poziomie = GetDeviceCaps(kontekst, HORZRES); rozmiar_ekranu_w_poziomie = GetDeviceCaps(hDC, HORZSIZE);
11. Aby ustalić aktualny rozmiar okna, należy posłużyć się funkcją GetClientRect(): CRect r; GetClientRect(r);