• Advertisement
Sign in to follow this  

bmp Loader class

This topic is 904 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello,

I was building a class to load a bmp file 24 bit depth.
To make it simple at first I have created a 2x2 image in paint with 4 pixels red, green, blue and white.

when I open the image in a hex editor I count the 54 bit header offset and try and make sense of the values.

The first 3 bytes I get is red but all the next BGR values are not what I expect them to be.

also every pixel is 3 bytes but there is C++ no type with 3 bytes could I use uint or can that corrupt the colors when changing from BGR to RGB?




Code so far:

 


int bmpTextureLoader::loadBMP(const char* location, GLuint& texture)
{
	UINT16  *datBuff[2] = { nullptr, nullptr };
	UINT16 *pixels = nullptr;

	BITMAPFILEHEADER * bmpHeader = nullptr;
	BITMAPINFOHEADER * bmpInfo = nullptr;


	// The file... We open it with it's constructor
	std::ifstream file(location, std::ios::binary);
	if (!file)
	{
		return -1;
	}


	// Allocate byte memory that will hold the two headers
	datBuff[0] = new UINT16[sizeof(BITMAPFILEHEADER)];
	datBuff[1] = new UINT16[sizeof(BITMAPINFOHEADER)];

	file.read((char*)datBuff[0], sizeof(BITMAPFILEHEADER));
	file.read((char*)datBuff[1], sizeof(BITMAPINFOHEADER));


	// Construct the values from the buffers
	bmpHeader = (BITMAPFILEHEADER*)datBuff[0];
	bmpInfo = (BITMAPINFOHEADER*)datBuff[1];


	if (bmpHeader->bfType != 0x4D42)
	{
		return 2;
	}

	// First allocate pixel memory

	const int imageSize = bmpInfo->biSizeImage;


	pixels = new UINT16[imageSize];

	int size = sizeof(pixels);

	size = size;

	file.seekg(bmpHeader->bfOffBits);
	file.read((char*)pixels, bmpInfo->biSizeImage);


		UINT16 tmpRGB = 0; // Swap buffer

	int cmpress = bmpInfo->biCompression;


		cmpress = cmpress;

	for (unsigned long i = 0; i < bmpInfo->biSizeImage; i += 3)
	{
		tmpRGB = pixels[i];
		pixels[i] = pixels[i + 2];
		pixels[i + 2] = tmpRGB;
	}

	// Set width and height to the values loaded from the file
	GLuint w = bmpInfo->biWidth;
	GLuint h = bmpInfo->biHeight;


	glGenTextures(1, &texture);             // Generate a texture

	glBindTexture(GL_TEXTURE_2D, texture); // Bind that texture temporarily

	GLint mode = GL_RGB;                   // Set the mode

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

	//// Create the texture. We get the offsets from the image, then we use it with the image's
	//// pixel data to create it.
	//must be multiple of 2 1024 x 1024, 512 x 512 , 256 x 256

	glTexImage2D(GL_TEXTURE_2D, 0, mode, w, h, 0, mode, GL_UNSIGNED_BYTE, pixels);


	//// Delete the two buffers.
	delete[] datBuff[0];
	delete[] datBuff[1];

	delete[] pixels;

	return 0; // Return success code 

}

Share this post


Link to post
Share on other sites
Advertisement

First off, I wouldn't recommend using BMP as a texture storage and loading format. Ideally, you'd create your own format, but barring that, I'd use TGA. It is far easier to load as the format just makes sense. BMP is highly screwed up, as you've noticed. The pixels are not stored as you would expect. Padding *may* be added per scanline, which makes looking at the file with a hex editor all but useless.

 

If you are insistent on using BMP, do some studying:

 

https://en.wikipedia.org/wiki/BMP_file_format

http://www.fileformat.info/format/bmp/egff.htm

http://paulbourke.net/dataformats/bmp/

 

It is not a programmer-friendly format.

Edited by MarkS

Share this post


Link to post
Share on other sites

Here my LoadBMPTextureFromFile function I wrote long time ago :

---

