【问题标题】:Create simple bitmap in C (without external libraries)在 C 中创建简单的位图(没有外部库)
【发布时间】:2018-04-29 19:30:55
【问题描述】:

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

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

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

【问题讨论】:

  • 您没有提供任何问题。向我们展示您尝试过的内容以及您不理解的内容。
  • 使用图像编辑器创建一个简单的已知位图,然后使用文件格式资源such as this 计算出来,然后以编程方式创建相同的东西。
  • 如果您使用的是 Linux,那么我建议使用 NetPBM 格式:pbm 用于黑白图像,pgm 用于灰度图像,而 ppm 用于全彩色图像。

标签: c image bitmap


【解决方案1】:

为什么bmp头有那些参数

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

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

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

头信息在BITMAPFILEHEADERBITMAPINFO两个结构体中,因为结构体要打包,所以稍微复杂一些。

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

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

#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;
}

您可以使用结构重写此代码。在这个代码结构中包含#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;
}

第一个版本使用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
};

然后你有下一个 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;
};

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

【讨论】:

  • 感谢您的回复。我把这个作为正确的答案。但是如果你能清除它们,我有几个问题:1)为什么在计算'width_in_bytes'的值时你总和31? 2)您用 5 个字节(32 位)声明宽度和高度,您可以用 8 位整数声明它们还是必须始终“填充”hader 所需的内存(54 位)?
  • 1) 这是一个确保宽度始终为 4 个字节的倍数的数学技巧 - 无论 bitcountwidth 的值如何,请参见 answer 2) 宽度和高度存储为 32 位值或 4 个字节(不是 5 个字节),它们总是必须是 4 个字节。我认为您实际上是在询问像素数据。在此示例中,每个像素为 3 个字节或 24 位(红、绿、蓝倒序)。也可以用 4 个字节、1 个字节等来表示像素。
  • 我明白了,现在我明白了!还有一件事:如果文件大小有 4 个字节要复制,为什么你决定将 'bfOffBits' 与 'header + 2' 分开 8 个字节?不应该是 'memcpy(header + 8, &bfOffBits, 4);' ?
  • 查看编辑后的答案,我尝试相对于实际位图结构进行解释。
猜你喜欢
  • 2023-03-22
  • 2017-10-31
  • 1970-01-01
  • 1970-01-01
  • 2020-05-02
  • 1970-01-01
  • 2011-08-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多