Co siedzi w plikach .bmp

󰃭 2024-12-12 (ostatnia edycja: 2024-12-12 )

Czym jest plik .bmp

BMP jest formatem pliku grafiki rastrowej, zwykle używanym w Windowsach. Grafika rastrowa zawiera informacje o każdym z pikseli, zatem można powiedzieć, że jest mapą pikseli. Jak zobaczymy dalej, piksel w BMP jest zazwyczaj zapisywany za pomocą trzech wartości oznaczających kolory BGR. Tak, w tej kolejności.

BMP może być bezstratnie skompresowane za pomocą kompresji RLE, ale nie musi i zazwyczaj nie jest, dlatego w tym wpisie temat zostanie pominięty.

Czego będziemy potrzebować

Mnie pliki .bmp kojarzą się z rysowaniem w Paincie, dlatego też użyję Painta do narysowania 3 prostych grafik.

Przyda się też dowolny hexedytor. Hexedytor jest narzędziem podobnym do notatnika, ale widzimy w nim wartość heksadecymalną każdego znajdującego się w pliku bajtu. Oczywiście oznacza to, że musimy wiedzieć jak działają systemy liczbowe binarny i szesnastkowy.

Obrazki:

LpOpisLiczba bajtówGrafika
1bitmapa 24-bitowa374 Bbitmapa 24-bitowa
2bitmapa monochromatyczna102 Bbitmapa monochromatyczna
3bitmapa 16-kolorowa198 Bbitmapa 16-kolorowa

Możliwe jest także wygenerowanie bitmapy 256-kolorowej, ale działa ona podobnie do 16-kolorowej, więc nie widzę sensu by ją opisywać.

Jak widzisz, obrazki są małe - wszystkie mają wymiary 10x10 px. Dzięki temu możemy przeanalizować ich zawartość bez tonięcia w tysiącach bajtów.

Co siedzi w środku

Podstawowa struktura pliku

W najprostszej postaci plik .bmp składa się z dwóch nagłówków i mapy pikseli. Na samym początku jest nagłówek pliku, zawierający informacje o pliku takie jak jego typ, wielkość i offset. Tuż za nim mamy nagłówek DIB (device-independent bitmap) z danymi samej grafiki. Są tam wymiary obrazka, informacje o kolorach czy liczbie bitów na piksel. Za nagłówkami zlokalizowana jest mapa pikseli.

Trzy wymienione wyżej elementy są obowiązkowe, to znaczy muszą wystąpić zawsze. Nas jednak będzie interesowała jeszcze tablica kolorów, której w przypadku pierwszej grafiki nie będzie, ale w trzech pozostałych już tak.

Nagłówek pliku

Nagłówek pliku zawsze składa się z 14 bajtów, oznaczających kolejno:

  • 2 bajty - sygnatura pliku, tak zwane magic bytes. Dla BMP jest to 42 4D, czyli w ASCII litery BM
  • 4 bajty - rozmiar pliku
  • 2 bajty - zarezerwowane
  • 2 bajty - zarezerwowane
  • 4 bajty - offset, czyli numer bajtu w którym zaczyna się mapa pikseli

Wszystkie wartości liczbowe zapisywane są w postaci little endian - oznacza to w uproszczeniu, że bajty trzeba czytać od prawej do lewej.

Nagłówek DIB

