在C中创建bmp文件

时间:2014-11-30 16:46:54

标签: c bmp

我正在尝试创建.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;

但仍然无法看到图像。

3 个答案:

答案 0 :(得分: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

您的偏移量与wikipedia上的偏移量不匹配,您正在使用的引用可能是针对16位操作系统上的16位编译器编写的(如cup in a comment所指出的)因此它假定{{ 1}}是2B型。

使用int中的值为我工作(Visual Studio herestdint.h指南):

stdint.h

最后但并非最不重要的是,您必须将内存对齐关闭为suggested by Weather Vane

答案 1 :(得分:2)

我相信您的字段大小不正确,请尝试此

#pragma pack(push, 1)

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

#pragma pack(pop)

答案 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 format没有这样的填充。

顺序

BITMAPFILEHEADER bfh;

bfh.bfType = 0x424D;
...
fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, f);

另一个问题是C语言没有指定类型的 endianess .bfType成员可以在内存中存储为[42][4D](Big-Endian)或[4D][42](Little-Endian)。 .bmp format特别要求以Little-Endian顺序存储值。

解决方案

可能能够通过使用特定于编译器的扩展(例如#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, bfh.bfSize);
  w16LE (buf, bfh.bfReserved1);
  w16LE (buf, bfh.bfReserved2);
  W32LE (buf, bfh.bfOffBits);

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