BMP文件像素读取异常求助:疑为行填充或fseek问题
BMP像素读取失败问题排查
问题概述
读取BMP文件像素时出现错误,怀疑行尾填充或fseek()函数导致故障。以下是核心代码、头文件定义及测试情况:
核心代码
bmp.c
struct pixel* read_data(FILE* stream, const struct bmp_header* header){ if(stream == NULL || header == NULL) return NULL; fseek(stream, 0, SEEK_SET); size_t row_size = (header->width * sizeof(struct pixel)) + strlen(PADDING_CHAR); size_t total_size = row_size * header->height; struct pixel* data = (struct pixel*)malloc(total_size); if(data == NULL){ printf("cant allocate"); return NULL; } size_t pixels_read = fread(data, sizeof(struct pixel), 1, stream); if(pixels_read == 0){ free(data); return NULL; } return data; }
bmp.h
#define PADDING_CHAR "\0" struct pixel { uint8_t blue; uint8_t green; uint8_t red; //uint8_t alpha; } __attribute__((__packed__)); /** * Read the pixels * * Reads the data (pixels) from stream representing the image. If the stream * is not open or header is not provided, returns `NULL`. * * @param stream opened stream, where the image data are located * @param header the BMP header structure * @return the pixels of the image or `NULL` if stream or header are broken */ struct pixel* read_data(FILE* stream, const struct bmp_header* header);
BMP头定义(24位颜色)
struct bmp_header{ uint16_t type; // "BM" (0x42, 0x4D) uint32_t size; // file size uint16_t reserved1; // not used (0) uint16_t reserved2; // not used (0) uint32_t offset; // offset to image data (54B) uint32_t dib_size; // DIB header size (40B) uint32_t width; // width in pixels uint32_t height; // height in pixels uint16_t planes; // 1 uint16_t bpp; // bits per pixel (1/4/8/24) uint32_t compression; // compression type (0/1/2) 0 uint32_t image_size; // size of picture in bytes, 0 uint32_t x_ppm; // X Pixels per meter (0) uint32_t y_ppm; // X Pixels per meter (0) uint32_t num_colors; // number of colors (0) uint32_t important_colors; // important colors (0) } __attribute__((__packed__));
测试情况
给定有效BMP流、正确文件位置及有效头,调用函数未成功读取数据:
测试1:
FILE* stream = "Qk1WAAAAAAAAADYAAAAoAAAAAgAAAAQAAAABABgAAAAAACAAAAAjLgAAIy4AAAAAAAAAAAAA////AAAAAAD/AAAA/wAAAAD/AAAA/wAAAAAA/wAAAAA="; // base64编码流 struct bmp_header* header = read_bmp_header(stream); fseek(stream, offset, SEEK_SET); 断言'read_data(stream, header) == "////AAAA/wAAAP8AAP8AAAD/AAAA/wAA"'失败。[实际结果: "Qk1WG21VAAAAAAAAAAAAAAAAAAAAAAAA"]测试2:
FILE* stream = "Qk1OAAAAAAAAADYAAAAoAAAAAgAAAAMAAAABABgAAAAAABgAAAAjLgAAIy4AAAAAAAAAAAAAAAAA/wD/AAD/AAAA/wAAAP////8A/wAA"; // base64编码流 struct bmp_header* header = read_bmp_header(stream); fseek(stream, offset, SEEK_SET); 断言'read_data(stream, header) == "AAAA/wD//wAAAP8A/////wD/"'失败。[实际结果: "Qk1OG21VAAAAAAAAAAAAAAAA"]
注:断言"=="后为预期结果,括号内为代码实际输出结果,数据以Base64格式传输以便查看。
问题分析与修复方案
代码存在3个关键错误,直接导致读取失败:
错误重置文件指针
调用方已通过fseek(stream, offset, SEEK_SET)定位到图像数据起始位置,但read_data里的fseek(stream, 0, SEEK_SET)将指针重置到文件开头,导致读取的是BMP文件头内容而非像素数据——这就是实际结果出现Qk1W/Qk1O(对应BMP标识BM的后续数据)的原因。行尾填充计算错误
BMP要求每行字节数对齐到4的倍数,正确计算方式应为row_size = (header->width * sizeof(struct pixel) + 3) & ~3。用strlen(PADDING_CHAR)计算填充长度完全错误:"\0"的strlen返回0,根本未处理填充,且不符合BMP的填充规则。fread参数错误fread(data, sizeof(struct pixel), 1, stream)仅读取1个像素结构体,而非全部需要的像素数据。正确调用应按总字节数读取所有图像数据。
修复后的代码示例
struct pixel* read_data(FILE* stream, const struct bmp_header* header){ if(stream == NULL || header == NULL) return NULL; // 移除错误的指针重置,保留调用方设置的正确位置 // fseek(stream, 0, SEEK_SET); // 正确计算对齐后的行字节数 size_t pixel_bytes_per_row = header->width * sizeof(struct pixel); size_t row_size = (pixel_bytes_per_row + 3) & ~3; // 对齐到4字节 size_t total_size = row_size * header->height; struct pixel* data = (struct pixel*)malloc(total_size); if(data == NULL){ printf("cant allocate"); return NULL; } // 按总字节数读取所有图像数据(含填充) size_t bytes_read = fread(data, 1, total_size, stream); if(bytes_read != total_size){ free(data); return NULL; } // 可选:剥离行尾填充(根据后续处理需求) // struct pixel* stripped_data = malloc(header->width * header->height * sizeof(struct pixel)); // if(stripped_data){ // for(int i=0; i<header->height; i++){ // memcpy(stripped_data + i*header->width, data + i*row_size, pixel_bytes_per_row); // } // free(data); // return stripped_data; // } return data; }
关键说明
- 移除
fseek(stream, 0, SEEK_SET),保留调用方设置的正确文件位置。 - 用位运算实现行字节数的4字节对齐,符合BMP格式要求。
- 调整
fread参数,按总字节数读取全部图像数据;若后续不需要填充,可添加代码剥离填充部分。
内容的提问来源于stack exchange,提问作者USER
相关产品推荐
相关产品推荐

