BMP 8bit odczyt/zapis

0

Cześć, od paru dni męczę się z pewnym problemem. Mianowicie próbuje rozgryźć to jak odczytać-zmodyfikować-zapisać obraz w formacie bitmapy... Szukam sporo w internecie i nie mogę znaleźć a sam aż tak tego nie rozumiem :(. Mam taki program napisany. Odczyt działa tylko nie wiem jak to zapisać do pliku. Wiem, że każdy element powstałej tablicy to kolejny bit obrazu więc wystarczy je zmodyfikować.

#include <iostream>
#include <Windows.h>
#include <fstream>
 
using namespace std;
 
 
uint8_t* datBuff[2] = { nullptr, nullptr }; 
uint8_t* pixels = nullptr; 
BITMAPFILEHEADER* bmpHeader = nullptr; 
BITMAPINFOHEADER* bmpInfo = nullptr; 
int LoadBMP(const char* location);
int SaveNewBmp(const char* location);
 
int main()
{
   
 
 
     
 
}
 
 
 
int LoadBMP(const char* location) {
 
 
 
    std::ifstream file(location, std::ios::binary);
    if (!file)
    {
        std::cout << "Failure to open bitmap file.\n";
 
        return 1;
    }
 
 
 
    datBuff[0] = new uint8_t[sizeof(BITMAPFILEHEADER)];
    datBuff[1] = new uint8_t[sizeof(BITMAPINFOHEADER)];
 
    file.read((char*)datBuff[0], sizeof(BITMAPFILEHEADER));
    file.read((char*)datBuff[1], sizeof(BITMAPINFOHEADER));
 
 
    bmpHeader = (BITMAPFILEHEADER*)datBuff[0];
    bmpInfo = (BITMAPINFOHEADER*)datBuff[1];
 
 
 
    if (bmpHeader->bfType != 0x4D42)
    {
        std::cout << "File \"" << location << "\" isn't a bitmap file\n";
        return 2;
    }
 
    pixels = new uint8_t[bmpInfo->biSizeImage];
 
    file.seekg(bmpHeader->bfOffBits);
    file.read((char*)pixels, bmpInfo->biSizeImage);
     
 
 
    return 0;
}
 
int SaveNewBmp(const char* location) {
    std::ofstream file(location, std::ios::binary);
    if (!file)
    {
        std::cout << "Failure to open bitmap file.\n";
 
        return 1;
    }
 
    file.write((char*)datBuff[0], sizeof(BITMAPFILEHEADER));
    file.write((char*)datBuff[1], sizeof(BITMAPINFOHEADER));
 
    file.write((char*)pixels, bmpInfo->biSizeImage);
    return 0;
}

Jakieś pomysły? Z góry dziękuję :D

2

Nigdy nie używałem ale powinno odpowiadać twoim potrzebom: http://easybmp.sourceforge.net/

0

Pytanie co chcesz konkretnie zrobić. Chcesz używać z WinApi, czy tylko mieć dostęp do poszczególnych pikseli. W tym drugim przypadku ważne jest wyrównanie wierszy pikseli do DWORD.

#define	ROUND_DWORD( w )	( ( w + 3 ) & ( ~3 ) )
switch ( bmpInfo->biBitCount )
{
case 24:	bytes_per_line = ROUND_DWORD( bmpInfo->biWidth* 3 );		break;
case 8:	bytes_per_line = ROUND_DWORD( bmpInfo->biWidth);			break;
case 4:	bytes_per_line = ROUND_DWORD( ( bmpInfo->biWidth+ 1 ) / 2 );	break;
case 1:	bytes_per_line = ROUND_DWORD( ( bmpInfo->biWidth+ 7 ) / 8 );	break;
default:
	NOT SUPORTED
	break;
}

Dla ośmiu bitów, pobranie piksela

int GetPixel8( int x, int y )
{
	// Bitmapa w BMP jest do góry nogami.
	int	y = bmpInfo->biHeight - y - 1;
	return pixels[ x + bytes_per_line * y ];
}

Gdybyś wczytał paletę kolorów mógłbyś użyć

void GetPixel8RGB( int& r, int& g, int& b, int x, int y )
{
	RGBQUAD	rgb = bmpInfo->bmiColors[ GetPixel8( x, y ) ];
	r = rgb.rgbRed;
	g = rgb.rgbGreen;
	b = rgb.rgbBlue;
}

Żeby wczytać paletę musisz zaalokować więcej pamięci
Wiersz 30

datBuff[1] = new uint8_t[sizeof(BITMAPINFOHEADER) + 256*sizeof(RGBQUAD) ];

Policzyć ile kolorów

	int	num_colors = 0;
	if ( bmpInfo->biBitCount <= 8 )
	{
		num_colors = bmpInfo->biClrUsed;
		if ( num_colors == 0 )
			num_colors = ( 1 << bmpInfo->biBitCount );
	}

Zmienić wczytywanie headera
Wiersz 33

file.read((char*)datBuff[1], sizeof(BITMAPINFOHEADER) + num_colors*sizeof(RGBQUAD));

Mogą być błędy, bo wyciągnąłem to istniejącego programu, ale musiałem pozmieniać dla Twoich nazw i architektury.

0

Windowsowy plik w formacie bmp składa się z czterech części.

  1. Na początku pliku jest nagłówek BITMAPFILEHEADR
    https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapfileheader
    Przy zapisie tego nagłówka interesują Ciebie trzy pola:

bfType - to jest WORD (czyli dwa bajty). Pierwszy z nich musi mieć wartość 0x42, a drugi 0x4D (to odpowiada znakom BM)
bfSize - to jest DWORD i tu musisz wpisać całkowity rozmiar pliku. Musisz albo wyliczyć rozmiar pliku, albo plik zapisać, sprawdzić jego rozmiar, cofnąć się do tego miejsca w pliku i wpisać odpowiednia wartość.
bfOffBits to jest DWORD i tu musisz wpisać przesunięcie od początku pliku do miejsca, gdzie w pliku zaczynają się dane dotyczące pikseli. I tu jak wyżej, albo trzeba to policzyć, albo w czasie zapisywania na bieżąco to wpisywać.

  1. Bezpośrednio za BITMAPFILEHEADER w pliku jest zapisana struktura BITMAPINFOHEADER.
    https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader

  2. Ten element jest opcjonalny i jest to tablica kolorów (o zmiennej długości) wartości typu RGBQUAD
    https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-rgbquad
    RGBQUAD - to cztery bajty (jeden DWORD).

  3. Właściwe dane (treść obrazu) - jest to tablica bajtów o zmiennej długości.

Konkretna zawartość pliku (to czy element 3 występuje) zależy od organizacji bitmapy.
W nagłówku BITMAPINFOHEADER jest pole biBitCount. Jeśli biBitCount jest mniejsze od 16, tzn. że bitmapa zawiera tablicę kolorów (czyli punkt 3 wymieniony wyżej występuje). W takim wypadku liczba elementów w tablicy kolorów jest 2 do potęgi biBitCount.
Jeśli biBitCount jest większe lub równe 16, to element 3 (tablica kolorów nie występuje).

Jak obliczyć całkowitą objętość danych oraz przesunięcie do danych z pikselami (dane potrzebne do wstawienia do BITMAFILEHEADER)?

Trzeba zacząć od policzenia objętości jednej linii obrazu.
W formacie bmp długość linii obrazu w bajtach jest zaokrąglana do czterech bajtów w górę. Do wyliczenia używamy funkcji

long ScanBytes(int pixWidth, int bitsPixel) {
	return (((long)pixWidth*bitsPixel+31) / 32) * 4;
}

gdzie:
pixWidth - szerokość obrazu w pikselach
bitsPixel - wspomniane wyżej biBitCount

Więc objętość jednej linii to:

long w=ScanBytes(.., ..)

Objętość całego bloku z danymi - to w*h (gdzie h - to wysokość obrazu).
Do tego trzeba dodać sizeof(BITMAPFILEHEADER) i sizeof(BITMAPINFOHEADER).
I jeśli biBitCount jest mniejsze od 16, to do tego dodajemy (2 do potęgi biBitCount) razy sizeof (RGBQUAD).
Tak wyliczoną wartość wstawiasz do bfSize.
Natomiast do bfOffBits wstawiasz to co wyliczyliśmy wyżej, ale minus objętość bloku z danymi (czyli tylko rozmiary nagłówków i ewentualnie rozmiar tablicy kolorów - jeśli występuje)

BITMAPFILEHEADR plus następująca po nim tablica kolorów RGBQUAD (jeśli występuje) traktowana jest jak jedna struktura typu BITMAPINFO.
Stąd nie przewiduje się, żeby po BITMAPFILEHEADER, a przed tablicą kolorów był jakiś odstęp. Czyli jeśli tablica kolorów występuje, to zapisujesz w pliku po kolei BITMAPFILEHEADER, BITMAPINFOHEADER i bezpośrednio za tym - po kolei po cztery bajty na kolor.

Po zapisaniu całej tablicy kolorów od razu od kolejnego bajtu możesz zacząć zapisywać dane dotyczące pikseli. To miejsce (jego położenie w pliku) jest właśnie wskazane w nagłówku (pole bfOffBits).

Spróbuj napisać to sam, jeśli nie ogarniesz - daj znać.

0

Zapis do BMP w moim bardzo starym programie wyglądał tak:

	int	file_size = 14 + 40;	// Don't use sizeof(), aligment.
	int	bitmap_size = 0;
	int	bytes_per_line = ROUND_DWORD( width * 3 );
	int	bit_count = 24;

	switch ( num_colors )
	{
	case 2:
		file_size += 2 * 4;
		bytes_per_line = ROUND_DWORD( ( width + 7 ) / 8 );
		bit_count = 1;
		break;
	case 16:
		file_size += 16 * 4;
		bytes_per_line = ROUND_DWORD( ( width + 1 ) / 2 );
		bit_count = 4;
		break;
	case 256:
		file_size += 256 * 4;
		bytes_per_line = ROUND_DWORD( width );
		bit_count = 8;
		break;
	}

	bitmap_size = bytes_per_line * height;
	file_size += bitmap_size;

	BITMAPFILEHEADER	bmp_file_header = { 0 };
	BITMAPINFOHEADER	bmp_info_header = { 0 };

	bmp_file_header.bfType = 0x4d42;
	bmp_file_header.bfSize = file_size;
	bmp_file_header.bfReserved1 = 0;
	bmp_file_header.bfReserved2 = 0;
	bmp_file_header.bfOffBits = file_size - bitmap_size;

	file.WriteBytes( &bmp_file_header, 14 );

	bmp_info_header.biSize = 40;
	bmp_info_header.biWidth = width;
	bmp_info_header.biHeight = height;
	bmp_info_header.biPlanes = 1;
	bmp_info_header.biBitCount = bit_count;
	bmp_info_header.biCompression = BI_RGB;
	bmp_info_header.biSizeImage = bitmap_size;
	bmp_info_header.biXPelsPerMeter = 0;
	bmp_info_header.biYPelsPerMeter = 0;
	bmp_info_header.biClrUsed = num_colors;
	bmp_info_header.biClrImportant = important_colors; // Maybe 0, maybe num_colors

	file.WriteBytes( &bmp_info_header, 40 );

	// Color map.
	if ( num_colors != 0 )
	{
		file.WriteBytes( palette, num_colors * sizeof(RGBQUAD) );
	}

	// Bitmap. Assumes that lines are properly aligned.
	file.WriteBytes( pixels, height * bytes_per_line );

1 użytkowników online, w tym zalogowanych: 0, gości: 1