BmpLoader.h :

//--------------------------------------------------------------------------------------
// BMP Loader : 8-bit / 24-bit / 32-bit, Compression RLE not supported
//--------------------------------------------------------------------------------------
#define BMP_MAGIC 0x4D42 // "MB"

struct BMP_HEADER
{
  UInt32 Size;
  UInt16 Reserved1;
  UInt16 Reserved2;
  UInt32 OffBits;
};

struct BMP_INFO_HEADER
{
  UInt32 Size;
  Int32  Width;
  Int32  Height;
  UInt16 Planes;
  UInt16 BitCount;
  UInt32 Compression;
  UInt32 SizeImage;
  Int32  XPelsPerMeter;
  Int32  YPelsPerMeter;
  UInt32 ClrUsed;
  UInt32 ClrImportant;
};

struct BMP_IMAGE_DATA
{
  UInt32 Width;
  UInt32 Height;
  UInt32 Components;
  UInt8* Pixels;
};

BMP_IMAGE_DATA* LoadBMPTextureFromFile( const std::string& Filename );

BmpLoader.cpp :

BMP_IMAGE_DATA* LoadBMPTextureFromFile( const std::string& Filename )
{
#ifdef DE_COMPILER_MSVC
  FILE* File = NULL;
  fopen_s( &File, Filename.c_str(), "rb" );
#else
  FILE* File = fopen( Filename.c_str(), "rb" );
#endif

  if( File == NULL )
  {
    ILogger::Get() << "ERROR : LoadBMPTextureFromFile couldn't find, or failed to load " + Filename + ".\n";
    return NULL;
  }

  UInt16 MagicNumber = 0;
  fread( &MagicNumber, sizeof( UInt16 ), 1, File );
  if( MagicNumber != BMP_MAGIC )
  {
    ILogger::Get() << "ERROR : The file " + Filename + " doesn't appear to be a valid .bmp file.\n";
    fclose( File );
    return NULL;
  }

  BMP_HEADER BMPHeader;
  fread( &BMPHeader, sizeof( BMP_HEADER ), 1, File );

  BMP_INFO_HEADER BMPInfoHeader;
  fread( &BMPInfoHeader, sizeof( BMP_INFO_HEADER ), 1, File );

  if( ( BMPInfoHeader.Size != sizeof( BMP_INFO_HEADER ) ) || ( BMPInfoHeader.Planes != 1 ) )
  {
    ILogger::Get() << "ERROR : The file " + Filename + " doesn't appear to be a valid .bmp file.\n";
    fclose( File );
    return NULL;
  }

  if( BMPInfoHeader.Compression != 0 )
  {
    ILogger::Get() << "ERROR : The file " + Filename + " is a compressed .bmp file.\n";
    fclose( File );
    return NULL;
  }

  if( BMPInfoHeader.BitCount == 1 )
  {
    ILogger::Get() << "ERROR : The file " + Filename + " is a monochrome .bmp file.\n";
    fclose( File );
    return NULL;
  }
  else if( BMPInfoHeader.BitCount == 4 )
  {
    ILogger::Get() << "ERROR : The file " + Filename + " is a 4-bit .bmp file.\n";
    fclose( File );
    return NULL;
  }
  else if( BMPInfoHeader.BitCount == 16 )
  {
    ILogger::Get() << "ERROR : The file " + Filename + " is a 16-bit .bmp file.\n";
    fclose( File );
    return NULL;
  }

  fseek( File, BMPHeader.OffBits, SEEK_SET );

  BMP_IMAGE_DATA* BMPImageData = new BMP_IMAGE_DATA;
  memset( BMPImageData, 0, sizeof( BMP_IMAGE_DATA ) );

  if( BMPInfoHeader.BitCount == 8 )
    BMPImageData->Components = 1;
  else if( BMPInfoHeader.BitCount == 24 )
    BMPImageData->Components = 3;
  else if( BMPInfoHeader.BitCount == 32 )
    BMPImageData->Components = 4;

  UInt32 BufferSize = BMPInfoHeader.Width * BMPInfoHeader.Height * BMPImageData->Components;
  BMPImageData->Pixels = new UInt8[ BufferSize ];
  fread( BMPImageData->Pixels, 1, BufferSize, File );

  fclose( File );

  BMPImageData->Width  = BMPInfoHeader.Width;
  BMPImageData->Height = BMPInfoHeader.Height;

  return BMPImageData;
}
Edited by Alundra

