31

I would like to read a bitmap file into a struct and manipulate it like eg. making a mirror effect, but I cannot understand which kind of struct should I be creating in order to read into it.

Thank you for your help.

Sep Roland
  • 33,889
  • 7
  • 43
  • 76
Ege
  • 941
  • 4
  • 17
  • 36
  • 5
    @H2CO3: One word: wrong. – Agent_L Jan 11 '13 at 14:03
  • 1
    @Agent_L Why, precisely? –  Jan 11 '13 at 15:29
  • 1
    @H2CO3 because the BMP file formats are specified in structs, and there are structs available and prepared exactly for reading the files into them. Using plain array gives no information about the data structure whatsoever. – Agent_L Jan 11 '13 at 15:35
  • 1
    @Agent_L Seems you're misunderstanding me :) I'm talking about the actual BMP data (that's more interesting than some header clutter, which is necessary, but the information we're curious of in terms of manipulation is the bitmap itself.) –  Jan 11 '13 at 15:37
  • 1
    @H2CO3 BMP has such thing as row padding, which means even pixel values are not in straightforward array, but instead only 1 row is a valid array, so you need stride to get from beginning of one row to the next. And casting a void* buffer to a struct ending with dummy array gets you (in some cases) best of both worlds: all data structurized and ready for access. – Agent_L Jan 11 '13 at 15:38
  • 1
    @Agent_L Yes but that doesn't change that essentially it's an "n bytes by k rows"-type data structure, and it's easy to describe using an array even if there are gaps/padding/unused parts. –  Jan 11 '13 at 15:40
  • 4
    @H2CO3 sure, but to get into the array stage, you need to fight at least 2 levels of structs. – Agent_L Jan 11 '13 at 15:44
  • @Agent_L I'm not arguing that :) –  Jan 11 '13 at 15:46

3 Answers3

40

»This is how you manually load a .BMP file

The bitmap file format:

  • Bitmap file header
  • Bitmap info header
  • Palette data
  • Bitmap data

So on with the code part. This is our struct we need to create to hold the bitmap file header.

#pragma pack(push, 1)

typedef struct tagBITMAPFILEHEADER
{
    WORD bfType;  //specifies the file type
    DWORD bfSize;  //specifies the size in bytes of the bitmap file
    WORD bfReserved1;  //reserved; must be 0
    WORD bfReserved2;  //reserved; must be 0
    DWORD bfOffBits;  //specifies the offset in bytes from the bitmapfileheader to the bitmap bits
}BITMAPFILEHEADER;

#pragma pack(pop)

The bftype field checks to see if you are in fact loading a .BMP file, and if you are, the field should be 0x4D42.

Now we need to create our bitmapinfoheader struct. This holds info about our bitmap.

#pragma pack(push, 1)

typedef struct tagBITMAPINFOHEADER
{
    DWORD biSize;  //specifies the number of bytes required by the struct
    LONG biWidth;  //specifies width in pixels
    LONG biHeight;  //specifies height in pixels
    WORD biPlanes;  //specifies the number of color planes, must be 1
    WORD biBitCount;  //specifies the number of bits per pixel
    DWORD biCompression;  //specifies the type of compression
    DWORD biSizeImage;  //size of image in bytes
    LONG biXPelsPerMeter;  //number of pixels per meter in x axis
    LONG biYPelsPerMeter;  //number of pixels per meter in y axis
    DWORD biClrUsed;  //number of colors used by the bitmap
    DWORD biClrImportant;  //number of colors that are important
}BITMAPINFOHEADER;

#pragma pack(pop)

Now on to loading our bitmap.

