Format pliku graficznego typu bmp

Materiały

Opis formatu pliku można znaleźć bez problemu w sieci. Poniżej podaję kilka przykładowych adresów, które znalazłem

  1. http://www.fastgraph.com/help/bmp_header_format.html
    wersja lokalna:bmp_header_format.html
  2. http://web.uccs.edu/wbahn/ECE1021/STATIC/REFERENCES/bmpfileformat.htm
    wersja lokalna:bmpfileformat.htm
  3. http://local.wasp.uwa.edu.au/~pbourke/dataformats/bmp/
  4. http://en.wikipedia.org/wiki/Windows_bitmap

Opis formatu pliku

Wprowadzenie

Pliki BMP są jednymi z prostszych plików służących do zapisu obrazów graficznych. Wykorzystywane mogą być do przechowywania szerokiej gamy obrazów poczynając od 1-bitowych (czyli czarno-białych) na 24-bitowych (czyli zawir erających 16 777 216 kolorów) kończąc. Jest to bezstratny format zapisu danych i choć teoretycznie możliwy jest zapis obrazu poddanego kompresji to zwykle się tego nie stosuje.

Ogólna struktura pliku

W pliku BMP możemy wyróżnić maksymalnie 4 części choć nie zawsze wszystkie występują. I tak w kolejności ich występowania są to
  1. nagłówek pliku - przechowuje najważniejsze informacje związane z plikiem jako takim;
  2. nagłówek obrazu - przechowuje informacje o obrazie;
  3. paleta kolorów - definiuje wykorzystane kolory (nie zawsze występuje);
  4. dane obrazu.
W opisie przyjmujemy następujące, tradycyjnie używane, definicje Ponadto zakładamy, że kolejność bajtów w zmiennej word i dword jest kolejnością typową dla architektury intelowskiej, tj. mniej znaczący bajt umieszczony jest jako pierwszy bajt z lewej strony (tzw. little endian).

Nagłówek pliku (czerwony w poniższym przykładzie)

typedef struct
{
  word bfType;
  dword bfSize;
  word bfReserved1;
  word bfReserved2;
  dword bfOffBits;
}
FileHeader;

Nagłówek obrazu (niebieski w poniższym przykładzie)

typedef struct
{
  dword biSize;
  dword biWidth;
  dword biHeight;
  word biPlanes;
  word biBitCount;
  dword biCompression;
  dword biSizeImage;
  dword biXPelsPerMeter;
  dword biYPelsPerMeter;
  dword biClrUsed;
  dword biClrImportant;
}
PictureHeader;

Paleta kolorów

Dane obrazu (czarny w poniższym przykładzie)

Ilość bajtów opisujących wiersz obrazu musi być podzielna przez 4. Jeśli tak nie jest to na koniec każdego wiersza dodawane są bajty o wartości 0 w takiej ilości aby tak zmieniona ilość bajtów w wierszu była podzielna przez 4.

Dane o pixelach zapisywane są od lewej do prawej i od dołu do góry obrazka.

Zauważmy jeszcze że składowe kolorów reprezentowane są ,,od końca'', czyli jeśli w pliku mamy ff 00 00 to oznacza to kolor 00 00 ff.

Przykład 1

Przeanalizujmy bardzo prosty obrazek 24-bitowy (czyli na zapisanie koloru jednego pixela żywać będziemy 24 bitów):

(obrazek widoczny na tej stronie jest powiększony 6 razy: z 5x5 do 30x30).

Oto zawartość pliku przedstawiona w postaci liczb szesnastkowych (przypominam, że dwie cyfry szesnastkowe reprezentują jeden bajt):

                                  1  1  1  1  1  1
    0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5

0: 42 4d 86 00 00 00 00 00 00 00 36 00 00 00 28 00

1: 00 00 05 00 00 00 05 00 00 00 01 00 18 00 00 00

2: 00 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00

3: 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00

4: 00 00 00 00 00 00 00 00 00 00 ff ff ff ff ff 00

5: ff 00 00 00 00 00 00 00 00 ff ff ff ff ff ff ff

