Portfolio[UWM] Techniki Multimedialne / PIRO

Przedstawienie formatu BMP.
Funkcje Load_BMP i Save_BMP


Zasadniczo nie wszyscy rozumieją pojęcia wczytywania plików. Istnieje ogrom różnych formatów plików, przykładowo  na co-dzień używamy jpg, png, txt, itp. Jednakże, jedna rzecz jest wspólna dla każdego pliku, a jest to, że każdy plik można było by nazwać zbiorem bajtów. Które tworzą odpowiednią strukturę, gdzie jakiś program ją interpretuje odpowiednio.

Weźmy mały przykład, chociażby:


możemy, zauważyć obrazek który jest 24-bitową bitmapą, o rozmiarach 32x32.

Ok, wizualnie my to znamy... jednakże, jak to widzi komputer ?
Odpowiednim programem lub też Hex Editor Online możemy zobaczyć plik w formie zbioru bajtów, przeważnie zapisując ją w formacie Hex (system szesnastkowym), przykładowo :


Zasadniczo, jak to niekiedy jest ... tzn. są ludzie którzy mieszkają "w garażu" i analizują pewne pliki, a potem powstają takie rzeczy jak w Word -> Drukarka -> PDF ^^

Widzimy powyżej kawałek naszego obrazka. Jednakże co z nim dalej ?
Ponieważ wczytanie całego pliku, niezależnie jaki to jest format - jest proste. Czytam do jakiejś tablicy i mamy...
Wracając do naszego obrazka, posiadamy pewną wiedzę. Naszą wiedza jest to, że możemy dość wprost sposób dowiedzieć się jak jest zbudowany bmp, tzn:

Przedstawiając to na naszym obrazku wygląda to tak :

Obszar, pomarańczowy jest zbiorem danych o pikselach, tylko trzeba uważać, w 24-bitowych bitmapach, każda trójka jest jednym pikselem. Każdy bajt w takiej trójce to dana składowa R G B.

Uwaga!

Kiedy szerokość obrazka nie jest podzielne przez 4, trzeba dopełniać, przykładowo:
posiadamy obrazek 24-bitowy o wymiarach 6x2, czyli jeden wiersz wygląda :
[RR GG BB] [
RR GG BB] [RR GG BB][RR GG BB] [RR GG BB] [RR GG BB] ZZ ZZ
na dany piksel przypada 3 bajty, więc posiadamy 6*3=18 bajtów w wierszu czyli nie jest to podzielne przez 4, czyli musimy uwzględnić dodatkowe 2 bajty ZZ ZZ


Color Table (niebieski pasek) jest pusty ponieważ, jest to tablica kolorów, która jest używana w 1, 8, 16-bitowych bitmapach, każdy kolor leżący w takiej tablicy jest z 4 bajtów, RGB i reserved, tzn. działanie tego wygląda tak:
  • 1-bitowych
    każdy bajt w obszarze pomarańczowym (pikseli) oznacza tylko tyle, że każdy taki jeden bajt przechowuje informacje o 8 pikselach, innym słowem trzeba odpowiednio wczytywać bity.
    taki bit ma wartość 0 lub 1, czyli w tablicy kolorów mamy dwa kolory o indeksach 0 i 1
  • 8-bitowe
    tutaj mamy sprawę ułatwioną każdy taki bajt w obszarze pomarańczowym, przechowuje informacje o jednym pikselu, czyli jak mamy obrazek o wymiarach 6x2 to musimy dopełnić w każdym wierszu do 8 bajtów, ponieważ szerokość jest równa 6 czyli 6 bajtów.
    taki bajt może mieć wartość od 0 do 255, czyli tyle może być zindeksowanych kolorów, 


Przedstawię strukturę "Bitmap" i "Pix"
typedef struct Pix
{
	int bw;
	int Red;
	int Green;
	int Blue;
	int Light;
} Pix;

typedef struct BitMap
{
	int Width;
	int Height;
	int Bits;

	unsigned short File[7];    // Header
	unsigned long Header[10];  // Info Header
	unsigned char *Color;
	Pix **Pixels;
} BitMap;


