3
\$\begingroup\$

I'm working on simple a 16/24/32-bit bitmap reader for fun, however, the method I am using to go about reading the DIB header seems odd. All this does is read a simple Windows Compatible Bitmap from a file and stores it in memory.

The idea here was that I wanted to auto-populate members of a struct with the corresponding values from the Bitmap File Header and DIB(Info) Header and then apply the image data to a buffer for later converting into an RGB and RGBA (32-bit only) array.

I do this by applying the struct members of the File and DIB header structs to a void* array and later looping over this with for(auto..). In this sense, it's pointless for the File Header because of it's fixed size, however for the DIB Header we do this since the DIB header varies between bitmap images depending on the different properties it has.

If you don't know about bitmaps, you can read up on them on the Wiki or Microsoft pages.

NOTE: Of course I could use a library, however that defeats the purpose of "fun" and/or "educational." This also isn't really for practical purposes, just for learning.

Bitmap Reader Header:

#ifndef LODEBMP
#define LODEBMP
    #include <stdio.h>

    // <-- Bitmap File Data Types -->
    typedef bool BOOL;
    typedef const char CBYTE;
    typedef unsigned char BYTE;
    typedef unsigned short WORD;
    typedef unsigned long DWORD;
    typedef signed long LONG;

    // Only recognizing two compression types:
    enum Compression {
        Bi_Rgb = 0, Bi_BitFields = 3
    };

    // Read errors for reading bitmaps from a file.
    enum ReadErrors {
        NoError = 0x0, BadFileType = 0x1 , ReadFailed = 0x2, BadBitMasks = 0x4 , WrongBitDepth = 0x8 ,
        WrongCompression = 0x10 , BadBMPHeader = 0x20 , BadDIBHeader = 0x40 , BadImageData = 0x80 , NullImage = 0x100
    };

    // Bitmap header containing info on the physical file.
    struct BITMAPFILEHEADER {
        void* FileHeader[ 0xE ];
        WORD FileType = 0;
        DWORD FileSize = 0;
        WORD ReserveX = 0;
        WORD ReserveY = 0;
        DWORD DataOffset = 0;

        /*
            This allows us to auto-populate the struct 
            using for(auto..){} over the FileHeader array.
        */
        BITMAPFILEHEADER() {
            FileHeader[ 0x0 ] = (&FileType);
            FileHeader[ 0x1 ] = (&FileSize);
            FileHeader[ 0x2 ] = (&ReserveX);
            FileHeader[ 0x3 ] = (&ReserveY);
            FileHeader[ 0x4 ] = (&DataOffset);
        };
    };

    // Bitmap header containing info on the bitmap image.
    struct BITMAPINFOHEADER {

        void* InfoHeader[ 0xF ];
        DWORD InfoSize = 0;
        LONG ImageWidth = 0;
        LONG ImageHeight = 0;
        WORD ColorPlanes = 0;
        WORD BitDepth = 0;
        DWORD Compression = 0;
        DWORD ImageSizeBytes = 0;
        LONG PxPerMeterX = 0;
        LONG PxPerMeterY = 0;
        DWORD ColorsInPalette = 0;
        DWORD PriorityColorCount = 0;
        DWORD RedBitMask = 0;
        DWORD BlueBitMask = 0;
        DWORD GreenBitMask = 0;
        DWORD AlphaBitMask = 0;

        /*
            This allows us to auto-populate the struct 
            using for(auto..){} over the InfoHeader array.
        */
        BITMAPINFOHEADER() {
            InfoHeader[ 0x0 ] = (&InfoSize);
            InfoHeader[ 0x1 ] = (&ImageWidth);
            InfoHeader[ 0x2 ] = (&ImageHeight);
            InfoHeader[ 0x3 ] = (&ColorPlanes);
            InfoHeader[ 0x4 ] = (&BitDepth);
            InfoHeader[ 0x5 ] = (&Compression);
            InfoHeader[ 0x6 ] = (&ImageSizeBytes);
            InfoHeader[ 0x7 ] = (&PxPerMeterX);
            InfoHeader[ 0x8 ] = (&PxPerMeterY);
            InfoHeader[ 0x9 ] = (&ColorsInPalette);
            InfoHeader[ 0xA ] = (&PriorityColorCount);
            InfoHeader[ 0xB ] = (&RedBitMask);
            InfoHeader[ 0xC ] = (&BlueBitMask);
            InfoHeader[ 0xD ] = (&GreenBitMask);
            InfoHeader[ 0xE ] = (&AlphaBitMask);
        };
    };

    // Currently unused...
    struct RGBAQUAD {
        BYTE Red;
        BYTE Green;
        BYTE Blue;
        BYTE Alpha;
    };

    // Complete Bitmap Structure.
    struct BITMAPINFO {
        BITMAPFILEHEADER File;
        BITMAPINFOHEADER Info;
        BYTE* Palette;
        BYTE* Image;
        WORD ERROR;

        BITMAPINFO();
    };

    void lodebmp_import( CBYTE*, BITMAPINFO* );
    void lodebmp_export( CBYTE*, BITMAPINFO* );
    void lodebmp_decode( BITMAPINFO* );
    void lodebmp_encode( BITMAPINFO* );
#endif

Bitmap Reader Source:

