在 C 中创建简单的位图(没有外部库)

jos*_*eph 1 c image bitmap

出于学习目的,我想使用没有外部库(例如 SDL)的 C 编程语言创建单个 2x2 位图图像。

我已经读到我需要为位图文件创建一个标头,但无法理解如何在代码中实现它以及为什么 bmp 标头具有这些参数。我找不到任何关于如何使用 C 做到这一点的明确教程。

您能否通过一个简单的文档示例为我提供有关如何实现这一点的指导?我正在使用 Linux。

Bar*_*ani 6

为什么 bmp 标头有这些参数

位图中的像素数据本质上是一维字节数组。如果没有显示宽度、高度、位计数和其他位图信息的标头信息,就不可能解释这些数据。

有不同类型的位图,包括不同的调色板格式和非调色板 16、24 或 32 位,以及其中一些组中的不同类型。

标头还包括由于历史原因而存在的其他信息。这对学习 C 没有什么价值。你只需要接受它。

头信息有两种结构BITMAPFILEHEADERBITMAPINFO它有点复杂,因为这些结构预计会被打包。

像素数据取决于位图格式。没有前面提到的单一位图格式。下面的示例显示了一个 24 位位图。

其他奇怪的是每行的宽度应该始终是 4 个字节的倍数。这称为填充。位图行也从底部开始,而不是从顶部开始。这在其他资源中有解释

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main(void)
{
    //width, height, and bitcount are the key factors:
    int32_t width = 2;
    int32_t height = 2;
    uint16_t bitcount = 24;//<- 24-bit bitmap

    //take padding in to account
    int width_in_bytes = ((width * bitcount + 31) / 32) * 4;

    //total image size in bytes, not including header
    uint32_t imagesize = width_in_bytes * height;

    //this value is always 40, it's the sizeof(BITMAPINFOHEADER)
    const uint32_t biSize = 40;

    //bitmap bits start after headerfile, 
    //this is sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)
    const uint32_t bfOffBits = 54; 

    //total file size:
    uint32_t filesize = 54 + imagesize;

    //number of planes is usually 1
    const uint16_t biPlanes = 1;

    //create header:
    //copy to buffer instead of BITMAPFILEHEADER and BITMAPINFOHEADER
    //to avoid problems with structure packing
    unsigned char header[54] = { 0 };
    memcpy(header, "BM", 2);
    memcpy(header + 2 , &filesize, 4);
    memcpy(header + 10, &bfOffBits, 4);
    memcpy(header + 14, &biSize, 4);
    memcpy(header + 18, &width, 4);
    memcpy(header + 22, &height, 4);
    memcpy(header + 26, &biPlanes, 2);
    memcpy(header + 28, &bitcount, 2);
    memcpy(header + 34, &imagesize, 4);

    //prepare pixel data:
    unsigned char* buf = malloc(imagesize);
    for(int row = height - 1; row >= 0; row--)
    {
        for(int col = 0; col < width; col++)
        {
            buf[row * width_in_bytes + col * 3 + 0] = 255;//blue
            buf[row * width_in_bytes + col * 3 + 1] = 0;//green
            buf[row * width_in_bytes + col * 3 + 2] = 0;//red
        }
    }

    FILE *fout = fopen("test.bmp", "wb");
    fwrite(header, 1, 54, fout);
    fwrite((char*)buf, 1, imagesize, fout);
    fclose(fout);
    free(buf);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

您可以使用结构重写此代码。在此代码结构中,包含#pragma pack(push, 1)依赖于编译器的内容。如果#pragma指令有效,请使用此版本。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#pragma pack(push, 1)
struct my_BITMAPFILEHEADER {
    uint16_t bfType;
    uint32_t bfSize;
    uint16_t bfReserved1;
    uint16_t bfReserved2;
    uint32_t bfOffBits;
};

struct my_BITMAPINFOHEADER {
    uint32_t biSize;
    int32_t  biWidth;
    int32_t  biHeight;
    uint16_t biPlanes;
    uint16_t biBitCount;
    uint32_t biCompression;
    uint32_t biSizeImage;
    int32_t  biXPelsPerMeter;
    int32_t  biYPelsPerMeter;
    uint32_t biClrUsed;
    uint32_t biClrImportant;
};
#pragma pack(pop)

int main(void)
{
    if(sizeof(struct my_BITMAPFILEHEADER) != 14 &&
        sizeof(struct my_BITMAPINFOHEADER) != 40)
    {
        printf("bitmap structures not packed properly\n");
        return 0;
    }

    //only width and height can be changed in this code:
    int width = 2;
    int height = 2;

    int bitcount = 24;//<- 24-bit bitmap
    int width_in_bytes = ((width * bitcount + 31) / 32) * 4;    //for padding
    uint32_t imagesize = width_in_bytes * height;   //total image size

    struct my_BITMAPFILEHEADER filehdr = { 0 };
    struct my_BITMAPINFOHEADER infohdr = { 0 };

    memcpy(&filehdr, "BM", 2);//bitmap signature
    filehdr.bfSize = 54 + imagesize;//total file size
    filehdr.bfOffBits = 54; //sizeof(filehdr) + sizeof(infohdr)

    infohdr.biSize = 40; //sizeof(infohdr)
    infohdr.biPlanes = 1; //number of planes is usually 1
    infohdr.biWidth = width;
    infohdr.biHeight = height;
    infohdr.biBitCount = bitcount;
    infohdr.biSizeImage = imagesize;

    //prepare pixel data:
    unsigned char* buf = malloc(imagesize);
    for(int row = height - 1; row >= 0; row--)
    {
        for(int col = 0; col < width; col++)
        {
            buf[row * width_in_bytes + col * 3 + 0] = 255;//blue
            buf[row * width_in_bytes + col * 3 + 1] = 0;//red
            buf[row * width_in_bytes + col * 3 + 2] = 0;//green
        }
    }

    FILE *fout = fopen("test.bmp", "wb");
    fwrite(&filehdr, sizeof(filehdr), 1, fout);
    fwrite(&infohdr, sizeof(infohdr), 1, fout);
    fwrite((char*)buf, 1, imagesize, fout);
    fclose(fout);
    free(buf);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

第一个版本用来unsigned char header[54]实现同样的事情。查看每个数据的大小及其在内存中的相对位置。该结构长 14 个字节。bfType从 0 开始,长度为 2 个字节(16 位)。bfSize从位置 2 开始,长度为 4 个字节...

struct my_BITMAPFILEHEADER {
    uint16_t bfType; //starts at 0, 2 bytes long
    uint32_t bfSize; //starts at 2, 4 bytes long
    uint16_t bfReserved1; //starts at 6, 2 bytes long
    uint16_t bfReserved2; //starts at 8, 2 bytes long
    uint32_t bfOffBits; //starts at 10, 4 bytes long
};
Run Code Online (Sandbox Code Playgroud)

然后你有下一个 40 字节长的结构:

struct my_BITMAPINFOHEADER {
    uint32_t biSize;//starts at 14
    int32_t  biWidth;//starts at 18
    int32_t  biHeight;//starts at 22
    uint16_t biPlanes;//starts at 26
    uint16_t biBitCount;//starts at 28
    uint32_t biCompression;//starts at 30
    uint32_t biSizeImage;//starts at 34
    int32_t  biXPelsPerMeter;
    int32_t  biYPelsPerMeter;
    uint32_t biClrUsed;
    uint32_t biClrImportant;
};
Run Code Online (Sandbox Code Playgroud)

biSize从 0 开始。但之前的结构是 14 个字节。所以biSzie从 开始14。这应该揭示了以前版本中用于将整数复制到的数字的奥秘header