用C语言创建bmp文件

3

我正在尝试创建一个.bmp文件(为了测试目的,填充一种颜色)。

这是我使用的代码:

#include <stdio.h>

#define BI_RGB 0

typedef unsigned int UINT;
typedef unsigned long DWORD;
typedef long int LONG;
typedef unsigned short WORD;
typedef unsigned char BYTE;

typedef struct tagBITMAPFILEHEADER {
    UINT bfType;
    DWORD bfSize;
    UINT bfReserved1;
    UINT bfReserved2;
    DWORD bfOffBits;
} BITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER {
    DWORD biSize;
    LONG biWidth;
    LONG biHeight;
    WORD biPlanes;
    WORD biBitCount;
    DWORD biCompression;
    DWORD biSizeImage;
    LONG biXPelsPerMeter;
    LONG biYPelsPerMeter;
    DWORD biClrUsed;
    DWORD biClrImportant;
} BITMAPINFOHEADER;

typedef struct COLORREF_RGB
{
    BYTE cRed;
    BYTE cGreen;
    BYTE cBlue;
}COLORREF_RGB;

int main(int argc, char const *argv[])
{

    BITMAPINFOHEADER bih;

    bih.biSize = sizeof(BITMAPINFOHEADER);
    bih.biWidth = 600;
    bih.biHeight = 600;
    bih.biSizeImage = bih.biWidth * bih.biHeight * 3;
    bih.biPlanes = 1;
    bih.biBitCount = 24;
    bih.biCompression = BI_RGB;
    bih.biXPelsPerMeter = 2835;
    bih.biYPelsPerMeter = 2835;
    bih.biClrUsed = 0;
    bih.biClrImportant = 0;



    COLORREF_RGB rgb;
    rgb.cRed = 0;
    rgb.cGreen = 0;
    rgb.cBlue = 0;



    BITMAPFILEHEADER bfh;

    bfh.bfType = 0x424D;
    bfh.bfReserved1 = 0;
    bfh.bfReserved2 = 0;
    bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + bih.biSize;
    bfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) +
        bih.biWidth * bih.biHeight * 4;


    FILE *f;
    f = fopen("test.bmp","wb");
    fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, f);
    fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, f);

    int i,j;
    for(i = 0; i < bih.biHeight; i++)
    {
        for(j = 0; j < bih.biWidth; j++)
        {
            fwrite(&rgb,sizeof(COLORREF_RGB),1,f);
        }
    }

    fclose(f);

    return 0;
}

每次我编译并运行它时,都会出现错误,说它不是有效的BMP图像。我在多个参考资料中仔细检查了所有值,仍然找不到错误。我是否误解了什么,我在这里做错了什么?
另外不确定是否重要,但我正在使用Ubuntu 14.04进行编译。
编辑:
发现了另一个问题:
bfh.bfType = 0x424D;

应该是

bfh.bfType = 0x4D42;

但仍然看不到图片。

1
这里似乎是结构体成员对齐的问题。如果在声明结构体之前加上 #pragma pack(1),会发生什么? - SirDarius
现在我得到了“从...读取BMP文件头时出错”的错误。 - Dusan Malic
1
请确保在使用多字节值时使用了正确的字节顺序。 - pmg
我复制/粘贴了上面的代码,编译并运行它,然后尝试用GIMP打开图像。 - Dusan Malic
2
typedef unsigned int UINT 在64位和32位系统上的大小很可能是不同的。 - hookenz
显示剩余4条评论
3个回答

4

首先,您设置:

bfSize 指定文件的大小(以字节为单位)。

将其设置为无效值,您的代码结果为 1440112,而实际文件大小为 1080112

bfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) +
    bih.biWidth * bih.biHeight * sizeof(COLORREF_RGB);    

由于 sizeof(COLORREF_RGB) 实际上是 4 而不是 3。

另一个错误是你的结构体和类型的大小:

                                  expected size        actual size*
typedef unsigned int UINT;    //              2                  4
typedef unsigned long DWORD;  //              4                  8
typedef long int LONG;        //              4                  8
typedef unsigned short WORD;  //              2                  2
typedef unsigned char BYTE;   //              1                  1

* 我正在使用x86_64架构上的gcc编译器

您的偏移量与维基百科上的偏移量不匹配,您所使用的参考文献可能是针对16位操作系统上的16位编译器编写的,因此它假定int为2B类型(正如cup在评论中指出的)。

