почему заголовок bmp имеет эти параметры
Пиксельные данные в растровом изображении по сути являются одномерным массивом байтов. Эти данные невозможно интерпретировать без информации заголовка, которая показывает ширину, высоту, количество битов и другую информацию о растровом изображении.
Существуют разные типы растровых изображений, в том числе разный формат палитры и непалитры 16, 24 или 32 бита, а также разные типы в некоторых из этих групп.
Заголовок также включает в себя другую информацию, которая существует по историческим причинам. Это мало что значит для изучения C. Вы просто должны это принять.
Информация заголовка представлена в двух структурах BITMAPFILEHEADER
и BITMAPINFO
Это немного сложнее, поскольку предполагается, что структуры упакованы.
Данные пикселей зависят от формата растрового изображения. Существует не один формат растрового изображения, как упоминалось ранее. В приведенном ниже примере показано 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;
}
Вы можете переписать этот код, используя структуры. В этой структуре кода упаковано #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