Ten nagłówek może mieć klika różnych wariantów o różnej długości, jednak najczęstszym jest BITMAPINFOHEADER. Jak się wkrótce okaże, każdy z naszych obrazek używa właśnie tego warinatu, dlatego też tylko ten wariant opiszę. Składa się on z 40 bajtów, w których znajdziemy kolejno:

  • 4 bajty - wielkość nagłówka DIB, u nas będzie to 40 (0x28)
  • 4 bajty - szerokość grafiki w pikselach. U nas powinna tu być wartość 10 pikseli (0x0A)
  • 4 bajty - wysokość grafiki w pikselach. Tu również powinniśmy mieć 10 pikseli (0x0A)
  • 2 bajty - liczba warstw kolorów, powinno być 1
  • 2 bajty - liczba bitów na piksel, inaczej głębia koloru, czyli za pomocą ilu bitów opisany jest kolor pojedynczego piksela. Na przykład, w standadowym RGB mamy 3 barwy, z których każda jest opisana przez jeden bajt i może mieć wartość z przedziału 0 - 255 (0x00 - 0xFF). 1 Bajt to 8 bitów, zatem w takim przypadku liczba bitów na piksel to 24 (3 barwy po 8 bitów). Jeżeli ta wartość będzie mniejsza niż 24, to zostanie użyta tablica kolorów.
  • 4 bajty - algorytm kompresji
  • 4 bajty - rozmiar samego obrazka
  • 4 bajty - rozdzielczość pozioma (piksel/metr)
  • 4 bajty - rozdzielczość pionowa (piksel/metr)
  • 4 bajty - liczba kolorów w palecie
  • 1 bajt - liczba ważnych kolorów, domyślnie 0
  • 1 bajt - rotacja palety, domyślnie 0
  • 2 bajty - zarezerwowane

Tablica kolorów

Tablica kolorów jest konieczna gdy liczba bitów na piksel w nagłówku DIB jest mniejsza niż 24. Załóżmy, że mamy tam wartość 2, czyli każdy piksel jest opisany przez 2 bity. 2 Bity mogą mieć następujące wartości:

00
01
10
11

Dostaliśmy 4 możliwości, czyli każdy piksel może mieć jeden z czterech kolorów. Tylko jakich? Żeby to ustalić konieczna jest tablica kolorów. Każdy kolor w tablicy opisany jest przez 4 bajty, BGR oraz pusty bajt, który jest nieistotny. Załóżmy teraz, że przypisujemy kolory następująco:

  • Kolor 0 - 0x000000 => 00 00 00 00
  • Kolor 1 - 0xFF0000 => 00 00 FF 00
  • Kolor 2 - 0xCDB945 => 45 B9 CD 00
  • Kolor 3 - 0x0000FF => FF 00 00 00

W takim przypadku tablica kolorów składa się z 16 bajtów:

00 00 00 00 00 00 FF 00 45 B9 CD 00 FF 00 00 00

Tablica pikseli

Tablica pikseli informuje o kolorze każdego z pikseli. Jeśli liczba bitów na piksel jest równa 24, to każdy z pikseli jest opisany przez trzy bajty, w kolejności kolorów BGR. Czyli jeśli piksel jest opisany przez bajty 46 F5 9D, to jego kolor ma wartości [niebieski = 46, zielony = F5, czerwony = 9D],

Tablicę pikseli można podzielić na linie, których liczba jest taka, jak wysokość grafiki. Jeżeli obrazek ma 400 px wysokości i 240 px szerokości, to jego tablicę pikseli możemy podzielić na 400 linii po 720 bajtów (240 pikseli przemnożone przez 3 bajty). Linie ułozone są w kolejności od dołu, czyli pierwsze 3 bajty opisują piksel po lewej stronie na dole, a ostatnie 3 bajty prawy górny piksel.

Co ważne, liczba bajtów przypadająca na linię musi być podzielna przez 4. W powyższym przykładzie szerokość obrazka to 240 px, a każda z linii zawiera 720 bajtów. 720 jest liczbą podzielną przez 4, więc nie ma problemu. Ale załóżmy, że grafika ma szerokość 250 px. Tę liczbę musimy przemnożyć przez 3 bajty i dostajemy długość linii równą 750 pikseli. 750 nie jest podzielne przez 4, zatem na końcu każdej linii trzeba dodać tak zwany padding, czyli bajty, których wartość nie ma żadnego znaczenia, ale które dopełniają długość linii do wielokrotności czwórki. Najbliższą taką wielokrotnością jest 752, a więc na końcu każdej linii dodane będą dodatkowe 2 bajty o dowolnej wartości, zazwyczaj 00.

Analiza grafiki 1

