Skip to main content
GameDev.net gamedev.net
🔒 Locked

Accessing Pixel Data in a BMP

Started by ak11streetballer Mar 12, 2008 at 5:33 AM 7 replies 2k views
Original Post
ak11streetballer
ak11streetballer
Hey guys, I wanted to know how one should go about accessing the pixel data of a BMP file. I want to open one BMP file and copy all its data into another BMP file, pixel by pixel. I am using C++. I used the code given on gamedev.net for reading the BMP file. Now I am having a problem in writing it. My code till now is -

#include "Bitmap.h"
#include <iostream>
#include <fstream>

using namespace std;

//basic constructor
Bitmap::Bitmap()
{
    reset();
}

//constructor loads the bitmap when it is created
Bitmap::Bitmap(char *file)
{
    reset();
    loadBMP(file);
}

//destructor
Bitmap::~Bitmap()
{
    if(colours!=0) {
        delete[] colours;
    }
    if(data!=0) {
        delete[] data;
    }
}

//load a bitmap from a file and represent it correctly in memory
bool Bitmap::loadBMP(char *file)
{
    FILE *in;                  //file stream for reading
    char *tempData;       //temp storage for image data
    int numColours;            //total available colours
    
    

    loaded=false;
    if(colours!=0)
        delete[] colours;
    
    if(data!=0)
        delete[] data;
    
    //open the file for reading in binary mode
    in=fopen(file,"rb");

    //if the file does not exist return in error
    if(in==NULL)
    {
        error="File not found";
        cout<<error<<endl;
        fclose(in);
        return false;
    }

    //read in the entire BITMAPFILEHEADER
    fread(&bmfh,sizeof(BitmapFileHeader),1,in);
	cout<<"sizeof(BitmapFileHeader)="<<sizeof(BitmapFileHeader)<<endl;
    
    //check for the magic number that says this is a bitmap
    if(bmfh.bfType!=BITMAP_MAGIC_NUMBER)
    {
        error="File is not in DIB format";
        cout<<error<<endl;
        fclose(in);
        return false;
    }

    //read in the entire BITMAPINFOHEADER
    fread(&bmih,sizeof(BitmapInfoHeader),1,in);
	cout<<"sizeof(BitmapInfoHeader)="<<sizeof(BitmapInfoHeader)<<endl;

    width=bmih.biWidth;
    height=bmih.biHeight;
    bpp=bmih.biBitCount;
	cout<<"biBitCount      ="<<bmih.biBitCount<<endl;
	cout<<"biClrImportant  ="<<bmih.biClrImportant<<endl;
	cout<<"biClrUsed       ="<<bmih.biClrUsed<<endl;
	cout<<"biCompression   ="<<bmih.biCompression<<endl;
	cout<<"biHeight        ="<<bmih.biHeight<<endl;
	cout<<"biPlanes        ="<<bmih.biPlanes<<endl;
	cout<<"biSize          ="<<bmih.biSize<<endl;
	cout<<"biSizeImage     ="<<bmih.biSizeImage<<endl;
	cout<<"biWidth         ="<<bmih.biWidth <<endl;
	cout<<"biXPelsPerMeter ="<<bmih.biXPelsPerMeter<<endl;
	cout<<"biYPelsPerMeter ="<<bmih.biYPelsPerMeter<<endl;
	cout<<"bpp             ="<<bpp<<endl;

    //calculate the size of the image data with padding
    dataSize=(width*height*(unsigned int)(bmih.biBitCount/8.0));

    //calculate the number of available colours
    numColours=1<<bmih.biBitCount;
    cout<<"Number of available colors = "<<numColours<<endl;

    //if the bitmap is not 8 bits per pixel or more return in error
    if(bpp<8)
    {
        error="File is not 8 or 24 bits per pixel";
        cout<<error<<endl;
        fclose(in);
        return false;
    }

    //load the palette for 8 bits per pixel
    if(bpp==8)
    {
    	colours=new RGBQuad[numColours];
    	fread(&colours,sizeof(RGBQuad),numColours,in);
    }

    //set up the temporary buffer for the image data
    tempData=new char[dataSize];

    //exit if there is not enough memory
    if(tempData==NULL)
    {
        error="Not enough memory to allocate a temporary buffer";
        cout<<error<<endl;
        fclose(in);
        return false;
    }

    //read in the entire image
    fread(tempData,sizeof(tempData),dataSize,in);
    fclose(in);

    //calculate the witdh of the final image in bytes
    byteWidth=padWidth=(int)((float)width*(float)bpp/8.0);

    //adjust the width for padding as necessary
    while(padWidth%4!=0)
    {
        padWidth++;
    }

    
    //change format from GBR to RGB
    if(bpp==8)
    {
    	loaded=convert8(tempData);
    	
   	}
    else if(bpp==24) {
    	loaded=convert24(tempData);
    	
   	}
   	
   	
    
    //clean up memory
    delete[] tempData;

    //bitmap is now loaded
    error="Bitmap loaded";
    cout<<error<<endl;
    
    
    //return success
    return loaded;
}

