在 C 中创建 bmp 文件

Posted

技术标签:

【中文标题】在 C 中创建 bmp 文件【英文标题】:Creating bmp file in C 【发布时间】:2014-11-30 16:46:53 【问题描述】:

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

但还是看不到图片。

【问题讨论】:

这里看起来像结构成员对齐问题。如果你在声明结构之前加上 #pragma pack(1) 会发生什么? 现在我得到“从...读取 BMP 文件头时出错” 确保对多字节值使用正确的字节顺序 您引用的错误不可能出现在您粘贴在问题中的代码中。你是怎么得到它的? typedef unsigned int UINT 在 64 位和 32 位上的大小很可能不同。 【参考方案1】:

首先,你设置:

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 所指出的那样),因此它假定 int 为 2B输入。

使用来自 stdint.h 的值对我有用(Visual Studio here 的 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;

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

【讨论】:

顺便说明一下:bmp 是 16 位窗口使用的格式,这就是为什么 UINT 是 uint16_t。在 16 位编译器上,int 是 16 位。【参考方案2】:

我认为您的字段大小不正确,试试这个

#pragma pack(push, 1)

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

#pragma pack(pop)

【讨论】:

还是一样。我使用this 作为我的主要参考,他们说应该是unsigned int 它没有说unsigned int 它说UINT 在该页面上的哪个位置定义了UINT?我不在乎你的页面说什么,我编译并运行了你的程序,并在此处注释了更改,它创建了一个 600x600 黑色 24 位位图,我的图像查看器显示。 您是否包含@SirDarius 在第一条评论中推荐的pack 编译指示(我在您最近发表评论后添加到我的答案中)? 奇怪的事情,但我只是在我的 Windows PC 上编译它,它在那里工作。正如我在帖子中提到的,我使用的是 Ubuntu 14.04。 禁止使用特定于机器的字段大小定义可移植文件格式,请参阅@Matt 的评论。【参考方案3】:

您正在尝试将 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+ 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);

【讨论】:

以上是关于在 C 中创建 bmp 文件的主要内容,如果未能解决你的问题,请参考以下文章

在EXAM的文件夹中创建一个空白的BMP文件,文件名为嘟嘟,怎么做呢 把步骤写下来把

如何在运行时使用 .bmp 文件并在 Unity 中创建纹理?

为工具栏图像条创建 32bpp BMP 文件?

C++ 如何创建位图文件

如何在 C 语言的 windows 的 %appdata% 路径中创建目录并创建文件

如何在 C 中创建具有设置权限/时间/等的文件?