6: ff ff 00 00 00 00 00 00 00 00 00 ff ff ff ff ff

7: ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

8: 00 00 00 00 00 00
A teraz spróbujmy odczytać zawartość obrazka ( zapis (i,j) oznacza bajt leżący w i-tym wierszu i j-tej kolumnie)
(0,0)-(0,1): pole bfType, zawartość pola: 42 4d co oznacza odpowiednio BM.(2,2)-(2,5): pole biSizeImage
(0,2)-(0,5): pole bfSize, zawartość pola: 86 00 00 00 co daje dziesiętną wartość liczbową 134 która jest równa wielkości pliku.
(0,6)-(0,7): pole bfReserved1, zawartość pola: 00 00 co daje dziesiętną wartość liczbową 0.
(0,8)-(0,9): pole bfReserved2, zawartość pola: 00 00 co daje dziesiętną wartość liczbową 0.
(0,10)-(0,13): pole bfOffBits, zawartość pola: 36 00 00 00 co daje dziesiętną wartość liczbową 54 a zatem blok
z danymi obrazka powinien zacząć się od bajtu (3,6).

(0,14)-(1,1): pole biSize, zawartość pola: 28 00 00 00 co daje dziesiętną wartość liczbową 40 a zatem dlugosc tego naglowka wynosi 40 bajtow.
czyli od pola (3,6) powinna zacząć się paleta kolorów jeśli będzie występować lub dane obrazka (porównaj z wartością wyliczoną dla pola bfOffBits).
(1,2)-(1,5): pole biWidth, zawartość pola: 05 00 00 00 co daje dziesiętną wartość liczbową 5 a zatem szerokość bitmapy wynosi 5 pixeli.
(1,6)-(1,9): pole biHeight, zawartość pola: 05 00 00 00 co daje dziesiętną wartość liczbową 5 a zatem wysokość bitmapy wynosi 5 pixeli.
(1,10)-(1,11): pole biPlanes, zawartość pola: 01 00 co daje dziesiętną wartość liczbową 1, która zgodna jest z podaną powyżej specyfikacją 
(1,12)-(1,13): pole biBitCount, zawartość pola: 18 00 co daje dziesiętną wartość liczbową 24 a zatem w pliku nie będzie przechowywana paleta kolorów.  
(1,14)-(2,1): pole biCompression, zawartość pola: 00 00 00 00 co daje dziesiętną wartość liczbową 0 a zatem jest to plik nieskompresowany.
(2,2)-(2,5): pole biSizeImage, zawartość pola: 50 00 00 00 co daje dziesiętną wartość liczbową 80
a zatem obrazek powinien być opisany przez 80 bajtów (uwzględniając ewentualne dopełnienia wynikające z konieczności zapewnienia, iż ilość bajtów w linii podzielna jest przez 4)
(2,6)-(2,9): pole biXPelsPerMeter, zawartość pola: 00 00 00 00 co daje dziesiętną wartość liczbową 0.
(2,10)-(2,13): pole biYPelsPerMeter, zawartość pola: 00 00 00 00 co daje dziesiętną wartość liczbową 0.
(2,14)-(3,1): pole biClrUsed, zawartość pola: 00 00 00 00 co daje dziesiętną wartość liczbową 0.
(3,2)-(3,5): pole biClrImportant, zawartość pola: 00 00 00 00 co daje dziesiętną wartość liczbową 0.

Wartość 0 w ostatnich dwóch polach nie powinna dziwić. Jest to tylko dodatkowa informacja dla systemów, które nie mogą operować całą gamą kolorów. Powinna wówczas pomóc w wyborze ,,ważniejszych'' kolorów. Wartość 0 w tych polach zwykle rozumiana jest w ten sposób, że wszystkie kolory są istotne.

Zgodnie z tym co policzono powyżej od tego pola zaczynają się dane obrazka. Palety kolorów nie ma gdyż jest to obrazek 24-bitowy.