unsigned char *LoadBitmapFile(char *filename, BITMAPINFOHEADER *bitmapInfoHeader)
{
    FILE *filePtr;  //our file pointer
    BITMAPFILEHEADER bitmapFileHeader;  //our bitmap file header
    unsigned char *bitmapImage;  //store image data
    int imageIdx=0;  //image index counter
    unsigned char tempRGB;  //our swap variable

    //open file in read binary mode
    filePtr = fopen(filename,"rb");
    if (filePtr == NULL)
        return NULL;

    //read the bitmap file header
    fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER),1,filePtr);

    //verify that this is a .BMP file by checking bitmap id
    if (bitmapFileHeader.bfType !=0x4D42)
    {
        fclose(filePtr);
        return NULL;
    }

    //read the bitmap info header
    fread(bitmapInfoHeader, sizeof(BITMAPINFOHEADER),1,filePtr); 

    //move file pointer to the beginning of bitmap data
    fseek(filePtr, bitmapFileHeader.bfOffBits, SEEK_SET);

    //allocate enough memory for the bitmap image data
    bitmapImage = (unsigned char*)malloc(bitmapInfoHeader->biSizeImage);

    //verify memory allocation
    if (!bitmapImage)
    {
        free(bitmapImage);
        fclose(filePtr);
        return NULL;
    }

    //read in the bitmap image data
    fread(bitmapImage,bitmapInfoHeader->biSizeImage,1,filePtr);

    //make sure bitmap image data was read
    if (bitmapImage == NULL)
    {
        fclose(filePtr);
        return NULL;
    }

    //swap the R and B values to get RGB (bitmap is BGR)
    for (imageIdx = 0;imageIdx < bitmapInfoHeader->biSizeImage;imageIdx+=3)
    {
        tempRGB = bitmapImage[imageIdx];
        bitmapImage[imageIdx] = bitmapImage[imageIdx + 2];
        bitmapImage[imageIdx + 2] = tempRGB;
    }

    //close file and return bitmap image data
    fclose(filePtr);
    return bitmapImage;
}

Now to make use of all of this:

BITMAPINFOHEADER bitmapInfoHeader;
unsigned char *bitmapData;
// ...
bitmapData = LoadBitmapFile("mypic.bmp",&bitmapInfoHeader);
//now do what you want with it, later on I will show you how to display it in a normal window

Later on I'll put up Writing to a .BMP, and how to load a targa file, and how to display them.«

Quoted from: http://www.vbforums.com/showthread.php?261522-C-C-Loading-Bitmap-Files-%28Manually%29 (User: BeholderOf). (Some minor corrections done)

Sep Roland
  • 33,889
  • 7
  • 43
  • 76
