Drexel Autonomous Systems Lab

 

                   

 

ADDING TWO BMPs

Keywords: Addition of BMPs, 8-bit bmp, 24-bit bmp, adding pictures/images, 256 colors.

This tutorial aims to quickly teach you how to right your ANSI C code to add 2 images. Adding two images is important because it allows you to modify an image that is projected, say, on a whiteboard by adding the information drawn on that white board assuming you used Mimio to capture the information drawn on top of your projected image. Furthermore bmp processing is the first step to learn image processing for machine vision.

Motivation and Audience

This tutorial's motivation is to show how to process bmp images by using ANSI C. Readers of this tutorial are assumed to have the following background and interests:

The rest of the tutorial is presented as follows:

 

Bit-Mapped Graphic Files

A bitmap is an array of bits that specifies the color of each pixel in a rectangular array of pixels. The number of bits devoted to an individual pixel determines the number of colors that can be assigned to that pixel. For example, if each pixel is represented by 8 bits, then a given pixel can be assigned 256 different colors (2^8 = 256). Disk files that store bitmaps usually contains one or more information blocks that store information such as number of bits per pixel, number of pixels in each row, and number of rows in the array. Such a file might also contain a color table (sometimes called a color palette). A color table maps numbers in the bitmap to specific colors. For example an 8-bit BMP file would have a color table that matches 256 colors to 3 byte RGB values of the colors, whereas a 24-bit BMP file does not have a color table.

Let's have a look into a BMP files inside. Following table shows the format of a BMP file.

Table 1. BMP File Format.

NAME

SIZE

POS.

DESCRIPTION

HEADER

14 BYTES TOTAL

Signature

2 bytes

0-1

BM

File Size

4 bytes

2-5

File Size in Bytes

Reserved 1

2 bytes

6-7

Unused (=0)

Reserved 2 2 bytes 8-9 Unused (=0)
Data Offset 4 bytes 10-13 Offset Value to Raster Data
 
INFO HEADER 40 BYTES TOTAL
Size 4 bytes 14-17 Size of Info Header (=40)
Width 4 bytes 18-21 Image Width in Pixels
Height 4 bytes 22-25 Image Height in Pixels
Planes 2 bytes 26-27 Number of Planes (=1)
Bit Count 2 bytes 28-29 Bits per Pixel (1, 4, 8 ,16, 24)
Compression 4 bytes 30-33

Type of Compression

0 = No compression

1 = 8-bit RLE encoding

2 = 4-bit RLE encoding

Image Size 4 bytes 34-37

(Compressed) Image Size

X Pixels per Meter 4 bytes 38-41 Horizontal Resolution pixels/m
Y Pixels per Meter 4 bytes 42-45 Vertical Resolution pixels/m
Colors Used 4 bytes 46-49 Number of Actual Colors Used (0 = 24-bit)
Colors Important 4 bytes 50-53 Number of Important Colors (=0 is all)

In Table 1, it can be observed that first 54 bytes of a BMP is used to describe the image file. Next, there might be a color table (There will probably be a color table if the bit count is less than or equal to 8 bits.) For 8-bit images there is a color table and its size is 1024 ( = (2^3)*4 ) bytes. Therefore from byte # 54 to 1023 of 8-bit images there is the color table. Then starting at byte # 1078 of 8-bit images ( = 14 + 40 + 1024), there is the raster data. Raster data is composed of the corresponding color value of each pixel. Therefore raster data is width*height long for 8-bit BMP files. For 24-bit images there is no color table and raster data starts right at byte # 54. Raster data of a 24-bit BMP is composed of RGB value of each pixel in 3-byte stacks. First byte is the blue value of the color ( = 0 - 255), second byte is the green value of the color (= 0 - 255) and the last byte is red value of the color (= 0 - 255). Note that numbers from 0 to 255 can be stored in a byte, or as unsigned character.

 

Reading The Header of a BMP

To be compiled with Turbo C
Note: download header.c rather than cutting and pasting from below.

Explanation of Header.c:

 

