出于学习目的,我想使用C编程语言创建一个2x2位图图像,没有外部库(例如SDL)。
我已经读过我需要为位图文件创建一个标题但是却无法理解如何在代码中实现它以及为什么bmp标头具有这些参数。我找不到任何关于如何使用C来完成它的明确教程。
您能否通过一个简单的文档示例为我提供一些如何实现这一目标的指导?我正在使用Linux。
答案 0 :(得分:1)
为什么bmp标头具有这些参数
位图中的像素数据本质上是一维字节数组。如果没有显示宽度,高度,位数和其他有关位图信息的标题信息,则无法解释此数据。
有不同类型的位图,包括不同的调色板格式和非调色板16,24或32位,以及其中一些组中的不同类型。
标题还包括由于历史原因而存在的其他信息。这对于学习C几乎没有价值。你只需要接受它。
标题信息有两种结构BITMAPFILEHEADER
和BITMAPINFO
这有点复杂,因为预期结构会被打包。
像素数据取决于位图格式。如前所述,没有一种位图格式。下面的示例显示了一个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个字节。因此biSzie
从14
开始。这应该揭示以前版本中用于将整数复制到header