//function to set the inital values
void Bitmap::reset(void) {
    loaded=false;
    colours=0;
    data=0;
    error="";
}

bool Bitmap::convert24(char* tempData) {
	int offset,diff;
    
    
	diff=width*height*RGB_BYTE_SIZE;
    
    //allocate the buffer for the final image data
    data=new char[diff];

    //exit if there is not enough memory
    if(data==NULL)
    {
        error="Not enough memory to allocate an image buffer";
        cout<<error<<endl;
        delete[] data;
        return false;
    }
    
    if(height>0)
    {
        offset=padWidth-byteWidth;
        //count backwards so you start at the front of the image
        for(int i=0;i<dataSize;i+=3)
        {
            //jump over the padding at the start of a new line
            if((i+1)%padWidth==0)
                i+=offset;
            
            //transfer the data
            *(data+i+2)=*(tempData+i);
            *(data+i+1)=*(tempData+i+1);
            *(data+i)=*(tempData+i+2);
            
            
        }
        
    }
    //image parser for a forward image
    else
    {
        offset=padWidth-byteWidth;
        int j=dataSize-3;
        //count backwards so you start at the front of the image
		for(int i=0;i<dataSize;i+=3)
        {
            //jump over the padding at the start of a new line
            if((i+1)%padWidth==0)
                i+=offset;
            
            //transfer the data
            *(data+j+2)=*(tempData+i);
            *(data+j+1)=*(tempData+i+1);
            *(data+j)=*(tempData+i+2);
            j-=3;
        }
    }
    
    return true;
}

bool Bitmap::convert8(char* tempData) {
	int offset,diff;
	bool write;
    char ans8='y', fileSave[100];
    
	diff=width*height*RGB_BYTE_SIZE;
    
    //allocate the buffer for the final image data
    data=new char[diff];

    //exit if there is not enough memory
    if(data==NULL)
    {
        error="Not enough memory to allocate an image buffer";
        cout<<error<<endl;
        delete[] data;
        return false;
    }

    if(height>0)
    {
        offset=padWidth-byteWidth;
        int j=0;
        //count backwards so you start at the front of the image
        for(int i=0;i<dataSize*RGB_BYTE_SIZE;i+=3)
        {
            //jump over the padding at the start of a new line
            if((i+1)%padWidth==0)
                i+=offset;
            
            //transfer the data
            *(data+i)=colours[*(tempData+j)].rgbRed;
            *(data+i+1)=colours[*(tempData+j)].rgbGreen;
            *(data+i+2)=colours[*(tempData+j)].rgbBlue;
            j++;
        }
    }

    //image parser for a forward image
    else {
        offset=padWidth-byteWidth;
        int j=dataSize-1;
        //count backwards so you start at the front of the image
        for(int i=0;i<dataSize*RGB_BYTE_SIZE;i+=3)
        {
            //jump over the padding at the start of a new line
            if((i+1)%padWidth==0)
                i+=offset;
            
            //transfer the data
            *(data+i)=colours[*(tempData+j)].rgbRed;
            *(data+i+1)=colours[*(tempData+j)].rgbGreen;
            *(data+i+2)=colours[*(tempData+j)].rgbBlue;
            j--;
        }
    }
    
    return true;
}

int main()
{
    char path[100],ans='y', fileSave[100];
    cout<<"Enter the file name: ";
    cin>>path;
    Bitmap bmp(path);
    cout<<"Is this ok?";
    cin>>ans;
}


The Bitmap.h header file contains the basic structure definition of a BMP file. How do I complete my task? Thanks! [Edited by - ak11streetballer on March 12, 2008 5:59:54 AM]
Evil Steve
Evil Steve
Firstly, you should use [ source ] tags to format code in the forums and to take up less vertical space (You can edit your post to add the tags if you wish).