Share this post


Link to post
Share on other sites
Writing an image loader can be a fun exercise and creating a custom format isn't too difficult: Width, height, bbp, compression options, an optional palette, and then the image payload, uncompressed or compressed with zlib. Just be mindful of any padding a compiler might add to your structs, if you are to directly serialize a struct to and from a file like Alundra's example.

If you're looking to get something working quickly, then you should use consider using any widely availble libraries. Microsoft has the Windows Imaging Component service, which allows you to use any of the codecs provided by the operating system: GIF, JPEG, PNG, BMP, and et. al. For cross platform options, there is stb_image. Most cross platform game tools will also include some image loading utilities as well.

Share this post


Link to post
Share on other sites

I got issue with stb_image but last updates solved them, but I use FreeImage because I need the load and save which are better in FreeImage.

 

also every pixel is 3 bytes but there is C++ no type with 3 bytes could I use uint or can that corrupt the colors when changing from BGR to RGB?

About that, since you know BMP are stored RGB, you have the array of pixel so you only need to do a for-loop to invert each pixel :

for each pixel
{
  r = pixel[ i + 2 ];
  g = pixel[ i + 1 ];
  b = pixel[ i ]
}
Edited by Alundra

Share this post


Link to post
Share on other sites

Thanks for your replies.

Unfortunately I cannot  use most of the libraries because I am trying to port a msvc c++ version to android and most windows libraries or libraries which work with opengl as opposed to opengles will not do, my bad I shoud have mentioned it . 


 

 

Here my LoadBMPTextureFromFile function I wrote long time ago :

---

BmpLoader.h :

//--------------------------------------------------------------------------------------
// BMP Loader : 8-bit / 24-bit / 32-bit, Compression RLE not supported
//--------------------------------------------------------------------------------------
#define BMP_MAGIC 0x4D42 // "MB"

struct BMP_HEADER
{
  UInt32 Size;
  UInt16 Reserved1;
  UInt16 Reserved2;
  UInt32 OffBits;
};

struct BMP_INFO_HEADER
{
  UInt32 Size;
  Int32  Width;
  Int32  Height;
  UInt16 Planes;
  UInt16 BitCount;
  UInt32 Compression;
  UInt32 SizeImage;
  Int32  XPelsPerMeter;
  Int32  YPelsPerMeter;
  UInt32 ClrUsed;
  UInt32 ClrImportant;
};

struct BMP_IMAGE_DATA
{
  UInt32 Width;
  UInt32 Height;
  UInt32 Components;
  UInt8* Pixels;
};

BMP_IMAGE_DATA* LoadBMPTextureFromFile( const std::string& Filename );

BmpLoader.cpp :