Header.c first opens the bmp file that is input to the main of the code and then retrieves the header information of a bmp file using the function getImageInfo. The format of a bmp file is given in Table 1. getImageInfo winds the bmp file to the relevant byte to read the required data and reads the required data byte by byte to obtain the value of the required data. Note that fseek function is used to wind the bmp file. As an example consider retrieving the width of the bmp. The width of the bmp is stored at bytes # 18-21. Therefore getImageInfo first rewinds the bmp file to byte # 18. Then reads 4 bytes of data byte by byte to find the 4-byte value of the width of the bmp. Instead of reading byte by byte you could also read 4-byte by 4-byte by using a long integer type variable for reading the bmp file. However, for there are some information stored in only 2-bytes that would require us to have two functions; one that reads 2 byte information and the other that reads 4-byte information.

 

/* Read a Bitmap File */

#include<stdio.h>
#include<stdlib.h>
#include<math.h>

/* Functions */
long getImageInfo(FILE*, long, int);

int main(int argc, char * argv[])
{

FILE  *bmpInput;
char    signature[2];                        /* Signature of the Image File BM = BMP */
int       nRows, nCols;                     /* Row and Column size of the Image */
int       xpixpeRm, ypixpeRm;          /* Pixel/m */
long     nColors;                              /* BMP number of colors */
long     fileSize;                               /* BMP file size */
long     vectorSize;                          /* BMP's raster data in number of bytes */
int        nBits;                                  /* # of BIts per Pixel */
int        rasterOffset;                        /* Beginning of the Raster Data */
int        i;


if(argc<2)

{
     printf("Usage: %s bmpInput.bmp\n", argv[0]);
     exit(0);
}
printf("Reading filename %s\n", argv[1]);

if ((bmpInput = fopen(argv[1], "rb")) == NULL)

{
     printf("Can not read BMP file");
     exit(0);
}

 

/* Read BMP input file */

/* Signature of the File BM = BMP at byte # 0*/
for(i=0; i<2; i++)
{
     signature[i] = (char)getImageInfo(bmpInput, i, 1);
}
if ((signature[0] == 'B') && (signature[1] == 'M')) printf("It is verified that the Image is in Bitmap format\n");
else
{
     printf("The image is not a bitmap format, quitting....\n");
     exit(0);
}

/* Read BMP bits/pixel at byte #28 */
nBits = (int)getImageInfo(bmpInput, 28, 2);
printf("The Image is \t%d-bit\n", nBits);

/* Position of First Raster Data at byte # 10*/
rasterOffset = (int)getImageInfo(bmpInput, 10, 4);
printf("The beginning of the Raster Data \nis at \t\t%d byte\n", rasterOffset);

/* Read BMP file size at byte # 2 */
fileSize = getImageInfo(bmpInput, 2, 4);
printf("File size is \t%ld byte\n", fileSize);

/* Read BMP width at byte #18 */
nCols = (int)getImageInfo(bmpInput, 18, 4);
printf("Width: \t\t%d\n", nCols);

/* Read BMP height at byte #22 */
nRows = (int)getImageInfo(bmpInput, 22, 4);
printf("Height: \t%d\n", nRows);

/* # of Pixels in a meter in x direction at byte # 38 */
xpixpeRm = (int)getImageInfo(bmpInput, 38, 4);
printf("Image has \t%d pixels per m in x-dir.\n", xpixpeRm);

/* # of Pixels in a meter in y direction at byte # 42 */
ypixpeRm = getImageInfo(bmpInput, 42, 4);
printf("Image has \t%d pixels per m in y-dir.\n", ypixpeRm);

/* Read number of colors at byte #46 */
nColors = pow(2L,nBits);
printf("There are \t%ld number of Colors \n", nColors);

vectorSize = (long)((long)nCols*(long)nRows);
printf("Vector Size is \t%ld\n", vectorSize);

fclose (bmpInput);

} /* end of main */


long getImageInfo(FILE* inputFile, long offset, int numberOfChars)
{

unsigned char     *ptrC;
long                    value=0L;
unsigned char      dummy;
int                       i;

dummy = '0';      ptrC = &dummy;

fseek(inputFile, offset, SEEK_SET);

for(i=1; i<=numberOfChars; i++)
{
     fread(ptrC, sizeof(char), 1, inputFile);
     value = (long)(value + (*ptrC)*(pow(256, (i-1))));
}

return(value);

} /* end of getImageInfo */

 

 

 