Grafika 1 to klasyczna bitmapa 24-bitowa. Składa się z 374 bajtów, a ich wartości to:

42 4D 76 01 00 00 00 00 00 00 36 00 00 00 28 00
00 00 0A 00 00 00 0A 00 00 00 01 00 18 00 00 00
00 00 40 01 00 00 C3 0E 00 00 C3 0E 00 00 00 00
00 00 00 00 00 00 E8 A2 00 E8 A2 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 E8 A2
00 E8 A2 00 00 00 E8 A2 00 00 00 00 00 00 00 00
F2 FF 00 F2 FF 00 F2 FF 00 F2 FF 00 00 00 00 00
00 E8 A2 00 00 00 00 00 00 00 00 00 00 F2 FF 00
F2 FF 24 1C ED 24 1C ED 00 F2 FF 00 F2 FF 00 00
00 00 00 00 00 00 00 00 00 00 F2 FF 00 F2 FF 24
1C ED 00 F2 FF 00 F2 FF 24 1C ED 00 F2 FF 00 F2
FF 00 00 00 00 00 00 00 00 00 F2 FF 00 F2 FF 00
F2 FF 00 F2 FF 00 F2 FF 00 F2 FF 00 F2 FF 00 F2
FF 00 00 00 00 00 00 00 00 00 F2 FF 00 F2 FF 7F
7F 7F 00 F2 FF 00 F2 FF 7F 7F 7F 00 F2 FF 00 F2
FF 00 00 00 00 00 00 00 00 00 F2 FF 00 F2 FF 00
F2 FF 00 F2 FF 00 F2 FF 00 F2 FF 00 F2 FF 00 F2
FF 00 00 00 00 00 00 00 00 00 00 00 00 F2 FF 00
F2 FF 00 F2 FF 00 F2 FF 00 F2 FF 00 F2 FF 00 00
00 00 00 00 00 00 E8 A2 00 00 00 00 00 00 00 00
F2 FF 00 F2 FF 00 F2 FF 00 F2 FF 00 00 00 00 00
00 E8 A2 00 00 00 E8 A2 00 E8 A2 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 E8 A2
00 E8 A2 00 00 00

Nagłówek pliku

Nagłówek pliku to zawsze 14 bajtów, a zatem oto on:

42 4D 76 01 00 00 00 00 00 00 36 00 00 00
  • 42 4D = BM - sygnatura pliku
  • 76 01 00 00 = 374 - wielkość pliku. Przypominam, że wartości liczbowe zapisane są w little-endian, czyli ta liczba to tak naprawdę 0x0176
  • 00 00- pole zarezerwowane, dla nas bez znaczenia
  • 00 00- pole zarezerwowane, dla nas bez znaczenia
  • 36 00 00 00 = 54 - offset, oznacza że tablica pikseli zaczyna się w 54-tym bajcie (licząc od 0)

Nagłówek DIB

Nagłówek DIB będzie miał 40 bajtów, o czym dowiemy się za chwilę. Oto on:

28 00 00 00 0A 00 00 00 0A 00 00 00 01 00 18 00
00 00 00 00 40 01 00 00 C3 0E 00 00 C3 0E 00 00
00 00 00 00 00 00 00 00
  • 28 00 00 00 = 40 - wielkość nagłówka DIB
  • 0A 00 00 00 = 10 - szerokość obrazka
  • 0A 00 00 00 = 10 - wysokość obrazka
  • 01 00 = 1 - liczba warstw kolorów
  • 18 00 = 24 - liczba bitów na piksel. Mamy tu 24, czyli każdy z kanałów RGB będzie opisany jednym bajtem. To z kolei oznacza, że nie ma potrzeby tworzyć tablicy kolorów
  • 00 00 00 00 - algorytm kompresji. Nie ma tu kompresji
  • 40 01 00 00 = 0x0140 = 320 - rozmiar samego obrazka. Zgadza się, szerokość obrazka to 10 pikseli, każdy piksel opisany jest przez 3 bajty. Mamy więc 30 bajtów opisujących piksele w każdej linii. Do tego musimy dodać 2 bajty paddingu, żeby długość linii była podzielna przez 4. Zatem mamy 32 bajty razy 10 linii (wysokość grafiki)
  • C3 0E 00 00 = 0x0EC3 = 3779 - rozdzielczość pozioma (piksel/metr)
  • C3 0E 00 00 = 0x0EC3 = 3779 - rozdzielczość pionowa (piksel/metr)
  • 00 00 00 00 - liczba kolorów w palecie
  • 00 - liczba ważnych kolorów
  • 00 - rotacja palety
  • 00 00 - zarezerwowane

