如何在C++中正确读取二进制(raw).pbm文件?代码问题排查
问题根源
你的代码完全误解了P4格式的二进制存储规则:P4格式是将8个像素打包到一个字节中存储,而非每个像素占用一个字节。当前你把每个像素当作单独字节读写,导致数据严重错位,才会出现黑色区域异常的情况。
读取函数的错误与修正
原错误点
- 未处理
width/height后的空白字符:使用>>读取宽高后,文件指针停在宽高后的换行或空格处,直接读取二进制数据会把空白字符当成像素数据。 - 逐字节读取单个像素:违背P4的位打包规则,每个字节实际对应8个像素(不足8的行补填充位)。
修正后的读取函数
#include <algorithm> #include <limits> void readPBMFile(const std::string& filePath, std::vector<std::vector<int>>& pixels) { std::ifstream file(filePath, std::ios::binary); if (!file.is_open()) { throw std::runtime_error("Failed to open file."); } std::string header; std::getline(file, header); if (header != "P4") { throw std::runtime_error("File is not in PBM format."); } int width, height; file >> width >> height; // 跳过宽高后的所有空白字符(包括换行),定位到二进制数据起始位置 file.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); pixels.resize(height, std::vector<int>(width)); const int bytesPerRow = (width + 7) / 8; // 每行需要读取的字节数,向上取整 for (int y = 0; y < height; ++y) { for (int byteIdx = 0; byteIdx < bytesPerRow; ++byteIdx) { unsigned char byte; file.read(reinterpret_cast<char*>(&byte), 1); // 逐个提取字节中的位,从最高位到最低位对应像素从左到右 for (int bitIdx = 0; bitIdx < 8; ++bitIdx) { const int x = byteIdx * 8 + bitIdx; if (x >= width) break; // 跳过行末尾的填充位 // P4规范:0表示黑色,1表示白色;位值为1时对应像素1(白),0对应0(黑) const int pixelValue = (byte >> (7 - bitIdx)) & 1; pixels[y][x] = pixelValue; } } } }
保存函数的错误与修正
原错误点
逐字节写入单个像素:每个像素占一个字节,完全不符合P4的打包规则,导致文件体积膨胀且格式错误。
修正后的保存函数
void savePBMFile(const std::string& filePath, const std::vector<std::vector<int>>& pixels) { if (pixels.empty() || pixels[0].empty()) { throw std::runtime_error("Empty pixel data."); } std::ofstream file(filePath, std::ios::binary); if (!file.is_open()) { throw std::runtime_error("Failed to create file."); } const int width = pixels[0].size(); const int height = pixels.size(); // 写入文本头,注意换行符使用'\n',避免系统自动转换(因为开了binary模式) file << "P4\n" << width << " " << height << "\n"; const int bytesPerRow = (width + 7) / 8; for (const auto& row : pixels) { unsigned char byte = 0; for (int x = 0; x < width; ++x) { const int bitPos = 7 - (x % 8); // 当前像素对应字节的位位置(从高位开始) if (row[x] == 1) { byte |= (1 << bitPos); // 置对应位为1(白色) } // 每8个像素或行末尾时写入字节 if ((x + 1) % 8 == 0 || x == width - 1) { file.write(reinterpret_cast<const char*>(&byte), 1); byte = 0; // 重置字节 } } } }
额外注意事项
- PBM规范中,0代表黑色像素,1代表白色像素,位提取和打包时要严格对应这个规则。
- 读写时必须跳过文本头后的所有空白字符,否则会把换行/空格当成二进制数据读取,导致整个像素矩阵错位。
- 对于宽度不是8的倍数的行,末尾会填充0位,读取时要跳过这些填充位,避免越界写入
pixels。
内容的提问来源于stack exchange,提问作者Alexander Jordanov
相关产品推荐
相关产品推荐