As for the actual problem - what is the problem you're having? I doubt someone is going to write the entire save function for you. Is there some specific aspect you're having problems with? It should be exactlty the same process as loading, except you'll use fwrite().

Also, the above code isn't entirely correct - scanlines in a BMP file are padded to a multiple of 4 byes - so if your image is 10 pixels wide and 8bpp, there'll be 10 bytes of image data, then 2 bytes of padding for each scanline.
ak11streetballer
ak11streetballer
How do I access the scan lines? And do you also mean that there will be 10 scan lines?
dave
dave
Although i don't have an answer to your specific question, i would like to suggest that you use the TGA format. Most graphics applications export to it and it is trivial to parse. I have written two custom loaders for TGAs in the past.
Evil Steve
Evil Steve
Quote:
Original post by ak11streetballer
How do I access the scan lines? And do you also mean that there will be 10 scan lines?
A scan line is a row of pixels. There'll only be 10 scan lines if your bitmap is 10px high.
You know the size of one scanline ((bits_per_pixel/8) * width + padding), and you know the offset to the start of the bit data (the bfOffBits member of the BITMAPFILEHEADER), so to access scaline Y, the offset is bfOffBits + scanline_size * Y.

Quote:
Original post by Dave
Although i don't have an answer to your specific question, i would like to suggest that you use the TGA format. Most graphics applications export to it and it is trivial to parse. I have written two custom loaders for TGAs in the past.
Seconded.
ak11streetballer
ak11streetballer
Ok Evil Steve... thanks for the help. You kind of made things clear to me about offsets and scan lines. Now I have the logic in my head on how I should go about read each scan line and writing it to another file.

Here is my modified code.

#include "Bitmap.h"#include <iostream>#include <fstream>//basic constructorBitmap::Bitmap(){    reset();}//constructor loads the bitmap when it is createdBitmap::Bitmap(char *file){    reset();    loadBMP(file);}//destructorBitmap::~Bitmap(){    if(colours!=0) {        delete[] colours;    }    if(data!=0) {        delete[] data;    }}bool Bitmap::loadBMP(char *file) {    FILE *in;                  //file stream for reading    char newPath[100], *buffer;       //temp storage for image data    int scanLineWidth;            //total available colours    //bitmap is not loaded yet    loaded=false;    //make sure memory is not lost    if(colours!=0) {        delete[] colours;    }    if(data!=0) {        delete[] data;    }    //open the file for reading in binary mode    in=fopen(file,"rb");    //if the file does not exist return in error    if(in==NULL) {        error="File not found";        fclose(in);        return false;    }    //read in the entire BITMAPFILEHEADER    fread(&bmfh,sizeof(BitmapFileHeader),1,in);	cout<<"sizeof(BitmapFileHeader)=" <<sizeof(BitmapFileHeader) << endl;        //check for the magic number that says this is a bitmap    if(bmfh.bfType!=BITMAP_MAGIC_NUMBER) {        error="File is not in DIB format";        cout<<error<<endl;        fclose(in);        return false;    }    //read in the entire BITMAPINFOHEADER    fread(&bmih,sizeof(BitmapInfoHeader),1,in);	cout << "sizeof(BitmapInfoHeader)=" << sizeof(BitmapInfoHeader) << endl;    //save the width, height and bits per pixel for external use    width=bmih.biWidth;    height=bmih.biHeight;    bpp=bmih.biBitCount;	cout << "biBitCount      =" << bmih.biBitCount << endl;	cout << "biClrImportant  =" << bmih.biClrImportant << endl;	cout << "biClrUsed       =" << bmih.biClrUsed << endl;	cout << "biCompression   =" << bmih.biCompression << endl;	cout << "biHeight        =" << bmih.biHeight << endl;	cout << "biPlanes        =" << bmih.biPlanes << endl;	cout << "biSize          =" << bmih.biSize << endl;	cout << "biSizeImage     =" << bmih.biSizeImage << endl;	cout << "biWidth         =" << bmih.biWidth << endl;	cout << "biXPelsPerMeter =" << bmih.biXPelsPerMeter << endl;	cout << "biYPelsPerMeter =" << bmih.biYPelsPerMeter << endl;    //calculate the size of the image data with padding    dataSize=(width*height*(unsigned int)(bmih.biBitCount/8.0));    //if the bitmap is not 8 bits per pixel or more    //return in error    if(bpp<8) {        error="File is not 8 or 24 bits per pixel";        fclose(in);        return false;    }    //read in the entire image    fread(tempData,sizeof(char),dataSize,in);    //close the file now that we have all the info        //calculate the witdh of the final image in bytes    byteWidth=padWidth=(int)((float)width*(float)bpp/8.0);    //adjust the width for padding as necessary    while(padWidth%4!=0) {        padWidth++;    }        //calculate the width of the scan line    scanLineWidth=(int)(padWidth+((bpp/8.0)*width));    cout<<"Scan Line Width = "<<scanLineWidth<<endl;    buffer=new char[scanLineWidth];    if(buffer==NULL) {        error="Not enough memory to allocate a temporary buffer";        fclose(in);        return false;    }        cout<<"Enter the new path: ";    cin>>newPath;    ofstream myfile;    myfile.open(newPath,ios::out|ios::binary);    myfile.write ((char*)&bmfh,sizeof(BitmapFileHeader));    myfile.write ((char*)&bmih,sizeof(BitmapInfoHeader));    fseek (in,bmfh.bfOffBits,SEEK_SET);    for (int i=0;i<height;i++)    {        fread (buffer, scanLineWidth, 1, in);        myfile.write ((char*)&buffer,sizeof(scanLineWidth));        fseek (in,(bmfh.bfOffBits+(scanLineWidth*(i+1))),SEEK_SET);        cout<<"Scan Line "<<(i+1)<<" write!"<<endl;    }        myfile.close();    fclose(in);         //clean up memory    delete[] buffer;    //bitmap is now loaded    error="Bitmap loaded";    cout<<error<<endl;    //return success    return loaded;}void Bitmap::reset(void) {    loaded=false;    colours=0;    data=0;    error="";}int main(){    char path[100], ans='y';    cout<<"Enter the path: ";    cin>>path;    Bitmap bmp(path);    cout<<"Is this ok?";    cin>>ans;    return 0;}


