Reputation: 489
For learning purposes, I want to create a single 2x2 bitmap image using C programming language with no external libraries (such as SDL).
I've read that I need to create a header for the bitmap file but failed to understand how to implement it in code and why does the bmp header have those parameters. I can't find any clear tutorial on how I could do it using C.
Could you provide me some guidance on how to implement this by a simple documented example? I'm using Linux.
Upvotes: 1
Views: 14608
Reputation: 1
#include<stdio.h>
#include<conio.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include <stdint.h>
long asciiToBinary(int n);
long asciiToBinary(int n) {
int remainder;
long binary = 0, i = 1;
while(n != 0) {
remainder = n%2;
n = n/2;
binary= binary + (remainder*i);
i = i*10;
}
return binary;
}
typedef struct
{ //BITMAPFILEHEADER
char bfType[2];
int bfSize;
short bfReserved1;
short bfReserved2;
int bfOffBits;
//BITMAPINFOHEADER
int biSize;
int biWidth;
int biHeight;
short biPlanes;
short biBitCount;
int biCompression;
int biSizeImage;
int biXPelsPerMeter;
int biYPelsPerMeter;
int biClrUsed;
int biClrImportant;
} BITMAPINFOHEADER;
typedef struct {
unsigned char blue;
unsigned char green;
unsigned char red;
} PIXEL;
int imageInfo(BITMAPINFOHEADER *img)
{
int offset = 0;
printf("------------------------------------------------------------- BITMAPFILEHEADER\n");
printf("0x%08X (%08d) bfType %8c%c (0x%04X)\n", offset, offset, img->bfType[0], img->bfType[1], *(short *)img->bfType);
offset += sizeof img->bfType;
printf("0x%08X (%08d) bfSize %9d (0x%08X)\n", offset, offset, img->bfSize, img->bfSize);
offset += sizeof img->bfSize;
printf("0x%08X (%08d) bfReserved1 %9hd (0x%04hX)\n", offset, offset, img->bfReserved1, img->bfReserved1);
offset += sizeof img->bfReserved1;
printf("0x%08X (%08d) bfReserved2 %9hd (0x%04hX)\n", offset, offset, img->bfReserved2, img->bfReserved2);
offset += sizeof img->bfReserved2;
printf("0x%08X (%08d) bfOffBits %9d (0x%08X)\n", offset, offset, img->bfOffBits, img->bfOffBits);
printf("------------------------------------------------------------- BITMAPINFOHEADER\n");
offset += sizeof img->bfOffBits;
printf("0x%08X (%08d) biSize %9d (0x%08X)\n", offset, offset, img->biSize, img->biSize);
offset += sizeof img->biSize;
printf("0x%08X (%08d) biWidth %9d (0x%08X)\n", offset, offset, img->biWidth, img->biWidth);
offset += sizeof img->biWidth;
printf("0x%08X (%08d) biHeight %9d (0x%08X)\n", offset, offset, img->biHeight, img->biHeight);
offset += sizeof img->biHeight;
printf("0x%08X (%08d) biPlanes %9d (0x%04hX)\n", offset, offset, img->biPlanes, img->biPlanes);
offset += sizeof img->biPlanes;
printf("0x%08X (%08d) biBitCount %9d (0x%04hX)\n", offset, offset, img->biBitCount, img->biBitCount);
offset += sizeof img->biBitCount;
printf("0x%08X (%08d) biCompression %9d (0x%08X)\n", offset, offset, img->biCompression, img->biCompression);
offset += sizeof img->biCompression;
printf("0x%08X (%08d) biSizeImage %9d (0x%08X)\n", offset, offset, img->biSizeImage, img->biSizeImage);
offset += sizeof img->biSizeImage;
printf("0x%08X (%08d) biXPelsPerMeter %9d (0x%08X)\n", offset, offset, img->biXPelsPerMeter, img->biXPelsPerMeter);
offset += sizeof img->biXPelsPerMeter;
printf("0x%08X (%08d) biYPelsPerMeter %9d (0x%08X)\n", offset, offset, img->biYPelsPerMeter, img->biYPelsPerMeter);
offset += sizeof img->biYPelsPerMeter;
printf("0x%08X (%08d) biClrUsed %9d (0x%08X)\n", offset, offset, img->biClrUsed, img->biClrUsed);
offset += sizeof img->biClrUsed;
printf("0x%08X (%08d) biClrImportant %9d (0x%08X)\n", offset, offset, img->biClrImportant, img->biClrImportant);
return offset;
}
int main(){
uint32_t RGB565ColorTable[] = {
0x7E00000, 0xF8000000, 0x001F0000, 0
};
uint16_t myData[64];
for(int i = 0; i<64; i++)
{
myData[i] = 0x241B; //Random Color
}
FILE *image;
const char filename[] = "full.bmp";
FILE *fichier = fopen(filename, "rb");
FILE *fptr = fopen("tth.bmp","w+");
if(!fichier)
printf ("Erreur a l'ouverture du fichier [%s]\n", filename);
else
{
BITMAPINFOHEADER *img = malloc(sizeof *img);
char fpath[1000],mydata[100];
BITMAPINFOHEADER bih;
int i=0,b[8],g[8],r[8];
double asciiTobinary;
image=fopen("full.bmp","rb");
fseek(image,2,SEEK_SET); //reading the height and width
fread(&bih.biSize,4,1,image);
printf("\n \n Size of the image=%d\n",bih.biSize);
fseek(image,18,SEEK_SET);
fread(&bih.biWidth,4,1,image);
fseek(image,22,SEEK_SET);
fread(&bih.biHeight,4,1,image);
printf("\n \n Width of the image =%d \n Height of the image =%d \n pixel = b | g | r \n \n",bih.biWidth,bih.biHeight);
PIXEL pic[bih.biWidth*bih.biHeight*2],p;
while(!feof(image)){ //reading the pixels rgb values
fread(&p.blue,sizeof(p.blue),1,image);
fread(&p.green,sizeof(p.green),1,image);
fread(&p.red,sizeof(p.red),1,image);
pic[i]=p;
printf(" %d= %u | %u | %u ",i+54,pic[i].blue,pic[i].green,pic[i].red);
i++; }
fclose(image);
if(img)
{
int i, tmp, offset;
printf("\n[%s]\n", filename);
fread(img, sizeof(BITMAPINFOHEADER), 1, fichier);
if(img->bfType[0] == 'B' && img->bfType[1] == 'M')
{
offset = imageInfo(img);
if(img->biBitCount == 1)
{
printf("---------------------------------------------------------------------- RGBQUAD\n");
fread(&tmp, sizeof tmp, 1, fichier);
offset += sizeof img->biClrImportant;
printf("0x%08X (%08d) %9d (0x%08X)\n", offset, offset, tmp, tmp);
fread(&tmp, sizeof tmp, 1, fichier);
offset += sizeof tmp;
printf("0x%08X (%08d) %9d (0x%08X)\n", offset, offset, tmp, tmp);
printf("------------------------------------------------------------------------- BITS\n");
for(i = 0; i < img->biSizeImage / (int)sizeof tmp; i++)
{
char bin[33];
fread(&tmp, sizeof tmp, 1, fichier);
offset += sizeof tmp;
printf("0x%08X (%08d) %8d (0x%08X) (%s)\n", offset, offset, tmp, tmp, itoa(tmp, bin, 2));
}
}
}
free(img);
}
fclose(fichier);
}
if(fptr == NULL) {
printf("Error!");
exit(1);
}
fwrite(&filename, sizeof(filename), 1, fptr);
//fwrite(&myInfoHeader, sizeof(myInfoHeader), 1, fptr);
fwrite(&RGB565ColorTable, sizeof(RGB565ColorTable), 1, fptr);
fwrite(&myData, sizeof(myData), 1, fptr);
fclose(fptr);
return 0;
}
Upvotes: 0
Reputation: 31599
why does the bmp header have those parameters
The pixel data in bitmap is essentially a one-dimensional array of bytes. It's impossible to interpret this data without header information which reveals width, height, bit-count and other information about the bitmap.
There are different types of bitmaps, including different palette format and non-palette 16, 24, or 32 bits, and different types within some of these groups.
The header also includes other information which are there for historical reasons. This has little value for learning C. You just have to accept it.
The header information is in two structures BITMAPFILEHEADER
and BITMAPINFO
It's a little more complicated because the structures are expected to be packed.
The pixel data depends on the bitmap format. There is not a single bitmap format as mentioned earlier. The example below shows a 24-bit bitmap.
Other oddities is that the width of each row should always be multiple of 4 bytes. This called padding. Also the bitmap rows start at the bottom, not at the top. This is explained in other 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;
}
You can rewrite this code using structures. In this code structure is packed with #pragma pack(push, 1)
which is compiler dependent. Use this version if #pragma
directive works.
#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;
}
The first version used unsigned char header[54]
to achieve the same thing. See the size of each data and its relative position in memory. The structure is 14 bytes long. bfType
starts at 0, and is 2 bytes long (16 bits). bfSize
starts at position 2, and is 4 bytes long...
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
};
Then you have the next structure which is 40 bytes long:
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
starts at 0. But the previous structure was 14 bytes. So biSzie
starts at 14
. This should reveal the mystery of the numbers used in previous version to copy integers to header
Upvotes: 5