BMP文件读写异常:像素写入错误致图像显示异常
BMP文件读写异常排查与修复
问题现象
使用C语言实现BMP读写时,读入自定义结构体后重新写出的图像呈现全黑状态,且列出现重复压缩现象;文件头部数据正常,但像素写入错误,对比十六进制文件发现头部之后的数据存在差异且长度明显更短。
用户代码实现
结构体定义
typedef uint16_t ImageType; typedef struct { uint32_t size; uint16_t additionalFeature; uint16_t copy; uint32_t offset; } ImageHeader; typedef struct { uint32_t headerSize; int width; int height; uint16_t colorSpaces; uint16_t bitsPerPixel; uint32_t compression; uint32_t size; int verticalResolution; int horizontalResolution; uint32_t totalColors; uint32_t importantColors; } ImageMetadata; typedef struct { uint8_t blue; uint8_t green; uint8_t red; } ImageColors; typedef struct { ImageType type; ImageHeader header; ImageMetadata metadata; ImageColors **pixels; } Image;
头文件引用
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <stdbool.h> #include <stdio.h> #include <stdbool.h>
像素读取函数
void readImage(char *filename, Image *image) { FILE *imageFile = fopen(filename, "rb"); if (!imageFile) { perror("ReadImageFileException"); fclose(imageFile); exit(EXIT_FAILURE); } fread(&(image->type), sizeof(ImageType), 1, imageFile); //validate for correct image type if (image->type != BMP_IMAGE_TYPE) { fprintf(stderr, "%hu", image->type); perror("ReadImageTypeException"); fclose(imageFile); exit(EXIT_FAILURE); } fread(&(image->header), sizeof(ImageHeader), 1, imageFile); fread(&(image->metadata), sizeof(ImageMetadata), 1, imageFile); // Allocate space for the pixels. image->pixels = malloc( image->metadata.height * sizeof(ImageColors *) ); for(int i = 0; i < image->metadata.height; i++){ image->pixels[i] = malloc(image->metadata.width * sizeof(ImageColors)); } // Read in each pixel for (int i = 0; i < image->metadata.height; i++){ for(int j = 0; j < image->metadata.width; j++){ ImageColors px; if( fread(&px, sizeof(ImageColors), 1, imageFile) < 1 ) { printf("Error while reading bmp pixel.\n"); return; } image->pixels[i][j] = px; } } fclose(imageFile); }
像素写入函数
void writeImage(char *filename, Image *image) { FILE *imageFile = fopen(filename, "wb+"); if (!imageFile) { perror("WriteImageFileException"); fclose(imageFile); exit(EXIT_FAILURE); } size_t typeWritten = fwrite(&(image->type), sizeof(image->type), 1, imageFile); if (typeWritten == 0) { perror("WriteImageTypeException"); fclose(imageFile); exit(EXIT_FAILURE); } size_t headerWritten = fwrite(&(image->header), sizeof(image->header), 1, imageFile); if (headerWritten == 0) { perror("WriteImageHeaderException"); fclose(imageFile); exit(EXIT_FAILURE); } size_t metadataWritten = fwrite(&(image->metadata), sizeof(image->metadata), 1, imageFile); if (metadataWritten == 0) { perror("WriteImageMetadataException"); fclose(imageFile); exit(EXIT_FAILURE); } fseek(imageFile, image->header.offset, SEEK_SET); // Read in each pixel for (int i = 0; i < image->metadata.height; i++){ for(int j = 0; j < image->metadata.width; j++){ if( fwrite(&image->pixels[i][j], sizeof(ImageColors), 1, imageFile) < 1 ) { printf("Error while wr bmp pixel.\n"); fclose(imageFile); exit(EXIT_FAILURE); } } } fclose(imageFile); }
main函数
int main(int argc, char **argv) { int ch; char *inputFile = "sample.bmp"; char *outputFile = "image-copy.bmp"; Image image; // try to use port and password from parameter while ( (ch = getopt_long_only(argc, argv, "", long_options, NULL)) != -1 ) { switch (ch) { case 'i': inputFile = optarg; break; case 'o': outputFile = optarg; break; default: break; } } printf("input File: %s\n", inputFile); readImage(inputFile, &image); writeImage(outputFile, &image); return 0; }
核心问题分析
- 结构体内存对齐偏差:编译器默认的内存对齐规则会在结构体成员间插入填充字节,导致自定义的
ImageHeader、ImageMetadata与BMP文件的二进制结构不匹配,读写头部时数据错位,后续像素读取位置完全错误。 - 忽略BMP行对齐规则:BMP要求每行像素数据长度必须是4字节的倍数,不足时会填充空字节。当前代码直接按宽×高读取/写入像素,未处理这些填充字节,导致像素数据读取不完整、写入时缺失填充,最终图像数据错位。
- 文件指针定位错误:写入函数中
fseek(imageFile, image->header.offset, SEEK_SET)属于多余操作,写完头部后文件指针已处于正确的像素起始位置,手动seek会导致像素写入位置偏移。 - 内存泄漏风险:读取函数中分配的二维像素数组未释放,属于潜在问题。
修复方案
1. 强制结构体1字节对齐
在结构体定义前后添加对齐指令,确保内存布局与BMP文件二进制结构完全一致:
#pragma pack(push, 1) // 此处放置所有结构体定义 typedef uint16_t ImageType; typedef struct {...} ImageHeader; typedef struct {...} ImageMetadata; typedef struct {...} ImageColors; typedef struct {...} Image; #pragma pack(pop)
2. 处理BMP行对齐填充
计算每行实际占用字节数(含填充),读写时跳过/写入填充字节:
修改读取函数的像素读取部分
// 计算每行实际字节数与填充字节数 int row_stride = (image->metadata.width * 3 + 3) / 4 * 4; int padding = row_stride - image->metadata.width * 3; for (int i = 0; i < image->metadata.height; i++){ for(int j = 0; j < image->metadata.width; j++){ ImageColors px; if( fread(&px, sizeof(ImageColors), 1, imageFile) < 1 ) { printf("Error while reading bmp pixel.\n"); return; } image->pixels[i][j] = px; } // 跳过行尾填充字节 fseek(imageFile, padding, SEEK_CUR); }
修改写入函数的像素写入部分
// 计算每行实际字节数与填充字节数 int row_stride = (image->metadata.width * 3 + 3) / 4 * 4; int padding = row_stride - image->metadata.width * 3; // 移除多余的fseek操作 // fseek(imageFile, image->header.offset, SEEK_SET); for (int i = 0; i < image->metadata.height; i++){ for(int j = 0; j < image->metadata.width; j++){ if( fwrite(&image->pixels[i][j], sizeof(ImageColors), 1, imageFile) < 1 ) { printf("Error while writing bmp pixel.\n"); fclose(imageFile); exit(EXIT_FAILURE); } } // 写入行尾填充字节 uint8_t pad[3] = {0}; fwrite(pad, 1, padding, imageFile); }
3. 补充内存释放(可选但推荐)
在main函数写完图像后添加内存释放代码,避免内存泄漏:
// 释放像素内存 for(int i = 0; i < image.metadata.height; i++){ free(image.pixels[i]); } free(image.pixels);
验证修改
修改后重新编译运行,对比输入输出文件的十六进制数据,头部和像素数据应完全一致(若需要精准匹配文件大小,可同步更新image.header.size为实际生成的文件大小)。
内容的提问来源于stack exchange,提问作者Gonzo Gonzales
相关产品推荐
相关产品推荐