Combining Two 8-bit grayscale BMPs

 

To be compiled with Turbo C
Note: download bmpadd3.c rather than cutting and pasting from below.

 

 

 

/*
Combines Two 8-bit Images, writes the raster data to a text file for reference.

First it copies the first BMP's header, color table to the output BMP file, which is going to be the combination of the two images. Then whenever there is a color information other than white (255) in the second file that information is copied to the output file. Therefore it is supposed that white is the unnecessary information in the second file. Furthermore this code needs to have same sized bmp files to accomplish the addition.

Because every bmp that has a color table would have different color tables, only grayscale addition is possible with bmp files having color tables (bmp files that are 8-bit and lower). Grayscale 8-bit images has the same color table, 256 shades of gray.

*/

#include<stdio.h>
#include<stdlib.h>
#include<math.h>


/* Functions */
long getImageInfo(FILE*, long, int);
void copyImageInfo(FILE*, FILE*);
void copyColorTable(FILE*, FILE*, int);

void main(void)
{

FILE                     *bmpfile1, *bmpfile2, *bmpOutput, *rawdata;                          /* Files that are used with this code */
unsigned char        *pChar1, *pChar2, *pCharO, dummy1, dummy2, dummyO;     /* The pointers here are used to read and write raster data and the rest are used to safely initialize the pointers */
int                          r, r1, r2, c, c1, c2;                                                                     /* Variables that are assigned to width and height of the bmp files 1 and 2  */
int                          nColors = 256;                                                                          /* Number of colors = 256 */ 
long                       fileSize1, fileSize2;                                       
long                       nbits1, nbits2;


/* Initialization */
dummy1 = '0'; pChar1 = &dummy1;
dummy2 = '0'; pChar2 = &dummy2;
dummyO = '0'; pCharO = &dummyO;


printf("ONLY 8 BIT BMPs CAN BE PROCESSED.!!!\n");
printf("IMAGE SIZES AND FORMATS MUST BE SAME.!!!\n");
printf("Reading bmpin1.bmp & bmpin2.bmp\n bmpin1.bmp is the main BMP.\n");

if ((bmpfile1 = fopen("bmpin1.bmp", "rb")) == NULL)
{
     printf("Can not open bmpin1.bmp\n");
     exit(0);
}

if ((bmpfile2 = fopen("bmpin2.bmp", "rb")) == NULL)
{
     printf("Can not open bmpin2.bmp\n");
     exit(0);
}

if ((bmpOutput = fopen("bmpOutx.bmp", "wb")) == NULL)
{
     printf("Can not open bmpoutx.bmp\n");
     exit(0);
}

if ((rawdata = fopen("rawdata.txt", "w")) == NULL)
{
     printf("Can not open rawdata.txt");
     exit(0);
}

/* Get Info of the Files and Compare */
c1 = (int)getImageInfo(bmpfile1, 18, 4);
c2 = (int)getImageInfo(bmpfile2, 18, 4);
printf("Width of File 1: \t\t%d\n", c1);
printf("Width of File 2: \t\t%d\n", c2);

r1 = (int)getImageInfo(bmpfile1, 22, 4);
r2 = (int)getImageInfo(bmpfile2, 22, 4);

printf("Height of File 1: \t%d\n", r1);
printf("Height of File 2: \t%d\n", r2);
if ( (r1 != r2) || (c1 != c2) )
{
     printf("Rows or cloumns don't match. Must enter same size BMPs.");
     exit(0);
}

nbits1 = getImageInfo(bmpfile1, 28, 2);
nbits2 = getImageInfo(bmpfile2, 28, 2);

printf("Bits/pixel of File 1: \t%ld\n", nbits1);
printf("Bits/pixel of File 2: \t%ld\n", nbits2);
if (nbits1 != nbits2)
{
     printf("File formats don't match. Must have same formatted BMPs");
     exit(0);
}

copyImageInfo(bmpfile1, bmpOutput);

copyColorTable(bmpfile1, bmpOutput, nColors);

/* Beginning of the Raster Data is set*/
fseek(bmpfile1, (int)(54+4*nColors), SEEK_SET);
fseek(bmpfile2, (int)(54+4*nColors), SEEK_SET);
fseek(bmpOutput, (int)(54+4*nColors), SEEK_SET);

/* BMP PROCESSING */

for(r=0; r<r1; r++)
{
     for(c=0; c<c1; c++)
     {
          fread(pChar1, sizeof(char), 1, bmpfile1);
          fread(pChar2, sizeof(char), 1, bmpfile2);

          /*////////////////////*/

          /* Copy main image to output */
          *pCharO = *pChar1;
 

          /* Copy the non-white of image 2 to output */
          if(*pChar2 != 255) *pCharO = *pChar2;

          /*////////////////////*/

          /* Raster Data is written to rawdata.txt for reference */
          fprintf(rawdata, "%d %d %d\n", *pChar1, *pChar2, *pCharO);

          fwrite(pCharO, sizeof(char), 1, bmpOutput);

     }
}

fclose(bmpOutput);
fclose(bmpfile1);
fclose(bmpfile2);

} /* end of main */