Funkcja użytkowa:
// Scale jest to łączenie bajtów np. Scale( 0010 , 1110 ) = 1110 0010 
unsigned long Scale(unsigned short _A, unsigned short _B)
{
	unsigned long A = ((long)(_B) << 16);
	A = A | (long)(_A);
	return A;
}


Przedstawię teraz funkcję która wczytuje bitmapię
  • 24-bit
  • 8-bit
  • 1-bit

obrazy :

BitMap Load_BMP(const char* _File)
{
	BitMap oBitMap; 
	FILE *oFile = fopen(_File, "rb");
	if (oFile == NULL)
	{
		printf ("Nie moze otworzyc pliku! %s
",_File);
		exit(1);
	}

	fseek(oFile,0,SEEK_SET);
	fread(oBitMap.File, 2, 7, oFile);//14
	fread(oBitMap.Header, 4, 10, oFile);//40
	
	// Szerokość i Wysokość Bitmapy 
	oBitMap.Width = oBitMap.Header[1];
	oBitMap.Height = oBitMap.Header[2];

	// Ile bitów przypada na pixel 
	oBitMap.Bits = oBitMap.Header[3] >> 16;
	printf("Header[5] : %d 
", oBitMap.Header[5]);

	// Table Color
	// Scale jest to łączenie bytów np. Scale( 0010 , 1110 ) = 1110 0010 
	unsigned long oOffset = Scale(oBitMap.File[5],oBitMap.File[6]) - 54;
	//printf("Offset : %d 
",oOffset);
	if(oOffset > 0)
	{
		oBitMap.Color = (unsigned char*) malloc(sizeof(unsigned char) * oOffset);
		fread(oBitMap.Color, sizeof(unsigned char), oOffset, oFile);
	}

	// Utworzenie dwu-wymiarowej tablicy pikseli.
	oBitMap.Pixels = (Pix**) malloc(sizeof(Pix*) * oBitMap.Height);
	for(int y = 0; y < oBitMap.Height; y++)
		oBitMap.Pixels[y] = (Pix*) malloc(sizeof(Pix) * oBitMap.Width);

	unsigned char oPalette;
	// Wczytanie poszczególnych pikseli  
	for(int y = 0; y < oBitMap.Height; y++)
	{
		int oSum = 0; // Suma kontrolna bajtów, potrzebna do eliminacji paddinu.
		for(int x = 0; x < oBitMap.Width; x++)
		{
			// Bitmapa zbudowana na podstawie 1-bitu
			if(oBitMap.Bits == 1)
			{
				oSum += 1; // za każdym razem wczytujemy 1 bajt (8bits)
				unsigned char oPix = 0;
				fread(&oPix, sizeof(unsigned char), 1, oFile);
				unsigned char oPosition = 128;
				for(int i = 0; i < 8; i++)
				{
					if(x+i < oBitMap.Width)
					{
						oBitMap.Pixels[y][x+i].bw = 1;
						oBitMap.Pixels[y][x+i].Light = 0;
						if((oPix&oPosition) == oPosition)
							oBitMap.Pixels[y][x+i].Light = 255;
					}
					oPosition = oPosition >> 1;
				}
				x += 7;
			}
			else if(oBitMap.Bits == 8)
			{
				oSum += 1; 
				fread(&oPalette, sizeof(unsigned char), 1, oFile);
				oBitMap.Pixels[y][x].Light = (int)oPalette;
				oBitMap.Pixels[y][x].bw = 1;
			}
			else if(oBitMap.Bits == 24)
			{
				oSum += 3; 
				oBitMap.Pixels[y][x].bw = 0;
				oBitMap.Pixels[y][x].Light = 0;

				fread(&oPalette, sizeof(unsigned char), 1, oFile);
				oBitMap.Pixels[y][x].Blue = (int)oPalette;
				fread(&oPalette, sizeof(unsigned char), 1, oFile);
				oBitMap.Pixels[y][x].Green = (int)oPalette;
				fread(&oPalette, sizeof(unsigned char), 1, oFile);
				oBitMap.Pixels[y][x].Red = (int)oPalette;
			}
		}

		// Usuwanie paddingu... 
		unsigned char oM = 0;
		if(oSum%4 != 0)
			for(int i = 0; i < (4-oSum%4); i++)
				fread(&oM, sizeof(unsigned char), 1, oFile);
	}
	printf ("LOADING BMP : %s
",_File);
	printf ("Width :  %6d ",oBitMap.Width);
	printf ("
Height:  %6d ",oBitMap.Height);
	printf ("
oBits :  %6d ",oBitMap.Bits);
	printf ("
        Success
");
	return oBitMap;
}



Przedstawię teraz funkcję która zapisuje bitmapię
  • 24-bit
  • 8-bit
  • 1-bit

obrazy :

void Save_BMP(BitMap _BMP, const char* _File)
{
	FILE *oFile = fopen(_File, "wb");
	if (oFile == NULL)
	{
		printf ("Nie można uzyskac dostepu lub utworzyc pliku %s
",_File);
		exit(1);
	}

	fwrite(&_BMP.File, 2, 7, oFile);
	fwrite(&_BMP.Header, 4, 10, oFile);

	unsigned long oOffset = Scale(_BMP.File[5],_BMP.File[6]) - 54;
	if(oOffset > 0)
		fwrite(_BMP.Color, sizeof(unsigned char), oOffset, oFile);

	for(int y = 0; y < _BMP.Height; y++)
	{
		int oSum = 0;
		for(int x = 0; x < _BMP.Width; x++)
		{
			if(_BMP.Bits == 1)
			{
				oSum += 1;
				unsigned char oPix = 0;
				unsigned char oPosition = 128;
				for(int i = 0; i < 8; i++)
				{
					if(x+i < _BMP.Width)
						if(_BMP.Pixels[y][x+i].Light == 255)
							oPix = oPix | oPosition;
					oPosition = oPosition >> 1;
				}
				fwrite(&oPix, sizeof(unsigned char), 1, oFile);
				x += 7;
			}
			else if(_BMP.Bits == 8)
			{
				unsigned char oLight = min(max(_BMP.Pixels[y][x].Light,0),255);
				fwrite(&oLight, sizeof(unsigned char), 1, oFile);
				oSum += 1; 
			}
			else if(_BMP.Bits == 24)
			{				
				unsigned char oBlue = min(max(_BMP.Pixels[y][x].Blue,0),255);
				fwrite(&oBlue, sizeof(unsigned char), 1, oFile);
				unsigned char oGreen = min(max(_BMP.Pixels[y][x].Green,0),255);
				fwrite(&oGreen, sizeof(unsigned char), 1, oFile);
				unsigned char oRed = min(max(_BMP.Pixels[y][x].Red,0),255);
				fwrite(&oRed, sizeof(unsigned char), 1, oFile);
				oSum += 3; 
			}
		}

		// Dodawanie paddingu... 
		unsigned char oM = 0;
		if(oSum%4 != 0)
			for(int i = 0; i < (4-oSum%4); i++)
				fwrite(&oM, sizeof(unsigned char), 1, oFile);
	}
}


Funkcje które zwracają i ustawia danego piksela.

Pix Get_PIX(int _X, int _Y, BitMap _BMP)
{
	// Ograniczenie aby nie wyjść za granice wielkości obrazka.
	_X = min(max(0, _X), _BMP.Width-1);
	_Y = min(max(0, _Y), _BMP.Height-1);

	// Zwrócenie piksela.
	return _BMP.Pixels[_BMP.Height-_Y-1][_X];
}
void Set_PIX(int _X, int _Y, Pix _Pix, BitMap _BMP)
{
	// Ograniczenie aby nie wyjść za granice wielkości obrazka.
	_X = min(max(0, _X), _BMP.Width-1); 
	_Y = min(max(0, _Y), _BMP.Height-1);
	
	// Zapis piksela.
	_BMP.Pixels[_BMP.Height-_Y-1][_X] = _Pix;
}

  • Administracja