Zgodnie z tym co wyliczono powyżej mamy 24-bitowy obrazek (zatem 3 bajty na pixel). Pixeli w linii jest 5, co łącznie daje 5x3=15 bajtów.
Liczbę tą musimy dopełnić jeszcze tyloma bajtami aby uzyskać liczbę podzielną przez 4 - w tym wypadku wystarczy 1 bajt co łącznie daje 16 bajtów na linie.

Dlatego bajty jakie nam pozostały podzielone zostały na bloki po 16. Łącznie wyszło 5 pełnych bloków co zgodne jest z ilością wierszy w naszym obrazku. Zgodne jest to także z liczbą 80 zapisaną w polu biSizeImage (od (2,2) do (2,5)).

Pamiętając o tym, że dane zapisywane są od lewego dolnego rogu spróbujemy teraz utworzyć obrazek
(przyjmujemy numeracje pixeli zaczynającą się od 0 w lewym górnym rogu)
(3,6)-(3,8): zawartość pola: 00 00 00 = RGB(0,0,0) = czarny, pixel (4,0)
(3,9)-(3,11): zawartość pola: 00 00 00 = RGB(0,0,0) = czarny, pixel (4,1)
(3,12)-(3,14): zawartość pola: 00 00 00 = RGB(0,0,0) = czarny, pixel (4,2)
(3,15)-(4,1): zawartość pola: 00 00 00 = RGB(0,0,0) = czarny, pixel (4,3)
(4,2)-(4,4): zawartość pola: 00 00 00 = RGB(0,0,0) = czarny, pixel (4,4)
(4,5): zawartość pola: 00 - dopełnienie
(4,6)-(4,8): zawartość pola: 00 00 00 = RGB(0,0,0) = czarny, pixel (3,0)
(4,9)-(4,11): zawartość pola: 00 ff ff = RGB(255,255,0) = zółty, pixel (3,1)
(4,12)-(4,14): zawartość pola: ff ff ff = RGB(255,255,255) = biały, pixel (3,2)
(4,15)-(5,1): zawartość pola: 00 ff 00 = RGB(0,255,0) = zielony, pixel (3,3)
(5,2)-(5,4): zawartość pola: 00 00 00 = RGB(0,0,0) = czarny, pixel (3,4)
(5,5): zawartość pola: 00 - dopełnienie
(5,6)-(5,8): zawartość pola: 00 00 00 = RGB(0,0,0) = czarny, pixel (2,0)
(5,9)-(5,11): zawartość pola: ff ff ff = RGB(255,255,255) = biały, pixel (2,1)
(5,12)-(5,14): zawartość pola: ff ff ff = RGB(255,255,255) = biały, pixel (2,2)
(5,15)-(6,1): zawartość pola: ff ff ff = RGB(255,255,255) = biały, pixel (2,3)
(6,2)-(6,4): zawartość pola: 00 00 00 = RGB(0,0,0) = czarny, pixel (2,4)
(6,5): zawartość pola: 00 - dopełnienie
(6,6)-(6,8): zawartość pola: 00 00 00 = RGB(0,0,0) = czarny, pixel (1,0)
(6,9)-(6,11): zawartość pola: 00 00 ff = RGB(255,0,0) = czerwony, pixel (1,1)
(6,12)-(6,14): zawartość pola: ff ff ff = RGB(255,255,255) = biały, pixel (1,2)
(6,15)-(7,1): zawartość pola: ff ff 00 = RGB(0,255,255) = jasny niebieski, pixel (1,3)
(7,2)-(7,4): zawartość pola: 00 00 00 = RGB(0,0,0) = czarny, pixel (1,4)
(7,5): zawartość pola: 00 - dopełnienie
(7,6)-(7,8): zawartość pola: 00 00 00 = RGB(0,0,0) = czarny, pixel (0,0)
(7,9)-(7,11): zawartość pola: 00 00 00 = RGB(0,0,0) = czarny, pixel (0,1)
(7,12)-(7,14): zawartość pola: 00 00 00 = RGB(0,0,0) = czarny, pixel (0,2)
(7,15)-(8,2): zawartość pola: 00 00 00 = RGB(0,0,0) = czarny, pixel (0,3)
(8,2)-(8,4): zawartość pola: 00 00 00 = RGB(0,0,0) = czarny, pixel (0,4)
(8,5): zawartość pola: 00 - dopełnienie