Tablica pikseli

Skoro liczba bitów na piksel jest równa 24, od razu po nagłówkach mamy tablicę pikseli. Wygląda ona tak:

E8 A2 00 E8 A2 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 E8 A2 00 E8 A2 00 00 00  <-- linia 0
E8 A2 00 00 00 00 00 00 00 00 F2 FF 00 F2 FF 00
F2 FF 00 F2 FF 00 00 00 00 00 00 E8 A2 00 00 00  <-- linia 1
00 00 00 00 00 00 00 F2 FF 00 F2 FF 24 1C ED 24
1C ED 00 F2 FF 00 F2 FF 00 00 00 00 00 00 00 00  <-- linia 2
00 00 00 00 F2 FF 00 F2 FF 24 1C ED 00 F2 FF 00
F2 FF 24 1C ED 00 F2 FF 00 F2 FF 00 00 00 00 00  <-- linia 3
00 00 00 00 F2 FF 00 F2 FF 00 F2 FF 00 F2 FF 00
F2 FF 00 F2 FF 00 F2 FF 00 F2 FF 00 00 00 00 00  <-- linia 4
00 00 00 00 F2 FF 00 F2 FF 7F 7F 7F 00 F2 FF 00
F2 FF 7F 7F 7F 00 F2 FF 00 F2 FF 00 00 00 00 00  <-- linia 5
00 00 00 00 F2 FF 00 F2 FF 00 F2 FF 00 F2 FF 00
F2 FF 00 F2 FF 00 F2 FF 00 F2 FF 00 00 00 00 00  <-- linia 6
00 00 00 00 00 00 00 F2 FF 00 F2 FF 00 F2 FF 00
F2 FF 00 F2 FF 00 F2 FF 00 00 00 00 00 00 00 00  <-- linia 7
E8 A2 00 00 00 00 00 00 00 00 F2 FF 00 F2 FF 00
F2 FF 00 F2 FF 00 00 00 00 00 00 E8 A2 00 00 00  <-- linia 8
E8 A2 00 E8 A2 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 E8 A2 00 E8 A2 00 00 00  <-- linia 9

Dla komfortu czytania wartości bajtów umieszczone są w 16 kolumnach. Każdy wiersz ma mieć 32 bajty, więc akurat wychodzą po dwa wiersze na linię. Nie ma sensu analizować każdej z linii, więc zajmijmy się tylko jedną, na przykład linią 2 (licząc od 0).

00 00 00 00 00 00 00 F2 FF 00 F2 FF 24 1C ED 24
1C ED 00 F2 FF 00 F2 FF 00 00 00 00 00 00 00 00

Piksele czytamy od lewej do prawej, ale w kolejności BGR, skąd wnioskuję, że tu też mamy little-endian. Przypominam też, że jest to linia w pliku zapisana jako trzecia, a na obrazku jest to trzeci rząd pikseli od dołu.

Pierwsze dwa piksele są czarne, mają wartości 00 00 00, czyli w RGB 0x000000. Piksel trzeci ma kolor 00 F2 FF = 0xFFF200 - mocna mieszanka czerwonego z zielonym, zatem żółty. I tak dalej. Ostatnie dwa piksele to znowu kolor czarny, a po nich dwa dodatkowe bajty paddingu o wartościach 00 00.

Analiza grafiki 2

Grafika 2 jest monochromatyczna, składa się tylko z dwóch kolorów. Złożona jest ze 102 bajtów, a wyglądają one tak:

