读取bmp文件的C语言代码

3

我正在尝试在我的程序中读取bmp文件,但是遇到了一些问题。读入文件后,如果我让它打印pBmp->header.fileSize,则会显示16,但是如果我在十六进制编辑器中查看该文件大小部分,则为F6 7A 10 00。如果我将该值更改为正确的文件大小,则会显示F6 7A F6 7A 10 00,但这会遇到应始终为零的resv1。我知道这只是读取1个像素数据的问题之一,另一个问题是当我尝试使用while循环读取像素直到文件结束时,我遇到了段错误。我已经花费了几个小时的时间搜索并尝试解决这个问题,但是没有太多成功。

// The BMPHEADER structure.
typedef struct {
    byte        sigB;
    byte        sigM;
    int32_t     fileSize;
    int16_t     resv1;
    int16_t     resv2;
    int32_t     pixelOffset;
} tBmpHeader;

// The BMPINFOHEADER structure.
typedef struct {
    int32_t     size;
    int32_t     width;
    int32_t     height;
    int16_t     colorPlanes;
    int16_t     bitsPerPixel;
    byte        zeros[24];
} tBmpInfoHeader;

typedef uint8_t byte;

typedef struct {
    byte blue;
    byte green;
    byte red;
} tPixel;

// A BMP image consists of the BMPHEADER and BMPINFOHEADER structures, and the 2D pixel array.
typedef struct {
    tBmpHeader      header;
    tBmpInfoHeader  infoHeader;
    tPixel          **pixel;
} tBmp;

tPixel **BmpPixelAlloc(int pWidth, int pHeight)
{
    tPixel **pixels = (tPixel **)malloc (pHeight * sizeof(tPixel *));
    for (int row = 0; row < pHeight; ++row)
    {
        pixels[row] = (tPixel *)malloc(pWidth * sizeof(tPixel));
    }

    printf("pixelAlloc\n"); 

    return pixels;
}

pBmp->pixel = BmpPixelAlloc(pBmp->infoHeader.width, pBmp->infoHeader.height);
if(FileRead(file, &pBmp->pixel, sizeof(tPixel), 1)!=0)
{ 
    errorCode = ErrorFileRead;
}

1
你可以展示完整的代码吗?你没有主函数,FileRead未被定义,你没有展示在哪里检查header.FileSize,你也没有展示tBmpHeader等。 - clcto
2个回答

4

不应该直接使用结构体进行直接I/O操作:即使您已按照BMP头文件的顺序声明了struct,也不能保证编译器将字段端对端地放置,没有任何内容在其中。

编译器通常遵循平台的对齐限制,这可能会导致它们在字段之间添加填充字节,以确保例如大字段的起始地址对齐。

您需要使用特定于编译器的技巧来强制打包结构,或者逐字节地将其写入结构中。


3

检查头文件结构的填充。你可能会发现编译器已经将 struct 中的 fileSize 值对齐到了结构体内的 4 字节边界上。如果您在结构元素地址中放入一些调试 printf,您就可以看到这一点。

我在这里使用了您的 struct 运行了一个示例:

typedef struct {
    byte        sigB;
    byte        sigM;
    int32_t     fileSize;
    int16_t     resv1;
    int16_t     resv2;
    int32_t     pixelOffset;
} tBmpHeader;

tBmpHeader hdr;

int main(int argc, char *argv[])
{
    printf("%d\n", sizeof(tBmpHeader) );
    printf("%p hdr\n", &hdr);
    printf("%p sigB\n", &hdr.sigB);
    printf("%p sigM\n", &hdr.sigM);
    printf("%p fileSize\n", &hdr.fileSize);
    printf("%p resv1\n", &hdr.resv1);
    printf("%p resv2\n", &hdr.resv2);
    printf("%p pixelOffset\n", &hdr.pixelOffset);
}

输出结果为:

16
0x8049788 hdr
0x8049788 sigB
0x8049789 sigM
0x804978c fileSize
0x8049790 resv1
0x8049792 resv2
0x8049794 pixelOffset

hdrfileSize元素之间有4个字节的偏移量,因此在fileSize之前有两个字节的填充。

需要读取头文件中的日期项。您可以通过将读取操作封装在单个函数(例如,“readBmpHeader(...)”)中来保持代码的一些“结构”。您可以保留struct,但分别读取每个字段。所以,您需要这样做(为了清晰起见,我省略了返回值检查):

FileRead(file, &pBmp->header.sigB, sizeof(byte), 1)
FileRead(file, &pBmp->header.sigB, sizeof(byte), 1)
FileRead(file, &pBmp->header.fileSize, sizeof(int32_t), 1)
FileRead(file, &pBmp->header.resv1, sizeof(int16_t), 1)
FileRead(file, &pBmp->header.resv2, sizeof(int16_t), 1)
FileRead(file, &pBmp->header.pixelOffset, sizeof(int32_t), 1)

看起来你是对的,但我不确定该怎么做。 - Covertpyro
啊,现在它可以工作了,你认为这就是我循环出问题的原因吗? - Covertpyro
@Covertpyro,你需要分开像素行读取,因为你单独的malloc地址不会在连续的内存中。 - lurker
是的,之前我已经成功获取了第一个像素的正确数据,但现在头文件可用后,第一个像素的数据不正确了。一位导师告诉我使用while(!feof)循环来填充数组,但这却导致了分段错误。 - Covertpyro
请查看代码示例。您需要读取保留值以跳过它们。while ( !feof(...) ) 可能会因为您的像素数据不连续而失败。因此,您将覆盖未分配的内存。另外,您不能将tPixel作为结构体读取。您需要一次一个字节,就像处理头文件一样。您可能需要更新您的代码并发布一个新问题。 :) - lurker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接