C++ 读取 bmp 文件中的像素值
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9296059/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
read pixel value in bmp file
提问by Sourav
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].
如何在 Windows 上的 C 或 C++ 中以所有像素 [h*w] 读取 24 位 BMP 图像的颜色值[在没有任何 3rd 方库的情况下更好]。我得到了Dev-C++
一个工作代码将非常感激,因为我从来没有从事过图像阅读工作,并且在谷歌搜索之后来到了 SO [如果你能比我更好地谷歌,请提供一个链接]。
回答by 0605002
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
- 它在 Windows 上运行,我不确定其他平台
- 它适用于 24 位彩色 BMP 图像
- 它假设图像的宽度是 4 的倍数,因此它不处理填充字节,以防万一
- 它将图像宽度和高度解码为 32 位小端整数
- 它返回一个指向动态分配的内存的指针,如果调用者没有释放它可能会导致内存泄漏
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]
.
现在data
应该包含像素的 (R, G, B) 值。像素 (i, j) 的颜色存储在data[3 * (i * width + j)]
、data[3 * (i * width + j) + 1]
和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).
在最后一部分,每个第一个和第三个像素之间的交换完成,因为我发现颜色值存储为 (B, G, R) 三元组,而不是 (R, G, B)。
回答by Krzysztof Kachniarz
Code of readBMP function after padding fix:
填充修复后的 readBMP 函数代码:
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;
}
回答by Liam
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.
我创建了一个 BitMap 类,它适用于每像素 24 位的 bmp 文件。如果 bmp 不兼容,您应该会收到相关错误。
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.)
它几乎完全符合维基百科的文章。(一个问题是它不适用于像素阵列偏移量大于 255 的文件。这在代码中有所说明,应该很容易修复。)
I've been using this with bmp files created by mspaint.
我一直在将它与 mspaint 创建的 bmp 文件一起使用。
Here is an example usage
这是一个示例用法
example.cpp
例子.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.
example_vector 现在包含从图像顶部向下索引的坐标 (1,1) 处像素的 rgb(按该顺序)值。索引从 0 开始。请参阅维基百科示例。
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
回答by didil
Here is a working C++ version of the answer:
这是答案的有效 C++ 版本:
#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;
}
回答by Owen Morgan
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.
我无法对顶级答案发表评论,因为我还没有足够的 stackoverflow 代表,但我只想指出该实现的一个非常关键的错误。
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):
一些位图可以用负高度写入,因此当您尝试分配图像数据缓冲区时,您的代码将崩溃std::bad_alloc
。具有负高度的位图意味着图像数据从上到下存储,而不是传统的从下到上。因此,顶级答案的稍微好一点的版本是(仍然不包括具有不同字节顺序和字节大小的系统的可移植性):
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;
}
回答by AlexTheo
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.
您必须先阅读位图标题。找到您将在位图标题中找到的数据偏移量并逐行读取像素后,请注意 bmp 文件格式的填充。
take a look on msdn http://msdn.microsoft.com/en-us/library/aa452883.aspx
看看 msdn http://msdn.microsoft.com/en-us/library/aa452883.aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/dd318229(v=vs.85).aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/dd318229(v=vs.85).aspx