Przykład 2

Przeanalizujmy teraz ten sam obrazek ale wykorzystujący 8-bitową definicję koloru: Oto zawartość pliku przedstawiona w postaci liczb szesnastkowych:
                                  1  1  1  1  1  1
    0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    
 0:42 4d 5e 04 00 00 00 00 00 00 36 04 00 00 28 00

 1:00 00 05 00 00 00 05 00 00 00 01 00 08 00 00 00
 
 2:00 00 28 00 00 00 00 00 00 00 00 00 00 00 00 00
 
 3:00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 80
 
 4:00 00 00 80 80 00 80 00 00 00 80 00 80 00 80 80
 
 5:00 00 c0 c0 c0 00 c0 dc c0 00 f0 ca a6 00 00 20
 
 6:40 00 00 20 60 00 00 20 80 00 00 20 a0 00 00 20
 
 7:c0 00 00 20 e0 00 00 40 00 00 00 40 20 00 00 40
 
 8:40 00 00 40 60 00 00 40 80 00 00 40 a0 00 00 40

 9:c0 00 00 40 e0 00 00 60 00 00 00 60 20 00 00 60
 
10:40 00 00 60 60 00 00 60 80 00 00 60 a0 00 00 60

11:c0 00 00 60 e0 00 00 80 00 00 00 80 20 00 00 80

12:40 00 00 80 60 00 00 80 80 00 00 80 a0 00 00 80

13:c0 00 00 80 e0 00 00 a0 00 00 00 a0 20 00 00 a0

14:40 00 00 a0 60 00 00 a0 80 00 00 a0 a0 00 00 a0

15:c0 00 00 a0 e0 00 00 c0 00 00 00 c0 20 00 00 c0

16:40 00 00 c0 60 00 00 c0 80 00 00 c0 a0 00 00 c0

17:c0 00 00 c0 e0 00 00 e0 00 00 00 e0 20 00 00 e0 

18:40 00 00 e0 60 00 00 e0 80 00 00 e0 a0 00 00 e0

19:c0 00 00 e0 e0 00 40 00 00 00 40 00 20 00 40 00

20:40 00 40 00 60 00 40 00 80 00 40 00 a0 00 40 00

21:c0 00 40 00 e0 00 40 20 00 00 40 20 20 00 40 20

22:40 00 40 20 60 00 40 20 80 00 40 20 a0 00 40 20

23:c0 00 40 20 e0 00 40 40 00 00 40 40 20 00 40 40

24:40 00 40 40 60 00 40 40 80 00 40 40 a0 00 40 40

25:c0 00 40 40 e0 00 40 60 00 00 40 60 20 00 40 60

26:40 00 40 60 60 00 40 60 80 00 40 60 a0 00 40 60

27:c0 00 40 60 e0 00 40 80 00 00 40 80 20 00 40 80

28:40 00 40 80 60 00 40 80 80 00 40 80 a0 00 40 80

29:c0 00 40 80 e0 00 40 a0 00 00 40 a0 20 00 40 a0

30:40 00 40 a0 60 00 40 a0 80 00 40 a0 a0 00 40 a0

31:c0 00 40 a0 e0 00 40 c0 00 00 40 c0 20 00 40 c0

32:40 00 40 c0 60 00 40 c0 80 00 40 c0 a0 00 40 c0

33:c0 00 40 c0 e0 00 40 e0 00 00 40 e0 20 00 40 e0

34:40 00 40 e0 60 00 40 e0 80 00 40 e0 a0 00 40 e0

35:c0 00 40 e0 e0 00 80 00 00 00 80 00 20 00 80 00

36:40 00 80 00 60 00 80 00 80 00 80 00 a0 00 80 00

37:c0 00 80 00 e0 00 80 20 00 00 80 20 20 00 80 20

