read pixel value in bmp file [closed]
Asked Answered
P

6

13

How can I read the color value of 24bit BMP images at all the pixel [h*w] in C or C++ on Windows [better without any 3rd party library]. I got Dev-C++
A working code will be really appreciated as I've never worked on Image reading & have come to SO after Googling [if you can google better than me, plz provide a link].

Potence answered 15/2, 2012 at 15:22 Comment(1)
<1 second of googling with "bmp file format": en.wikipedia.org/wiki/BMP_file_formatSpadework
H
52

The following code snippet is not complete, and contains lots of hidden assumptions and bugs. I wrote it from scratch for a university course project from mere observation, where it minimally fulfilled all the requirements. I didn't work on it any more, because there must be libraries that would do the job way better.

Here are the conditions where it worked okay (some assumptions are pointed out in the comments):

  1. It ran on Windows, I'm not sure about other platforms
  2. It works for 24-bit color BMP images
  3. It assumes that the width of the image is a multiple of 4, so it doesn't handle the padding bytes in case it's not
  4. It decodes the image width and height as 32-bit little endian integers
  5. It returns a pointer to dynamically allocated memory, it may cause memory leak if it's not released by the caller

Other answers have covered some of these issues.


You can try this one:

unsigned char* readBMP(char* filename)
{
    int i;
    FILE* f = fopen(filename, "rb");
    unsigned char info[54];

    // read the 54-byte header
    fread(info, sizeof(unsigned char), 54, f); 

    // extract image height and width from header
    int width = *(int*)&info[18];
    int height = *(int*)&info[22];

    // allocate 3 bytes per pixel
    int size = 3 * width * height;
    unsigned char* data = new unsigned char[size];

    // read the rest of the data at once
    fread(data, sizeof(unsigned char), size, f); 
    fclose(f);

    for(i = 0; i < size; i += 3)
    {
            // flip the order of every 3 bytes
            unsigned char tmp = data[i];
            data[i] = data[i+2];
            data[i+2] = tmp;
    }

    return data;
}

Now data should contain the (R, G, B) values of the pixels. The color of pixel (i, j) is stored at data[3 * (i * width + j)], data[3 * (i * width + j) + 1] and data[3 * (i * width + j) + 2].

In the last part, the swap between every first and third pixel is done because I found that the color values are stored as (B, G, R) triples, not (R, G, B).

Hoopes answered 15/2, 2012 at 15:46 Comment(3)
If you are reading a 24bit color BMP, you also need to take care about the row-padding. For some reasons BMP expects all rows to be aligned in byte multiples of 4. You compute the padding from the image width with: int row_padded = (width*3 + 3) & (~3) You then fread() a row of row_padded bytes, but use only width elements. The rest is discarded...Interweave
Please note that the function above has some deficiencies, in the assignments of the width and the height of the image: 1. it assumes little endian. It won't work for big endian platform 2. It assumes that sizeof(int) is 4. It won't work if it's not.Helmet
This answer has a bug, please see my comment for an update #9296559Outstare
F
16

Code of readBMP function after padding fix:

unsigned char* ReadBMP(char* filename)
{
    int i;
    FILE* f = fopen(filename, "rb");

    if(f == NULL)
        throw "Argument Exception";

    unsigned char info[54];
    fread(info, sizeof(unsigned char), 54, f); // read the 54-byte header

    // extract image height and width from header
    int width = *(int*)&info[18];
    int height = *(int*)&info[22];

    cout << endl;
    cout << "  Name: " << filename << endl;
    cout << " Width: " << width << endl;
    cout << "Height: " << height << endl;

    int row_padded = (width*3 + 3) & (~3);
    unsigned char* data = new unsigned char[row_padded];
    unsigned char tmp;

    for(int i = 0; i < height; i++)
    {
        fread(data, sizeof(unsigned char), row_padded, f);
        for(int j = 0; j < width*3; j += 3)
        {
            // Convert (B, G, R) to (R, G, B)
            tmp = data[j];
            data[j] = data[j+2];
            data[j+2] = tmp;

            cout << "R: "<< (int)data[j] << " G: " << (int)data[j+1]<< " B: " << (int)data[j+2]<< endl;
        }
    }

    fclose(f);
    return data;
}
Fixate answered 11/6, 2013 at 9:38 Comment(4)
@arc_lupus he returns data. The caller needs to delete the data when he/she is done with it.Mischiefmaker
May be wrong, but I believe that this code has a typo. The amount of memory needed is 3 * width * height, not row_padded. row_padded is needed only for reading the file.Taunt
@JohnSmith but he is reading the file, so the padding is ok? So if your image has 121 pixels in a row, prepare to read 124 and throw away the last 3--if I understood correctly.Architectonic
@KrzysztofKachniarz @Architectonic as far as I understand, he is storing each line in a data array over and over again. when function returns, he will have just the last line of the image, not the whole image. and again, you do not need to pad the array in memory. in order to store the image you need 3*width*height chars, not just 3*width+somthing charsTaunt
S
7

Here is a working C++ version of the answer:

#include <fstream>
#include <iostream>
#include <string>
#include <array>
#include <vector>
#include <iterator>

std::vector<char> readBMP(const std::string &file)
{
    static constexpr size_t HEADER_SIZE = 54;

    std::ifstream bmp(file, std::ios::binary);

    std::array<char, HEADER_SIZE> header;
    bmp.read(header.data(), header.size());

    auto fileSize = *reinterpret_cast<uint32_t *>(&header[2]);
    auto dataOffset = *reinterpret_cast<uint32_t *>(&header[10]);
    auto width = *reinterpret_cast<uint32_t *>(&header[18]);
    auto height = *reinterpret_cast<uint32_t *>(&header[22]);
    auto depth = *reinterpret_cast<uint16_t *>(&header[28]);

    std::cout << "fileSize: " << fileSize << std::endl;
    std::cout << "dataOffset: " << dataOffset << std::endl;
    std::cout << "width: " << width << std::endl;
    std::cout << "height: " << height << std::endl;
    std::cout << "depth: " << depth << "-bit" << std::endl;

    std::vector<char> img(dataOffset - HEADER_SIZE);
    bmp.read(img.data(), img.size());

    auto dataSize = ((width * 3 + 3) & (~3)) * height;
    img.resize(dataSize);
    bmp.read(img.data(), img.size());

    char temp = 0;

    for (auto i = dataSize - 4; i >= 0; i -= 3)
    {
        temp = img[i];
        img[i] = img[i+2];
        img[i+2] = temp;

        std::cout << "R: " << int(img[i] & 0xff) << " G: " << int(img[i+1] & 0xff) << " B: " << int(img[i+2] & 0xff) << std::endl;
    }

    return img;
}
Sextillion answered 18/7, 2016 at 15:31 Comment(3)
Haven't you already reached the end of the stream in first bmp.read? I would deem second bmp.read does nothing?Canterbury
VERY important: this reads the image flipped vertically.Dorcus
Can you please tell me what changes I need to do above code in order toread .png file?Messalina
O
7

I can't comment on the top level answer because I don't have enough stackoverflow rep yet, but I just wanted to point out one very critical bug with that implementation.

Some bitmaps can be written with a negative height, so when you try to allocate your image data buffer, your code will crash with std::bad_alloc. Bitmaps with negative height means that the image data is stored top to bottom instead of the traditional bottom to top. Therefore, a slightly better version of the top level answer is (still not including portability for systems with different endianness and size of bytes):

unsigned char* readBMP(char* filename)
{
    int i;
    FILE* f = fopen(filename, "rb");
    unsigned char info[54];
    fread(info, sizeof(unsigned char), 54, f); // read the 54-byte header

    // extract image height and width from header
    int width, height;
    memcpy(&width, info + 18, sizeof(int));
    memcpy(&height, info + 22, sizeof(int));

    int heightSign = 1;
    if (height < 0){
        heightSign = -1;
    }

    int size = 3 * width * abs(height);
    unsigned char* data = new unsigned char[size]; // allocate 3 bytes per pixel
    fread(data, sizeof(unsigned char), size, f); // read the rest of the data at once
    fclose(f);

    if(heightSign == 1){
        for(i = 0; i < size; i += 3)
        {
            //code to flip the image data here....
        }
    }
    return data;
}
Outstare answered 31/3, 2017 at 12:26 Comment(1)
Why should we need abs(height) instead of simply height?Tucana
W
5