long getImageInfo(FILE* inputFile, long offset, int numberOfChars)
{

unsigned char    *ptrC, dummy = '0';
long                   value = 0L;
int                      i;

ptrC = &dummy;

fseek(inputFile, offset, SEEK_SET);

for(i=1; i<=numberOfChars; i++)
{
     fread(ptrC, sizeof(char), 1, inputFile);
     /* Byte by byte reading */
     value = (long)(value + (*ptrC)*(pow(256, (i-1))));
}
return(value);

} /* end of getImageInfo */

void copyImageInfo(FILE* inputFile, FILE* outputFile)
{

/* copies inputFile's header data to outputFile */

unsigned char          *ptrC, dummy = '0';
int                            i;

ptrC = &dummy;

fseek(inputFile, 0L, SEEK_SET);
fseek(outputFile, 0L, SEEK_SET);

for(i=1; i<=54; i++)
{
     fread(ptrC, sizeof(char), 1, inputFile);
     fwrite(ptrC, sizeof(char), 1, outputFile);
}

} /* end of copyImageInfo */

void copyColorTable(FILE* inputFile, FILE* outputFile, int nColors) {

/* copies inputFile's color table to outputFile */
unsigned char               *ptrC, dummy = '0';
int                                 i;

ptrC = &dummy;

fseek(inputFile, 54L, SEEK_SET);
fseek(outputFile, 54L, SEEK_SET);

for(i=1; i<=4*nColors; i++)
{
     fread(ptrC, sizeof(char), 1, inputFile);
     fwrite(ptrC, sizeof(char), 1, outputFile);
}

} /* end of copyColorTable */

 

Example: Below pictures illustrate the addition of two 8-bit grayscale images.

 

 

 

 

Combining Two 24-bit BMPs

 

To be compiled with Turbo C
Note: You can download bmp24ad3.c.

 

It is quite similar to 8-bit version. The only difference is in the bmp processing part of the algorithm. In 24-bit images every pixel has a RGB value as mentioned before in the beginning of this tutorial.

 

/* COMBINE TWO 24-BIT IMAGES */
.....

....

/*/////////////////////////*/
/*//// BMP PROCESSING /////*/


for(r=0; r<r1; r++)
{
     for(c=0; c<c1; c++)
     {
          for(i=0; i<3; i++)
          {

               fread(pChar1, sizeof(char), 1, bmpfile1);

               /* Copy main image to output */
               *pCharO = *pChar1;
               fwrite(pCharO, sizeof(char), 1, bmpOutput);

               fread(pChar2, sizeof(char), 1, bmpfile2);
               store4byte2[i] = *pChar2;

               j = j + 1L;

          }

          /* Copy the non-white of image 2 to output */

          if( (store4byte2[0] != 255) || (store4byte2[1] != 255) || (store4byte2[2] != 255) )
          {
               j = j - 3L;

               fseek(bmpOutput, (54L+j), SEEK_SET);
               for(i=0; i<3; i++)
               {
                    fwrite(&store4byte2[i], sizeof(char), 1, bmpOutput);
                    j = j + 1L;
                }
          }

     }
}
 

....

....
 

 

Example: Below pictures illustrate the addition of two 24-bmp images.

 

 

 

 

 

Final Words

This tutorial's objective was to show how to add two images by using ANSI C.

 

Click here to email me