38:40 00 80 20 60 00 80 20 80 00 80 20 a0 00 80 20

39:c0 00 80 20 e0 00 80 40 00 00 80 40 20 00 80 40

40:40 00 80 40 60 00 80 40 80 00 80 40 a0 00 80 40

41:c0 00 80 40 e0 00 80 60 00 00 80 60 20 00 80 60

42:40 00 80 60 60 00 80 60 80 00 80 60 a0 00 80 60

43:c0 00 80 60 e0 00 80 80 00 00 80 80 20 00 80 80

44:40 00 80 80 60 00 80 80 80 00 80 80 a0 00 80 80

45:c0 00 80 80 e0 00 80 a0 00 00 80 a0 20 00 80 a0

46:40 00 80 a0 60 00 80 a0 80 00 80 a0 a0 00 80 a0

47:c0 00 80 a0 e0 00 80 c0 00 00 80 c0 20 00 80 c0

48:40 00 80 c0 60 00 80 c0 80 00 80 c0 a0 00 80 c0

49:c0 00 80 c0 e0 00 80 e0 00 00 80 e0 20 00 80 e0

50:40 00 80 e0 60 00 80 e0 80 00 80 e0 a0 00 80 e0

51:c0 00 80 e0 e0 00 c0 00 00 00 c0 00 20 00 c0 00

52:40 00 c0 00 60 00 c0 00 80 00 c0 00 a0 00 c0 00

53:c0 00 c0 00 e0 00 c0 20 00 00 c0 20 20 00 c0 20

54:40 00 c0 20 60 00 c0 20 80 00 c0 20 a0 00 c0 20

55:c0 00 c0 20 e0 00 c0 40 00 00 c0 40 20 00 c0 40

56:40 00 c0 40 60 00 c0 40 80 00 c0 40 a0 00 c0 40

57:c0 00 c0 40 e0 00 c0 60 00 00 c0 60 20 00 c0 60

58:40 00 c0 60 60 00 c0 60 80 00 c0 60 a0 00 c0 60

59:c0 00 c0 60 e0 00 c0 80 00 00 c0 80 20 00 c0 80

60:40 00 c0 80 60 00 c0 80 80 00 c0 80 a0 00 c0 80

61:c0 00 c0 80 e0 00 c0 a0 00 00 c0 a0 20 00 c0 a0

62:40 00 c0 a0 60 00 c0 a0 80 00 c0 a0 a0 00 c0 a0

63:c0 00 c0 a0 e0 00 c0 c0 00 00 c0 c0 20 00 c0 c0

64:40 00 c0 c0 60 00 c0 c0 80 00 c0 c0 a0 00 f0 fb

65:ff 00 a4 a0 a0 00 80 80 80 00 00 00 ff 00 00 ff

66:00 00 00 ff ff 00 ff 00 00 00 ff 00 ff 00 ff ff

67:00 00 ff ff ff 00 00 00 00 00 00 00 00 00 00 fb

68:ff fa 00 00 00 00 00 ff ff ff 00 00 00 00 00 f9

69:ff fe 00 00 00 00 00 00 00 00 00 00 00 00 

Przykład 3

Przeanalizujmy jeszcze raz ten sam obrazek, ale wykorzystując 4-bitową definicję koloru: Oto zawartość pliku przedstawiona w postaci liczb szesnastkowych:
                                  1  1  1  1  1  1
    0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    
0: 42 4d 8a 00 00 00 00 00 00 00 76 00 00 00 28 00

1: 00 00 05 00 00 00 05 00 00 00 01 00 04 00 00 00

2: 00 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00

3: 00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 80

4: 00 00 00 80 80 00 80 00 00 00 80 00 80 00 80 80

5: 00 00 80 80 80 00 c0 c0 c0 00 00 00 ff 00 00 ff

6: 00 00 00 ff ff 00 ff 00 00 00 ff 00 ff 00 ff ff

7: 00 00 ff ff ff 00 00 00 00 00 0b fa 00 00 0f ff

8: 00 00 09 fe 00 00 00 00 00 00