对于我来说,使用stdint.h中的值有效(有关Visual Studio的stdint.h指南在这里):

#include <stdint.h>

typedef uint16_t UINT;
typedef uint32_t DWORD;
typedef int32_t LONG;
typedef uint16_t WORD;
typedef uint8_t BYTE;

最后但并非最不重要的是,你需要关闭内存对齐,正如Weather Vane建议的那样


1
只是附带说明:bmp是16位Windows使用的格式,这就是为什么UINT是uint16_t的原因。在16位编译器上,int是16位。 - cup

2
我认为您的字段大小不正确,请尝试以下内容。
#pragma pack(push, 1)

typedef struct tagBITMAPFILEHEADER {
    WORD bfType;
    DWORD bfSize;
    WORD bfReserved1;
    WORD bfReserved2;
    DWORD bfOffBits;
} BITMAPFILEHEADER;

#pragma pack(pop)

还是一样的。我正在使用这个作为我的主要参考,他们说应该是unsigned int - Dusan Malic
它并没有说 unsigned int,而是说 UINT。那么在那个页面上定义了 UINT?我不关心你的页面说了什么,我已经按照这里的修改编译并运行了你的程序,它创建了一个 600x600 的黑色 24 位图,我的图像查看器可以显示它。 - Weather Vane
有点奇怪,但我刚在我的Windows电脑上编译了它,那里可以运行。正如我在帖子中提到的,我正在使用Ubuntu 14.04。 - Dusan Malic
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Weather Vane
将所有字段定义为char name[size],然后逐字节填充字段。这样就不会出现对齐字节等问题。 - user3629249
显示剩余2条评论

2
你正在尝试将一个C结构映射到一些外部定义的二进制格式,这样做存在一些问题:

大小

typedef unsigned int UINT;
typedef unsigned long DWORD;
typedef long int LONG;
typedef unsigned short WORD;
typedef unsigned char BYTE;

C语言只规定了这些类型的最小大小。它们在不同的编译器和操作系统中的实际大小可能会有所不同。您结构体成员的大小和偏移量可能与您预期的不同。唯一可以依赖的大小(在您今天可能遇到的几乎所有系统上)是char为8位。

填充

typedef struct tagBITMAPFILEHEADER {
    UINT bfType;
    DWORD bfSize;
    UINT bfReserved1;
    UINT bfReserved2;
    DWORD bfOffBits;
} BITMAPFILEHEADER;

DWORD 通常比 UINT 大两倍,实际上您的程序依赖于此。这通常意味着编译器会在 bfTypebfSize 之间引入填充以使后者具有其类型的适当对齐方式。但是 .bmp 格式 没有这种填充。

顺序

BITMAPFILEHEADER bfh;

bfh.bfType = 0x424D;
...
fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, f);
另一个问题是C语言没有指定类型的字节顺序(endianess)。在内存中,.bfType成员可能以[42][4D](大端)或[4D][42](小端)存储。特别是.bmp格式要求值按小端顺序存储。
解决方案:
您可能可以使用编译器特定的扩展(例如#pragma或编译器开关)来解决其中一些问题,但可能无法解决所有问题。
唯一正确的编写外部定义的二进制格式的方法是使用unsigned char数组,并逐个字节写入值。个人建议编写一组特定类型的帮助函数:
void w32BE (unsigned char *p, unsigned long ul)
{
  p[0] = (ul >> 24) & 0xff;
  p[1] = (ul >> 16) & 0xff;
  p[2] = (ul >>  8) & 0xff;
  p[3] = (ul      ) & 0xff;
}
void w32LE (unsigned char *p, unsigned long ul)
{
  p[0] = (ul      ) & 0xff;
  p[1] = (ul >>  8) & 0xff;
  p[2] = (ul >> 16) & 0xff;
  p[3] = (ul >> 24) & 0xff;
}
/* And so on */

还有一些用于编写整个.bmp文件或其中某些部分的函数:

int function write_header (FILE *f, BITMAPFILEHEADER bfh)
{
  unsigned char buf[14];

  w16LE (buf   , bfh.bfType);
  w32LE (buf+ 2, bfh.bfSize);
  w16LE (buf+ 6, bfh.bfReserved1);
  w16LE (buf+ 8, bfh.bfReserved2);
  w32LE (buf+10, bfh.bfOffBits);

  return fwrite (buf, sizeof buf, 1, f);
}

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