#include "LodeBMP.h"

/*
    These two arrays contain the size and order of the
    data in the File and DIB(Info) header: WORD(2), DWORD(3).
*/
DWORD BITMAPFILEDATA[ 0x5 ] = { 2, 4, 2, 2, 4 };
DWORD BITMAPINFODATA[ 0xF ] = {
    4, 4, 4, 2, 2, 4, 4, 4,
    4, 4, 4, 4, 4, 4, 4,
};

// Complete Bitmap Structure Constructor.
BITMAPINFO :: BITMAPINFO() {
    Image = nullptr;
    Palette = nullptr;
    ERROR = NullImage;
};

/*
    Returns the BYTE sum of the types(WORD/DWORD) of
    the DIB Header properties from the first to the last(i).
    Used for dynamically populating DIB header info.
*/
DWORD BITMAPINFOTYPESUM( DWORD* BITMAPINFODATA , int i ) {
    DWORD SUM = 0;
    for( int j = 0; j < i; j++ ) { SUM += BITMAPINFODATA[ j ]; };
    return SUM;
};

/*
    Loads a 16/24/32-bit Windows-Compatible bitmap from a file.
*/
void bitmap_import( CBYTE* fname, BITMAPINFO* bitmap ) {
    FILE* BitmapFile;
    fopen_s( &BitmapFile, fname, "rb" );

    if ( BitmapFile != nullptr ) {
        DWORD InfoSize = 0, i = 0, j = 0;
        WORD WDATA = 0, *WSTORAGE = nullptr;
        DWORD DWDATA = 0, *DWSTORAGE = nullptr;

        /*
            Loop over the File Header Elements and populate them.
            Of course we could have manually populated them since
            the File Header doesn't change dynamically... might change.
        */
        for( auto& el : bitmap -> File.FileHeader ) {
            switch( BITMAPFILEDATA[ i ] ) {
                case sizeof( WORD ):
                    fread( &WDATA, BITMAPFILEDATA[ i ], 1, BitmapFile );
                    WSTORAGE = static_cast<WORD*>(el);
                    *WSTORAGE = WDATA;
                break;
                case sizeof( DWORD ):
                    fread( &DWDATA, BITMAPFILEDATA[ i ], 1, BitmapFile );
                    DWSTORAGE = static_cast<DWORD*>(el);
                    *DWSTORAGE = DWDATA;
                break;
                default: break;
            };

            i++;
        };

        /*
            Get the DIB Header Size, then back up the file read position
            to the beginning of the DIB header to read the full DIB header.
        */
        fread( &InfoSize, sizeof( DWORD ), 1, BitmapFile );
        fseek( BitmapFile, -signed( sizeof( DWORD ) ), SEEK_CUR );

        /*
            Loop over the DIB Header Elements and populate them
            until we've reached the end of the DIB header. This is
            necessary to dynamically assign DIB header elements without
           reading and assigning unnecessary elements.
        */
        for( auto& el : bitmap -> Info.InfoHeader ) {
            if ( BITMAPINFOTYPESUM( BITMAPINFODATA , j ) < InfoSize ) {
                switch( BITMAPINFODATA[ j ] ) {
                    case sizeof( WORD ):
                        fread( &WDATA, BITMAPINFODATA[ j ], 1, BitmapFile );
                        WSTORAGE = static_cast<WORD*>(el);
                        *WSTORAGE = WDATA;
                    break;
                    case sizeof( DWORD ):
                        fread( &DWDATA, BITMAPINFODATA[ j ], 1, BitmapFile );
                        DWSTORAGE = static_cast<DWORD*>(el);
                        *DWSTORAGE = DWDATA;
                    break;
                    default: break;
                };
            };

            j++;
        };

        // Read the Bitmap Image contents into a buffer.
        bitmap -> Image = new BYTE[ bitmap -> Info.ImageSizeBytes ];
        fread( bitmap -> Image, bitmap -> Info.ImageSizeBytes, 1, BitmapFile );
        fclose( BitmapFile );
        bitmap -> ERROR &= ~NullImage;
    } else {
        bitmap -> ERROR |= ReadFailed;
    };
};
\$\endgroup\$
2
\$\begingroup\$

It looks like you're trying to use a data-driven method of reading in the file. That's not necessarily a bad idea, but it's a little odd because you have a bunch of structs that rely on the data being in a certain format that you already know ahead of time. It also means that you have a bunch of pieces of data that need to remain in synch, and those pieces of data are declared in 2 different files.

For example, in the header you declare BITMAPFILEHEADER to have an array of pointers to 14 elements. (Shouldn't it be 5 elements that take up 14 bytes?) In its constructor you hook the first 5 of those pointers up to the other members of the struct. You then declare an array of element sizes in the source file (BITMAPFILEDATA). Your loop for reading an filling in the array is now very general, but any time you want to update it (say to handle more formats, or newer versions of the format), you'll now need to make sure you have enough elements in the pointer array, the right elements as member variables, that you've hooked up all the pointers to all the member variables, and that the array of sizes has enough elements and in the correct order. This sounds like a nightmare to maintain.

Meanwhile, since that particular header is fixed, you could just read the 14 bytes directly from the file into a structure in memory in a single function call and be on your way.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.