Is this anywhere close to correct because I keep ending up with a BMP file which says 'Drawing Failed'? I am still confused about how and where I should store each scan line and write what I stored to a new BMP file.
ak11streetballer
ak11streetballer
Thanks a lot Big Sassy! I was successful in writing a 24 Bitmap to a 16 Bitmap file. Now I was trying my hand in writing a 32 Bitmap to a 16 Bitmap. Is this possible? I think in the 32 Bitmap extra 8 bits are introduced which represent alpha. How do I convert this to a 565 format?
ak11streetballer
ak11streetballer
Hey guys,

This is Big Sassy's program for loading a BMP and I had a few doubts in it.

#define RGB16(red, green, blue) ( ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3))void LoadBitmap(){   BITMAPINFOHEADER infoheader;   WORD             *bitmapData;   WORD             *bitmapDone;   FILE             *bitmapFile;   BYTE             red, green, blue;   bitmapFile = fopen("yourBitmap.bmp", "rb");   fseek(bitmapFile, sizeof(BITMAPFILEHEADER), SEEK_SET);   fread(&infoheader, sizeof(BITMAPINFOHEADER), 1, bitmapFile);   bitmapData = new WORD[infoheader.biWidth * infoheader.biHeight];   bitmapDone = new WORD[infoheader.biWidth * infoheader.biHeight];   for( int y=0; y<infoheader.biHeight; ++y)   {      for( int x=0; x<infoheader.biWidth; ++x)      {         fread(&blue, sizeof(BYTE), 1, file);         fread(&green, sizeof(BYTE), 1, file);         fread(&red, sizeof(BYTE), 1, file);         bitmapData[y*infoheader.biWidth + x] = RGB16(red, green, blue);      }   }      int heightIndex = 0;   for( y=infoheader.biHeight-1;  y>=0; --y)   {      for( int x=0; x<infoheader.biWidth; ++x)         bitmapDone[heightIndex*infoheader.biWidth + x] = bitmapData[y*infoheader.biWidth + x];      ++heightIndex;   }}


My doubts were in these two specific lines.

#define RGB16(red, green, blue) ( ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3))


AND

bitmapData[y*infoheader.biWidth + x] = RGB16(red, green, blue);


Now bitmapData has all the data of the source BMP. It is stored in the RGB16 format defined by the Macro. When I want to write this data to a new BMP image, do I write the specific array number of bitmapData or do I write it colour by colour in the sequence GBR? If the latter is correct, how do I access the RGB components from bitmapData?

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.