elanonrigby
elanonrigby

Reputation: 489

Create simple bitmap in C (without external libraries)

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

Answers (2)

Privacy102
Privacy102

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

Barmak Shemirani
Barmak Shemirani

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

Related Questions