I'm a n00b who's been looking at this problem for the past few days and I'm just plain stuck. I'm working in OpenSuse Linux trying to interpret Windows bitmap images for display using the Cairo graphics library. Simply put, I just need to get the color information for each pixel into an array and feed it to Cairo, e.g. pixeldata[i] = someColor, for all the pixels in the image. So far I've figured out how to parse through the bitmap headers and got it working great for displaying 24-bit bitmaps.
But, now I'm struggling to get 8-bit bitmaps to display as well, and it's just been an unwieldy, unintuitive beast to wrangle. I'm able to get the image to display, but the colors displayed are wrong... and not just that, they change every time I run the program! :P I think I'm accessing and interpreting the bmiColors palette array incorrectly. Here's the relevant code I've pieced together from my lengthy Internet research that builds the pixel array (note, by this point in the code, the header information has already been parsed and is available in objects m_bmpInfoHeader and m_bmpHeader):
#define RGB(r, g, b) ((long)(((char)(r) | ((char)((short)(g)) << 8)) | (((char)(b)) << 16 )))
#pragma pack (2)
typedef struct tagRGBQUAD {
long rgbBlue;
long rgbGreen;
long rgbRed;
int rgbReserved;
} RGBQUAD;
typedef struct
{
char verifier[2];
unsigned int size;
unsigned short int reserved1, reserved2;
unsigned int offset;
} BITMAPHEADER;
typedef struct
{
unsigned int size; /* Header size in bytes */
signed int width, height; /* Width and height of image */
unsigned short int planes; /* Number of colour planes */
unsigned short int bits; /* Bits per pixel */
unsigned int compression; /* Compression type */
unsigned int imagesize; /* Image size in bytes */
int xresolution,yresolution; /* Pixels per meter */
unsigned int ncolors; /* Number of colors */
unsigned int importantcolors; /* Important colors */
RGBQUAD bmiColors [1];
} BITMAPINFOHEADER;
#pragma pack()
// Function sets up and returns color index for bitmap.
long BitmapDef::GetColorInx (int numbits, char* data, long offset)
{
long inx;
switch (numbits)
{
case 1:
inx = data[offset >> 3];
offset &= 7;
inx >>= offset;
inx &= 0x01;
break;
case 2:
inx = data[offset >> 2];
offset &= 3;
offset <<= 1;
inx >>= offset;
inx &= 0x03;
break;
case 4:
inx = data[offset >> 1];
if (!(offset & 1))
{
inx >>= 4;
}
inx &= 0x0f;
break;
case 24:
{
offset *= 3;
inx = *((long*) &data[offset]);
char r = GetBValue(inx);
char g = GetGValue(inx);
char b = GetRValue(inx);
inx = ((r << 16) + (g << 8) + b);
break;
}
case 8:
default:
inx = data[offset] & 0xff;
break;
}
return inx;
}
void BitmapDef::Build8BitPixelData()
{
m_PixelData = new unsigned int[m_bmpInfoHeader.width * m_bmpInfoHeader.height];
FILE * pFile;
long lSize;
char * buffer;
size_t result;
pFile = fopen ((const char *)m_Filename, "rb" );
if (pFile==NULL)
{
fputs ("File error", stderr);
exit (1);
}
// obtain file size:
fseek (pFile , 0 , SEEK_END);
lSize = ftell (pFile);
rewind (pFile);
// allocate memory to contain the whole file:
buffer = (char*) malloc (sizeof(char) * lSize);
if (buffer == NULL) {fputs ("Memory error", stderr); exit (2);}
// copy the file into the buffer:
result = fread (buffer, 1, lSize, pFile);
if (result != lSize) {fputs ("Reading error",stderr); exit (3);}
BITMAPHEADER* bmfh = (BITMAPHEADER*) (&(buffer[0]));
char* bmp = (char*) &buffer[m_bmpHeader.offset];
BITMAPINFOHEADER * bmi = (BITMAPINFOHEADER*) &buffer[sizeof(*bmfh)];
std::cout<<"\nsize: "<<bmi->size<<std::endl;
std::cout<<"width: "<<bmi->width<<std::endl;
std::cout<<"height: "<<bmi->height<<std::endl;
std::cout<<"planes: "<<bmi->planes<<std::endl;
std::cout<<"bits: "<<bmi->bits<<std::endl;
std::cout<<"annnd compression: "<<bmi->planes<<std::endl;
int ix, iy;
int position = 0;
for (iy = 0; iy < bmi->height; ++iy)
{
for (ix = 0; ix < bmi->width; ++ix)
{
int offset = (m_bmpInfoHeader.height - 1 - iy) * m_bmpInfoHeader.width;
offset += ix;
//std::cout<<"offset: "<<offset<<" bmp[offset]: "<<bmp[offset] << " " ;
long inx = GetColorInx (m_bmpInfoHeader.bits, dataBuf, offset);
//std::cout<<inx<<" ";
m_PixelData[position] = RGB(bmi->bmiColors[inx].rgbRed, bmi->bmiColors[inx].rgbGreen, bmi->bmiColors[inx].rgbBlue);
position++;
}
}
fclose (pFile);
free (buffer);
}
Any ideas? Please note that I'm working in a non-Windows environment, so I've had to translate many Windows-centric functions to work in C++. Any help is appreciated, thanks!
Updates:
Thanks to cbranch's recommendation, I modified the RGBQUAD to have char attributes instead of long/int, in order that it remains a 4-byte structure. This fixed the issue with the colors constantly changing. However, strangely the colors are still off. At the moment I'm trying to display a simple image of a green diamond on a black background, but for some reason the diamond is displaying as yellow. Any ideas?
Also, I just noticed that I accidentally left the "#pragma pack()" directives out from around the structs in my original post, and just now added them to the post.
RGBQuad should be four bytes.
This macro:
#define RGB(r, g, b) ((long)(((char)(r) | ((char)((short)(g)) << 8)) | (((char)(b)) << 16 )))
is problematic. You're probably getting sign extension. A full green pixel will have g == 0xFF. You cast it to (signed) short, so you probably get sign extension (0xFFFF), and then shift. Now you've got red and green set to full values and thus it looks yellow.
When doing bitwise manipulation, you almost always want to use unsigned values.