42 4D 66 00 00 00 00 00 00 00 3E 00 00 00 28 00
00 00 0A 00 00 00 0A 00 00 00 01 00 01 00 00 00
00 00 28 00 00 00 C3 0E 00 00 C3 0E 00 00 00 00
00 00 00 00 00 00 00 00 00 00 FF FF FF 00 C0 C0
00 00 9E 40 00 00 33 00 00 00 6D 80 00 00 7F 80
00 00 6D 80 00 00 7F 80 00 00 3F 00 00 00 9E 40
00 00 C0 C0 00 00

Nagłówek pliku

Na początku znowu mamy 14 bajtów nagłówka, który wygląda niemal tak samo jak w przypadku grafiki 1. Jedyną różnicą jest wielkość pliku, która tutaj wynosi 66 00 00 00, czyli 102.

Nagłówek DIB

Nagłówek DIB grafiki 2 znowu ma długość 40 bajtów, z których większość jest taka sam jak w przypadku grafiki 1.

28 00 00 00 0A 00 00 00 0A 00 00 00 01 00 01 00
00 00 00 00 28 00 00 00 C3 0E 00 00 C3 0E 00 00
00 00 00 00 00 00 00 00

Różnią się natomiast następujące wartości:

  • 01 00 = 1 - liczba bitów na piksel. Każdy piksel opisany będzie jednym bitem, który może mieć wartość 0 lub 1.
  • 28 00 00 00 = 40 - wielkość obrazka. Skąd taka liczba? Tutaj też długość linii liczona w bajtach musi być podzielna przez 4. Każdy piksel opisany jest jednym bitem, zatem wystarczą tylko 2 bajty żeby opisać kaźdy z pikseli. Kolejne 2 to padding. 4 bajty razy 10 linii = 40 bajtów.

Tablica kolorów

Liczba bitów potrzebnych do opisania piksela jest mniejsza niż 24, a więc potrzebna jest tablica kolorów. Ile możemy mieć tutaj kolorów? Skoro tylko jeden bit odpowiada za opisanie piksela, to wychodzi że kolory mogą być dwa.

00 00 00 00 FF FF FF 00

Każdy kolor opisany jest przez 4 bajty - B, G, R oraz… alfa? Ta ostatnia wartość nie ma znaczenia i raczej będzie wynosiła 0. Edytowanie jej niczego nie zmienia. Za to edycja pozostałych wartości już tak. Jak widać teraz kolor 0 ma wartość 0x000000, czyli czerń, a kolor 1 0xFFFFFF, czyli biel. Można jednak te wartości edytować i zamiast obrazka czarno-białego mieć obrazek na przykład czerwono-zielony.

Tablica pikseli

Skoro znamy już wartości kolorów 0 i 1, to możemy przejść do damej grafiki. Wygląda ona tak:

C0 C0 00 00
9E 40 00 00
33 00 00 00
6D 80 00 00
7F 80 00 00
6D 80 00 00
7F 80 00 00
3F 00 00 00
9E 40 00 00
C0 C0 00 00

Tym razem pogrupowałem bajty w 4 kolumny, aby każdy wiersz odpowiadał jednej linii. Jak już ustaliliśmy, do opisu całego wiersza wystarczą 2 bajty, które dają nam 16 bitów. I tak już mamy 6 bitów zapasu, ale musimy dodać po dwa bajty paddingu. Zapiszmy teraz to samo, ale w postaci bitowej:

| grafika  |      | padding        |
 1100000011 000000 0000000000000000 
 1001111001 000000 0000000000000000 
 0011001100 000000 0000000000000000 
 0110110110 000000 0000000000000000
 0111111110 000000 0000000000000000 
 0110110110 000000 0000000000000000 
 0111111110 000000 0000000000000000 
 0011111100 000000 0000000000000000
 1001111001 000000 0000000000000000 
 1100000011 000000 0000000000000000