BMP_IMAGE_DATA* LoadBMPTextureFromFile( const std::string& Filename )
{
#ifdef DE_COMPILER_MSVC
  FILE* File = NULL;
  fopen_s( &File, Filename.c_str(), "rb" );
#else
  FILE* File = fopen( Filename.c_str(), "rb" );
#endif

  if( File == NULL )
  {
    ILogger::Get() << "ERROR : LoadBMPTextureFromFile couldn't find, or failed to load " + Filename + ".\n";
    return NULL;
  }

  UInt16 MagicNumber = 0;
  fread( &MagicNumber, sizeof( UInt16 ), 1, File );
  if( MagicNumber != BMP_MAGIC )
  {
    ILogger::Get() << "ERROR : The file " + Filename + " doesn't appear to be a valid .bmp file.\n";
    fclose( File );
    return NULL;
  }

  BMP_HEADER BMPHeader;
  fread( &BMPHeader, sizeof( BMP_HEADER ), 1, File );

  BMP_INFO_HEADER BMPInfoHeader;
  fread( &BMPInfoHeader, sizeof( BMP_INFO_HEADER ), 1, File );

  if( ( BMPInfoHeader.Size != sizeof( BMP_INFO_HEADER ) ) || ( BMPInfoHeader.Planes != 1 ) )
  {
    ILogger::Get() << "ERROR : The file " + Filename + " doesn't appear to be a valid .bmp file.\n";
    fclose( File );
    return NULL;
  }

  if( BMPInfoHeader.Compression != 0 )
  {
    ILogger::Get() << "ERROR : The file " + Filename + " is a compressed .bmp file.\n";
    fclose( File );
    return NULL;
  }

  if( BMPInfoHeader.BitCount == 1 )
  {
    ILogger::Get() << "ERROR : The file " + Filename + " is a monochrome .bmp file.\n";
    fclose( File );
    return NULL;
  }
  else if( BMPInfoHeader.BitCount == 4 )
  {
    ILogger::Get() << "ERROR : The file " + Filename + " is a 4-bit .bmp file.\n";
    fclose( File );
    return NULL;
  }
  else if( BMPInfoHeader.BitCount == 16 )
  {
    ILogger::Get() << "ERROR : The file " + Filename + " is a 16-bit .bmp file.\n";
    fclose( File );
    return NULL;
  }

  fseek( File, BMPHeader.OffBits, SEEK_SET );

  BMP_IMAGE_DATA* BMPImageData = new BMP_IMAGE_DATA;
  memset( BMPImageData, 0, sizeof( BMP_IMAGE_DATA ) );

  if( BMPInfoHeader.BitCount == 8 )
    BMPImageData->Components = 1;
  else if( BMPInfoHeader.BitCount == 24 )
    BMPImageData->Components = 3;
  else if( BMPInfoHeader.BitCount == 32 )
    BMPImageData->Components = 4;

  UInt32 BufferSize = BMPInfoHeader.Width * BMPInfoHeader.Height * BMPImageData->Components;
  BMPImageData->Pixels = new UInt8[ BufferSize ];
  fread( BMPImageData->Pixels, 1, BufferSize, File );

  fclose( File );

  BMPImageData->Width  = BMPInfoHeader.Width;
  BMPImageData->Height = BMPInfoHeader.Height;

  return BMPImageData;
}

 


I have adapted this code to add the texture but for some reason I get a black screen and I have no clue why, here is my code: 



 

#include "BmpLoader.h"


