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].
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).
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 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;
}
data
. The caller needs to delete
the data
when he/she is done with it. –
Mischiefmaker 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
chars –
Taunt 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;
}
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
THE FOLLOWING LINE ONLY WORKS IF THE OFFSET IS 1 BYTE!!!!
section in the current previous versions. –
Warenne 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
© 2022 - 2024 — McMap. All rights reserved.