ollo
  • 24,797
  • 14
  • 106
  • 155
  • 3
    You forgot about structure packing. Using `WORD` in struct paired with binary read on 32-bit (or more) system is asking for trouble. – Agent_L Jan 11 '13 at 14:01
  • 9
    You mean adding `#pragma pack(push,1)` is required? – ollo Jan 11 '13 at 14:04
  • 6
    Absolutelly. Either pragma, or setting globally for project. Otherwise you have no idea how far `bfReserved1` and `bfReserved2` are apart. Maybe 2 bytes (as you intended), maybe 4, maybe else. – Agent_L Jan 11 '13 at 14:07
  • Thanks! Added pragma's for the structs. – ollo Jan 11 '13 at 14:10
  • 3
    I just learned it should work in GCC too : http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html (I thought it was Microsoft-specific feature) – Agent_L Jan 11 '13 at 14:14
  • 7
    With gcc you can use packed attribute instead too: `__attribute__((__packed__))` (see: http://gcc.gnu.org/onlinedocs/gcc-3.1.1/gcc/Type-Attributes.html) – ollo Jan 11 '13 at 14:15
  • 2
    Another approach would be to read entire file into a buffer and THEN cast the buffer ptr into struct ptr. It's useful when dealing with several formats, so one can match several types while loading only once. The dummy array at the end of BITMAPINFO takes advantage of this. Using `bfOffBits` is way simplier then. – Agent_L Jan 11 '13 at 14:26
  • Thank you very much sir very appreciated it. It would be great if you could put up some examples like rotate or mirror for basic examples. – Ege Jan 12 '13 at 14:38
  • 1
    `free`ing `bitmapImage` when it is ***0*** in this code makes no sense whatsoever. – Andon M. Coleman May 12 '14 at 00:15
  • For a good coding style, shouldn't `malloc` be called in the same method as its `free` counterpart (i.e. outside of this bitmap read method)? – Daniel Marschall May 25 '15 at 23:09
  • what about the color array? sometimes when there are few colors the data table doesn't contain the rgb but a set of numbers described at the beginning you didn't mentioned that – Ofer Magen Jun 27 '16 at 20:39
  • for the header alignment issue i find its much cleaner to just read the first 2 bytes into a pointer of its own, validate the type and then load the next 4 integers into a header struct. then you don't need to rely on #pragma – DevZer0 Nov 19 '17 at 04:26
  • Please note that there's a mismatch: struct`BITMAPINFOHEADER` has `bOffBits`, but in the function `LoadBitmapFile()` you use `bfOffBits`. Maybe all use `bfOffBits`? – kenmux Nov 21 '17 at 02:50
  • I found an amazing free hex editor which allows directly importing c-style structures and applying them to a file: FlexHex. Then, a window show which values each field is set to. But I have yet to figure out if it supports also offset structures... – jumpjack Jul 25 '18 at 12:50
5

here A short working example.

It converts a wav-file to bmp (a long ago I had such fun).

Code:

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

#define RATE 44100

typedef struct {
    unsigned short type;                 /* Magic identifier            */
    unsigned int size;                       /* File size in bytes          */
    unsigned int reserved;
    unsigned int offset;                     /* Offset to image data, bytes */
} HEADER;
typedef struct {
    unsigned int size;               /* Header size in bytes      */
    int width,height;                /* Width and height of image */
    unsigned short planes;       /* Number of colour planes   */
    unsigned short bits;         /* Bits per pixel            */
    unsigned int compression;        /* Compression type          */
    unsigned int imagesize;          /* Image size in bytes       */
    int xresolution,yresolution;     /* Pixels per meter          */
    unsigned int ncolours;           /* Number of colours         */
    unsigned int importantcolours;   /* Important colours         */
} INFOHEADER;
typedef struct {
    unsigned char r,g,b,junk;
} COLOURINDEX;


int main(int argc, char *argv[]){
    int i,j,rd;
    int gotindex = 0;
    unsigned char grey,r,g,b;
    double ampl;
    short _2byte[2];
    HEADER header;
    INFOHEADER infoheader;
    COLOURINDEX colourindex[256];
    FILE *fptr; 
    SNDFILE* sndfile = NULL;
    SF_INFO sfinfo;
    long rate = RATE;

    void (*bmpread)();
    void _eightbit(){
        if(fread(&grey, sizeof(unsigned char), 1, fptr) != 1){
        fprintf(stderr,"Image read failed\n");
        exit(-1);
        }
        if (gotindex){
            ampl =  colourindex[grey].r * 64. +
                colourindex[grey].g * 128.+
                colourindex[grey].b * 64.;
        } else {
            ampl = grey * 256. - 32768.;
        }
//      printf("%.2f\n", ampl);
    }
    void _twentyfourbit(){
        do{
            if((rd = fread(&b, sizeof(unsigned char), 1, fptr)) != 1) break;
            if((rd = fread(&g, sizeof(unsigned char), 1, fptr)) != 1) break;
            if((rd = fread(&r, sizeof(unsigned char), 1, fptr)) != 1) break;
        }while(0);
        if(rd != 1){    
            fprintf(stderr,"Image read failed\n");
            exit(-1);
        }
        ampl = r * 64. + g * 128. + b * 64. - 32768.;
//      printf("%.2f\n", ampl);
    }
    if (argc < 3){
        printf("Usage: %s <input.bmp> <output.wav> [samplerate]\n", argv[0]);
        printf("For example:\n\t%s pict.bmp sample.wav 44100 2\n", argv[0]);
        exit(0);
    }
    printf("Input file: %s\n", argv[1]);
    printf("Output file: %s\n", argv[2]);
    if(argc > 3) rate = atoi(argv[3]);
    if(rate < 4000) rate = 4000;
    //if(argc > 4) channels = atoi(argv[4]);        
    sfinfo.samplerate = rate;
    sfinfo.channels = 2;
    sfinfo.format = SF_FORMAT_WAV|SF_FORMAT_PCM_16;
    if((fptr = fopen(argv[1],"r")) == NULL) {
        fprintf(stderr,"Unable to open BMP file \"%s\"\n",argv[1]);
        exit(-1);
    }
        /* Read and check BMP header */
    if(fread(&header.type, 2, 1, fptr) != 1){
        fprintf(stderr, "Failed to read BMP header\n");
        exit(-1);
    }
    if(header.type != 'M'*256+'B'){
        fprintf(stderr, "File is not bmp type\n");
        exit(-1);
    }
    do{
        if((rd = fread(&header.size, 4, 1, fptr)) != 1) break;
        printf("File size: %d bytes\n", header.size);
        if((rd = fread(&header.reserved, 4, 1, fptr)) != 1) break;
        if((rd = fread(&header.offset, 4, 1, fptr)) != 1) break;
        printf("Offset to image data is %d bytes\n", header.offset);
    }while(0);
    if(rd =! 1){
        fprintf(stderr, "Error reading file\n");
        exit(-1);
    }
    /* Read and check the information header */
    if (fread(&infoheader, sizeof(INFOHEADER), 1, fptr) != 1) {
        fprintf(stderr,"Failed to read BMP info header\n");
        exit(-1);
    }
    printf("Image size = %d x %d\n", infoheader.width, infoheader.height);
    printf("Number of colour planes is %d\n", infoheader.planes);
    printf("Bits per pixel is %d\n", infoheader.bits);
    printf("Compression type is %d\n", infoheader.compression);
    printf("Number of colours is %d\n", infoheader.ncolours);
    printf("Number of required colours is %d\n", infoheader.importantcolours);
    /* Read the lookup table if there is one */
    for (i=0; i<255; i++){
        colourindex[i].r = rand() % 256;
        colourindex[i].g = rand() % 256;
        colourindex[i].b = rand() % 256;
        colourindex[i].junk = rand() % 256;
    }
    if (infoheader.ncolours > 0) {
        for (i=0; i<infoheader.ncolours; i++){
            do{
            if ((rd = fread(&colourindex[i].b, sizeof(unsigned char),1,fptr)) != 1)
                break;
            if ((rd = fread(&colourindex[i].g, sizeof(unsigned char),1,fptr)) != 1)
                break;
            if ((rd = fread(&colourindex[i].r, sizeof(unsigned char),1,fptr)) != 1)
                break;
            if ((rd = fread(&colourindex[i].junk, sizeof(unsigned char),1,fptr)) != 1)
                break;
            }while(0);
            if(rd != 1){
                fprintf(stderr,"Image read failed\n");
                exit(-1);
            }           
            printf("%3d\t%3d\t%3d\t%3d\n", i,
            colourindex[i].r, colourindex[i].g, colourindex[i].b);
        }
        gotindex = 1;
    }
    if(infoheader.bits < 8){
        printf("Too small image map depth (%d < 8)\n", infoheader.bits);
        exit(-1);
    }
    /* Seek to the start of the image data */
    fseek(fptr, header.offset, SEEK_SET);
    printf("Creating 16bit WAV %liHz.\n", rate);
    sndfile = sf_open(argv[2], SFM_WRITE, &sfinfo);
    if(sndfile == NULL){
        fprintf(stderr, "Cannot open output file!\n"); exit(-1);
    }
    bmpread = _eightbit;
    if(infoheader.bits == 24)
        bmpread = _twentyfourbit;

    /* Read the image */
    for (j=0;j<infoheader.height;j++) {
        _2byte[1] = 32700;
        for (i=0;i<infoheader.width;i++) {
            bmpread();
            _2byte[0] = (short)ampl;
            sf_write_short(sndfile, _2byte, 2);
            _2byte[1] = 0;
        } // i
    } // j
    fclose(fptr);
    sf_close(sndfile);
}
ollo
  • 24,797
  • 14
  • 106
  • 155
Eddy_Em
  • 864
  • 6
  • 14
  • You need to read the binary file portably. Portable read routines are here https://github.com/MalcolmMcLean/ieee754/blob/master/binaryio.c and, though the integer routines are simple, they are maybe not obvious. A portable BMP format reader is here https://github.com/MalcolmMcLean/babyxrc/blob/master/src/bmp.c – Malcolm McLean Oct 19 '16 at 13:01
  • @MalcolmMcLean your code is very slow. Better example is [here](http://stackoverflow.com/a/19276193/1965803). Also your code isn't portable as instead of fixed types like int64_t, uint32_t etc it use int/short etc. Try it on 8/16 or 32-bit architecture! – Eddy_Em Oct 19 '16 at 18:47
  • That code isn't portable. If you have a guaranteed bit width type, it's reasonable to use it. But it's not solving the problem, just hardcoding it in. Mine will work with any size of int above 16 bits, so on any conforming C compiler. – Malcolm McLean Oct 20 '16 at 12:20
-3

So to read a bitmap you need structures as answered by @ollo but there is one easier way too .

  • If you are on windows then you can simply add #include <wingdi.h> in your code and you will get access to all structures without writing them in a separate file.
  • If you need some sample code then I had implemented various filters on bmp files like:
  1. Grayscale
  2. Edge-Detection
  3. Blur
  4. Sepia

In CS50x course but the problem was that the output.bmp generated was completely right on CS50 ide but it didnot worked on my local computer on which there is Windows 10. So I did some changes in it and it worked. The code is portable for Linux and other OS too.The code in which I did changes and will work on your system is here

Lovish Garg
  • 79
  • 2
  • 8
  • is the link to your github necessary ? – phoenixstudio Dec 31 '20 at 16:02
  • There are more than one file in my project and what's wrong if I give link to my github. In fact its better because I have uploaded a detailed README and there are differnet files for different works and if anyone wants to run that BMP program on its system then he/she can simply fork the repository. – Lovish Garg Jan 01 '21 at 07:46