int LoadBMPTextureFromFile(const std::string& Filename, GLuint& texture)
{
#ifdef _MSC_VER
	FILE* File = NULL;
	fopen_s(&File, Filename.c_str(), "rb");
#else
	FILE* File = fopen(Filename.c_str(), "rb");
#endif

	if (File == NULL)
	{
		//ILogger::Get() << "ERROR : LoadBMPTextureFromFile couldn't find, or failed to load " + Filename + ".\n";
		return NULL;
	}

	UINT16 MagicNumber = 0;
	fread(&MagicNumber, sizeof(UINT16), 1, File);
	if (MagicNumber != BMP_MAGIC)
	{
		//ILogger::Get() << "ERROR : The file " + Filename + " doesn't appear to be a valid .bmp file.\n";
		fclose(File);
		return NULL;
	}

	BMP_HEADER BMPHeader;
	fread(&BMPHeader, sizeof(BMP_HEADER), 1, File);

	BMP_INFO_HEADER BMPInfoHeader;
	fread(&BMPInfoHeader, sizeof(BMP_INFO_HEADER), 1, File);

	if ((BMPInfoHeader.Size != sizeof(BMP_INFO_HEADER)) || (BMPInfoHeader.Planes != 1))
	{
		//ILogger::Get() << "ERROR : The file " + Filename + " doesn't appear to be a valid .bmp file.\n";
		fclose(File);
		return 0;
	}

	if (BMPInfoHeader.Compression != 0)
	{
		//ILogger::Get() << "ERROR : The file " + Filename + " is a compressed .bmp file.\n";
		fclose(File);
		return 0;
	}

	if (BMPInfoHeader.BitCount == 1)
	{
		//ILogger::Get() << "ERROR : The file " + Filename + " is a monochrome .bmp file.\n";
		fclose(File);
		return 0;
	}
	else if (BMPInfoHeader.BitCount == 4)
	{
		//ILogger::Get() << "ERROR : The file " + Filename + " is a 4-bit .bmp file.\n";
		fclose(File);
		return 0;
	}
	else if (BMPInfoHeader.BitCount == 16)
	{
	//	ILogger::Get() << "ERROR : The file " + Filename + " is a 16-bit .bmp file.\n";
		fclose(File);
		return 0;
	}

	fseek(File, BMPHeader.OffBits, SEEK_SET);

	BMP_IMAGE_DATA* BMPImageData = new BMP_IMAGE_DATA;
	memset(BMPImageData, 0, sizeof(BMP_IMAGE_DATA));

	if (BMPInfoHeader.BitCount == 8)
		BMPImageData->Components = 1;
	else if (BMPInfoHeader.BitCount == 24)
		BMPImageData->Components = 3;
	else if (BMPInfoHeader.BitCount == 32)
		BMPImageData->Components = 4;

	UINT32 BufferSize = BMPInfoHeader.Width * BMPInfoHeader.Height * BMPImageData->Components;
	BMPImageData->Pixels = new UINT8[BufferSize];
	fread(BMPImageData->Pixels, 1, BufferSize, File);


	UINT8 tmpRGB = 0;

	for (ULONG i = 0; i < BMPInfoHeader.SizeImage; i += 3)
	{
		if (i + 2 > BMPInfoHeader.SizeImage) // prevent going beyond allocated memory
		{
			break;
		}

		tmpRGB = BMPImageData->Pixels[i];
		BMPImageData->Pixels[i] = BMPImageData->Pixels[i + 2];
		BMPImageData->Pixels[i + 2] = tmpRGB;
	}

		glGenTextures(1, &texture);             // Generate a texture
	
		glBindTexture(GL_TEXTURE_2D, texture); // Bind that texture temporarily
	
		GLint mode = GL_RGB;                   // Set the mode
	
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	
		//// Create the texture. We get the offsets from the image, then we use it with the image's
		//// pixel data to create it.
		//must be multiple of 2 1024 x 1024, 512 x 512 , 256 x 256
	
		glTexImage2D(GL_TEXTURE_2D, 0, mode, BMPImageData->Width, BMPImageData->Height, 0, mode, GL_UNSIGNED_BYTE, BMPImageData->Pixels);


	fclose(File);

	BMPImageData->Width = BMPInfoHeader.Width;
	BMPImageData->Height = BMPInfoHeader.Height;


	delete BMPImageData->Pixels;

	return 1;
}

Share this post


Link to post
Share on other sites
First error: you're assuming that the pixel data starts at the 55th byte. That's wrong: it starts at the 54th byte.

Second error: it always starts from bottom to top. So you have:

FF 00 00 (#0000FF - BLUE)
FF FF FF (#FFFFFF - WHITE)
00 00 (padding)
00 00 FF (#FF0000 - RED)
00 FF 00 (#00FF00 - GREEN)
00 00 (more padding)

As you can see, BMP is a very f***ed up format, so you should probably consider using using another format.

Share this post


Link to post
Share on other sites

First error: you're assuming that the pixel data starts at the 55th byte. That's wrong: it starts at the 54th byte.

Second error: it always starts from bottom to top. So you have:

FF 00 00 (#0000FF - BLUE)
FF FF FF (#FFFFFF - WHITE)
00 00 (padding)
00 00 FF (#FF0000 - RED)
00 FF 00 (#00FF00 - GREEN)
00 00 (more padding)

As you can see, BMP is a very f***ed up format, so you should probably consider using using another format.

You have an error in your second error: If the height field is negative, pixel data is top to bottom.

 

That only adds strength to your final comment though. smile.png

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement