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].
-
8<1 second of googling with "bmp file format": en.wikipedia.org/wiki/BMP_file_format– Marc BCommented Feb 15, 2012 at 15:24
6 Answers
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):
- It ran on Windows, I'm not sure about other platforms
- It works for 24-bit color BMP images
- 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
- It decodes the image width and height as 32-bit little endian integers
- 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).
-
6If 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 thenfread()
a row ofrow_padded
bytes, but use only width elements. The rest is discarded... Commented Sep 5, 2012 at 13:05 -
6Please 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. Commented Jan 22, 2015 at 7:42
-
This answer has a bug, please see my comment for an update stackoverflow.com/questions/9296059/… Commented May 23, 2019 at 10:03
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;
}
-
4@arc_lupus he returns
data
. The caller needs todelete
thedata
when he/she is done with it.– rubenvbCommented Jun 26, 2014 at 8:00 -
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. Commented Jun 8, 2015 at 10:04
-
@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. Commented Jun 30, 2015 at 16:18
-
@KrzysztofKachniarz @jiggunjer 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 need3*width*height
chars, not just3*width+somthing
chars Commented Jul 1, 2015 at 10:19
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;
}
-
Haven't you already reached the end of the stream in first bmp.read? I would deem second bmp.read does nothing? Commented May 28, 2019 at 17:08
-
1
-
Can you please tell me what changes I need to do above code in order toread .png file? Commented Jul 27, 2020 at 22:03
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;
}
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
-
I like your approach - but this method does not work. It is not getting the correct Height and Width. Commented Jun 3, 2016 at 12:54
-
@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 error– LiamCommented Jun 3, 2016 at 13:00
-
I think the actual was 1300 while it was pulling 20 for both Height and Width Commented Jun 3, 2016 at 15:24
-
1@Robben_Ford_Fan_boy Okay, you're right. I was only using this for small objects (sprites) and so the issue never came up. I'll fix it.– LiamCommented Jun 3, 2016 at 17:23
-
1@Robben_Ford_Fan_boy 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.– LiamCommented Jun 3, 2016 at 23:35
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