I've created a BitMap class that works for bmp files that have 24 bits per pixel. If the bmp isn't compatible, you should get a relevant error.

It follows along almost exactly with the Wikipedia article. (The one problem is that it doesn't work with files that have a pixel array offset that is greater than 255. This is noted in the code and should be easily fixable.)

I've been using this with bmp files created by mspaint.

Here is an example usage

example.cpp

#include "bmp.h"

int main() {
    // load the file. The constructor now does most of the work
    BitMap example_bmp("examplefile.bmp"); 

    // get the vector <R,G,B> for the pixel at (1,1)
    std::vector<unsigned int> example_vector = example_bmp.getPixel(1,1); 
}

example_vector now contains the rgb (in that order) values of the pixel at coordinate (1,1) indexed from the top of the image, going down. Indices start at 0. See the Wikipedia examples.

Here is the header file:

#ifndef BMP_H
#define BMP_H

#include <iostream>
#include <vector>
#include <fstream>
class BitMap {

    private:
        unsigned char m_bmpFileHeader[14];
        unsigned int m_pixelArrayOffset;
        unsigned char m_bmpInfoHeader[40];

        int m_height;
        int m_width;
        int m_bitsPerPixel;

        int m_rowSize;
        int m_pixelArraySize;

        unsigned char* m_pixelData;

        char * m_copyname;
        const char * m_filename;
    public:
        BitMap(const char * filename);
        ~BitMap();

        std::vector<unsigned int> getPixel(int i,int j);

        void makeCopy(char * filename);
        void writePixel(int i,int j, int R, int G, int B);

        void swapPixel(int i, int j, int i2, int j2);

        void dispPixelData();

        int width() {return m_width;}
        int height() {return m_height;}

        int vd(int i, int j);
        int hd(int i, int j);

        bool isSorted();
};

BitMap::BitMap( const char * filename) {

    using namespace std;

    m_filename = filename;

    ifstream inf(filename);
    if(!inf) {
        cerr<<"Unable to open file: "<<filename<<"\n";
    }



    //unsigned char m_bmpFileHeader[14];
    unsigned char a;
    for(int i =0;i<14;i++) {
        inf>>hex>>a;
        m_bmpFileHeader[i] = a;
    }
    if(m_bmpFileHeader[0]!='B' || m_bmpFileHeader[1]!='M') {
        cerr<<"Your info header might be different!\nIt should start with 'BM'.\n";
    }

    /*
        THE FOLLOWING LINE ONLY WORKS IF THE OFFSET IS 1 BYTE!!!!! (it can be 4 bytes max)
        That should be fixed now. 
        old line was
        m_pixelArrayOffset = m_bmpFileHeader[10];
    */
    unsigned int * array_offset_ptr = (unsigned int *)(m_bmpFileHeader + 10);
    m_pixelArrayOffset = *array_offset_ptr;


    if( m_bmpFileHeader[11] != 0 || m_bmpFileHeader[12] !=0 || m_bmpFileHeader[13] !=0 ) {
        std::cerr<< "You probably need to fix something. bmp.h("<<__LINE__<<")\n";
    }



    //unsigned char m_bmpInfoHeader[40];
    for(int i=0;i<40;i++) {
        inf>>hex>>a;
        m_bmpInfoHeader[i]=a;
    }

    int * width_ptr = (int*)(m_bmpInfoHeader+4);
    int * height_ptr = (int*)(m_bmpInfoHeader+8);

    m_width = *width_ptr;
    m_height = *height_ptr;

    printf("W: %i, H: %i", m_width, m_height);

    m_bitsPerPixel = m_bmpInfoHeader[14];
    if(m_bitsPerPixel!=24) {
        cerr<<"This program is for 24bpp files. Your bmp is not that\n";
    }
    int compressionMethod = m_bmpInfoHeader[16];
    if(compressionMethod!=0) {
        cerr<<"There's some compression stuff going on that we might not be able to deal with.\n";
        cerr<<"Comment out offending lines to continue anyways. bpm.h line: "<<__LINE__<<"\n";
    }


    m_rowSize = int( floor( (m_bitsPerPixel*m_width + 31.)/32 ) ) *4;
    m_pixelArraySize = m_rowSize* abs(m_height);

    m_pixelData = new unsigned char [m_pixelArraySize];

    inf.seekg(m_pixelArrayOffset,ios::beg);
    for(int i=0;i<m_pixelArraySize;i++) {
        inf>>hex>>a;
        m_pixelData[i]=a; 
    }



}

BitMap::~BitMap() {
    delete[] m_pixelData;
}

void BitMap::dispPixelData() {
    for(int i=0;i<m_pixelArraySize;i++) {
        std::cout<<(unsigned int)m_pixelData[i]<<" ";   
    }
    std::cout<<"\n";
}

// output is in rgb order.
std::vector<unsigned int> BitMap::getPixel(int x, int y) {
    if(x<m_width && y<m_height) {
        std::vector<unsigned int> v;
        v.push_back(0);
        v.push_back(0);
        v.push_back(0);

        y = m_height -1- y; //to flip things
        //std::cout<<"y: "<<y<<" x: "<<x<<"\n";
        v[0] = (unsigned int) ( m_pixelData[ m_rowSize*y+3*x+2 ] ); //red
        v[1] = (unsigned int) ( m_pixelData[ m_rowSize*y+3*x+1 ] ); //greed
        v[2] = (unsigned int) ( m_pixelData[ m_rowSize*y+3*x+0 ] ); //blue


        return v;
    }
    else {std::cerr<<"BAD INDEX\n";std::cerr<<"X: "<<x<<" Y: "<<y<<"\n";}
}

void BitMap::makeCopy(char * filename) {
    std::ofstream copyfile(filename);
    std::ifstream infile(m_filename);
    m_copyname = filename;

    unsigned char c;
    while(infile) {
        infile>>c;
        copyfile<<c;
    }
}

// changes the file
void BitMap::writePixel(int x,int y, int R, int G, int B) {
    std::fstream file(m_filename);
    y = m_height -1- y; // to flip things.
    int blueOffset = m_pixelArrayOffset+m_rowSize*y+3*x+0;

    // writes to the file
    file.seekg(blueOffset,std::ios::beg);
    file<< (unsigned char)B;
    file.seekg(blueOffset+1,std::ios::beg);
    file<< (unsigned char)G;
    file.seekg(blueOffset+2,std::ios::beg);
    file<< (unsigned char)R;

    // edits data in pixelData array 
    m_pixelData[m_rowSize*y+3*x+2] = (unsigned char)R;
    m_pixelData[m_rowSize*y+3*x+1] = (unsigned char)G;
    m_pixelData[m_rowSize*y+3*x+0] = (unsigned char)B;
}

// changes the file
void BitMap::swapPixel(int i, int j, int i2, int j2) {
    std::vector<unsigned int> p1 = (*this).getPixel(i,j);

    std::vector<unsigned int> p2 = (*this).getPixel(i2,j2);

    (*this).writePixel(i,j,p2[0],p2[1],p2[2]);
    (*this).writePixel(i2,j2,p1[0],p1[1],p1[2]);

}
#endif
Warenne answered 12/1, 2016 at 0:8 Comment(6)
I like your approach - but this method does not work. It is not getting the correct Height and Width.Burdett
@robben_ford_fan_boy what is the correct values and what are you getting. I recall using this somewhat extensively, although it is possible that this version has an errorWarenne
I think the actual was 1300 while it was pulling 20 for both Height and WidthBurdett
@Burdett Okay, you're right. I was only using this for small objects (sprites) and so the issue never came up. I'll fix it.Warenne
Will do - what did you changeBurdett
@Burdett The height and width (and pixel array offset) have 4 bytes reserved for them. I was only using one byte originally. You an see the relevant changes under the THE FOLLOWING LINE ONLY WORKS IF THE OFFSET IS 1 BYTE!!!! section in the current previous versions.Warenne
D
2

You have to read the bitmap header first. After got to the data offset which you will find in the bitmap headers and read the pixels line by line, make care about the padding in bmp file format.

take a look on msdn http://msdn.microsoft.com/en-us/library/aa452883.aspx

http://msdn.microsoft.com/en-us/library/windows/desktop/dd318229(v=vs.85).aspx

Dna answered 15/2, 2012 at 15:26 Comment(1)
I'm using C/C++ not VC++, thnx for your help :)Potence

© 2022 - 2024 — McMap. All rights reserved.