Wartość 0 w grafice odpowiada kolorowi 0 (czarny), a wartość 1 kolorowi 1 (białemu). Jako miły akcent, czysto dla zabawy, podmieńmy wszytskie 1 na znak .. Dostajemy naszą grafikę do góry nogami, bo przecież linie są umieszczone w pliku od ostatniej do pierwszej:

..000000..
.00....00.
00..00..00
0..0..0..0
0........0
0..0..0..0
0........0
00......00
.00....00.
..000000..

Analiza grafiki 3

Tym razem mamy do czynienia z grafiką zawierającą 16 kolorów. Będzie ona działała podobnie do wersji monochromatycznej z tym, że tablica kolorów będzie dłuższa. Myślę, że nie ma sensu ponownie omawiać nagłówka pliku, więc przejdziemy od razu do wyciągnięcia ważnych informacji z nagłówka DIB. Ale najpierw zobaczmy same bajty, których tutaj jest 198:

42 4D C6 00 00 00 00 00 00 00 76 00 00 00 28 00
00 00 0A 00 00 00 0A 00 00 00 01 00 04 00 00 00
00 00 50 00 00 00 C3 0E 00 00 C3 0E 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 80
00 00 00 80 80 00 80 00 00 00 80 00 80 00 80 80
00 00 80 80 80 00 C0 C0 C0 00 00 00 FF 00 00 FF
00 00 00 FF FF 00 FF 00 00 00 FF 00 FF 00 FF FF
00 00 FF FF FF 00 66 00 00 00 66 00 00 00 60 08
88 80 06 00 00 00 00 BB 33 BB 00 00 00 00 08 B3
BB 3B 80 00 00 00 08 BB BB BB 80 00 00 00 08 B3
BB 3B B0 00 00 00 08 BB BB BB 80 00 00 00 00 BB
BB BB 00 00 00 00 60 08 BB 80 06 00 00 00 66 00
00 00 66 00 00 00

Nagłówek DIB

28 00 00 00 0A 00 00 00 0A 00 00 00 01 00 04 00
00 00 00 00 50 00 00 00 C3 0E 00 00 C3 0E 00 00
00 00 00 00 00 00 00 00

Spójrzmy na liczbę bitów na piksel. Tutaj wynosi ona 04 00, tak więc każdy piksel opisujemy 4 bitami. 4 bity mogą nam dać w sumie 16 wartości (0 - 15) i te 16 wartości trzeba rozpisać teraz w tablicy kolorów.

Tablica kolorów

00 00 00 00 00 00 80 00 00 80 00 00 00 80 80 00
80 00 00 00 80 00 80 00 80 80 00 00 80 80 80 00
C0 C0 C0 00 00 00 FF 00 00 FF 00 00 00 FF FF 00
FF 00 00 00 FF 00 FF 00 FF FF 00 00 FF FF FF 00

Tak jak poprzednio, tu też każdy z kolorów 0 - 15 ma przypisaną wartość RGB (+ pusty bajt).

I tak kolor 0 ma wartość 0x000000, kolor 1 0x080000, kolor 2 0x000800, i tak dalej. Oczywiście wartości można edytować na dowolne inne, jednak ich liczba zawsze będzie ograniczona do 16.

Tablica pikseli

Skoro już wiemy jaki kolor odpowiada jakiej liczbie z przedziału 0 - 15, to wiemy jak interpretować poszczególne piksele.

66 00 00 00 66 00 00 00 
60 08 88 80 06 00 00 00
00 BB 33 BB 00 00 00 00 
08 B3 BB 3B 80 00 00 00
08 BB BB BB 80 00 00 00 
08 B3 BB 3B B0 00 00 00
08 BB BB BB 80 00 00 00 
00 BB BB BB 00 00 00 00
60 08 BB 80 06 00 00 00 
66 00 00 00 66 00 00 00

Tutaj bardzo łatwo możemy odczytać wartości opisujące piksele. Pierwsze dwa piksele mają kolor 6, potem kolejne sześć pikseli kolor 0, a kolejne dwa znowu 6. Ostatnie 3 bajty w linii to padding.