skip to Main Content

I could need some help to figure out how to feed the proc below. I need to write a monochrome BMP file. The code below (its from: How to Save monochrome Image as bmp in windows C++ ?) looks like to be able to do this. I’m now stuck on how to convert a std::bitset or preferably boost::dynamic_bitset into this byte* format. All of my attempts so far failed, I wasn’t able to write something like an 8×8 checker pattern into the BMP. The proc creates the BMP and it is readable by Photoshop, but the content is a mess. So any suggestions how to solve this are appreciated!

Save1BppImage(byte* ImageData, const char* filename, long w, long h){

    int bitmap_dx = w; // Width of image
    int bitmap_dy = h; // Height of Image

    // create file
    std::ofstream file(filename, std::ios::binary | std::ios::trunc);
    if(!file) return;

    // save bitmap file headers
    BITMAPFILEHEADER fileHeader;
    BITMAPINFOHEADER * infoHeader;
    infoHeader = (BITMAPINFOHEADER*) malloc(sizeof(BITMAPINFOHEADER) );
    RGBQUAD bl = {0,0,0,0};  //black color
    RGBQUAD wh = {0xff,0xff,0xff,0xff}; // white color


    fileHeader.bfType      = 0x4d42;
    fileHeader.bfSize      = 0;
    fileHeader.bfReserved1 = 0;
    fileHeader.bfReserved2 = 0;
    fileHeader.bfOffBits   = sizeof(BITMAPFILEHEADER) + (sizeof(BITMAPINFOHEADER));

    infoHeader->biSize          = (sizeof(BITMAPINFOHEADER) );
    infoHeader->biWidth         = bitmap_dx;    
    infoHeader->biHeight        = bitmap_dy;
    infoHeader->biPlanes        = 1;
    infoHeader->biBitCount      = 1;
    infoHeader->biCompression   = BI_RGB; //no compression needed
    infoHeader->biSizeImage     = 0;
    infoHeader->biXPelsPerMeter = 0;
    infoHeader->biYPelsPerMeter = 0;
    infoHeader->biClrUsed       = 2;
    infoHeader->biClrImportant  = 2;

    file.write((char*)&fileHeader, sizeof(fileHeader)); //write bitmapfileheader
    file.write((char*)infoHeader, (sizeof(BITMAPINFOHEADER) )); //write bitmapinfoheader
    file.write((char*)&bl,sizeof(bl)); //write RGBQUAD for black
    file.write((char*)&wh,sizeof(wh)); //write RGBQUAD for white

    int bytes = (w/8) * h ; //for example for 32X64 image = (32/8)bytes X 64 = 256;

    file.write((const char*)ImageData, bytes);

    file.close();
}

-edit-

an naive approach of mine was something like this

    byte test[64];
for(unsigned int i=0; i<64; ++i)
    if(i % 2)
        test[i] = 0;
    else
        test[i] = 1;

Save1BppImage(test, "C:/bitmap.bmp", 8, 8);

3

Answers


  1. Chosen as BEST ANSWER

    Just for the archive, below the working version. It takes a boost bitset as input pixel storage.

    void bitsetToBmp(boost::dynamic_bitset<unsigned char> bitset, const char* filename, int width, int height){
    //write the bitset to file as 1-bit deep bmp
    //bit order 0...n equals image pixels  top left...bottom right, row by row
    //the bitset must be at least the size of width*height, this is not checked
    
    std::ofstream file(filename, std::ios::binary | std::ios::trunc);
    if(!file) return;
    
    // save bitmap file headers
    BITMAPFILEHEADER fileHeader;
    BITMAPINFOHEADER * infoHeader;
    infoHeader = (BITMAPINFOHEADER*) malloc(sizeof(BITMAPINFOHEADER) );
    RGBQUAD bl = {0,0,0,0};  //black color
    RGBQUAD wh = {0xff,0xff,0xff,0xff}; // white color
    
    
    fileHeader.bfType      = 0x4d42;
    fileHeader.bfSize      = 0;
    fileHeader.bfReserved1 = 0;
    fileHeader.bfReserved2 = 0;
    fileHeader.bfOffBits   = sizeof(BITMAPFILEHEADER) + (sizeof(BITMAPINFOHEADER)) + 2*sizeof(RGBQUAD); 
    
    infoHeader->biSize          = (sizeof(BITMAPINFOHEADER) );
    infoHeader->biWidth         = width;    
    infoHeader->biHeight        = height;
    infoHeader->biPlanes        = 1;
    infoHeader->biBitCount      = 1;
    infoHeader->biCompression   = BI_RGB; //no compression needed
    infoHeader->biSizeImage     = 0;
    infoHeader->biXPelsPerMeter = 0;
    infoHeader->biYPelsPerMeter = 0;
    infoHeader->biClrUsed       = 2;
    infoHeader->biClrImportant  = 2;
    
    file.write((char*)&fileHeader, sizeof(fileHeader)); //write bitmapfileheader
    file.write((char*)infoHeader, (sizeof(BITMAPINFOHEADER) )); //write bitmapinfoheader
    file.write((char*)&bl,sizeof(bl)); //write RGBQUAD for black
    file.write((char*)&wh,sizeof(wh)); //write RGBQUAD for white
    
    // convert the bits into bytes and write the file
    int offset, numBytes = ((width + 31) / 32) * 4;
    byte* bytes = (byte*) malloc(numBytes * sizeof(byte));
    
    for(int y=height - 1; y>=0; --y){
        offset = y * width;
        memset(bytes, 0, (numBytes * sizeof(byte)));
        for(int x=0; x<width; ++x)
            if(bitset[offset++]){
                bytes[x / 8] |= 1 << (7 - x % 8);
        };
        file.write((const char *)bytes, numBytes);
    };
    free(bytes);
    file.close();
    

    }

    I wonder if theres a simpler/faster way to put the bits into a file? The whole bitset could instead be overhanded as array of rows to skip the subset extraction.


  2. I have something very similiar…

    • This approach DOES NOT treat the padding of the BMP format. So You can only make bitmaps with width multiple of 4.

    • This is NOT a monochromatic bitmap. It’s a RGB format, but you can tune it easily.

    • This is NOT an exactly answer to you, but for sure may be useful for you.

    Enjoy it.

    void createBitmap( byte * imageData, const char * filename, int width, int height )
    {
        BITMAPFILEHEADER bitmapFileHeader;
        memset( &bitmapFileHeader, 0, sizeof( bitmapFileHeader ) );
        bitmapFileHeader.bfType = ( 'B' | 'M' << 8 );
        bitmapFileHeader.bfOffBits = sizeof( BITMAPFILEHEADER ) + sizeof( BITMAPINFOHEADER );
        bitmapFileHeader.bfSize = bitmapFileHeader.bfOffBits + width * height * 3;
    
        BITMAPINFOHEADER bitmapInfoHeader;
        memset( &bitmapInfoHeader, 0, sizeof( bitmapInfoHeader ) );
        bitmapInfoHeader.biSize = sizeof( BITMAPINFOHEADER );
        bitmapInfoHeader.biWidth = width;
        bitmapInfoHeader.biHeight = height;
        bitmapInfoHeader.biPlanes = 1;
        bitmapInfoHeader.biBitCount = 24;
    
        std::ofstream file( filename, std::fstream::binary );
    
        file.write( reinterpret_cast< char * >( &bitmapFileHeader ), sizeof( bitmapFileHeader ) );
        file.write( reinterpret_cast< char * >( &bitmapInfoHeader ), sizeof( bitmapInfoHeader ) );
    
        // the pixels!
        file.write( imageData, width * height * 3 );
    
        file.close();
    }
    
    int main( int argc, const char * argv[] )
    {
        int width = 12; // multiple of 4
        int height = 12;
    
        byte imageData[ width * height * 3 ];
    
        // fill imageData the way you want, this is just a sample
        // on how to set the pixel at any specific (X,Y) position
    
        for ( int y = 0; y < height; ++y )
        {
            for ( int x = 0; x < width; ++x )
            {
                int pos = 3 * ( y * width + x );
    
                byte pixelColor = ( x == 2 && y == 2 ) ? 0x00 : 0xff;
    
                imageData[ pos ] = pixelColor;
                imageData[ pos + 1 ] = pixelColor;
                imageData[ pos + 2 ] = pixelColor;
            }
        }
    
        createBitmap( imageData, "bitmap.bmp", width, height );
    
        return 0;
    }
    

    In this sample we want a white bitmap with a single black pixel at position X = 2, Y = 2.

    The BMP format constiders that Y grows up from bottom to top.

    If you have a bitmap width a pixel per bit (the real monochromatic bitmap) you can test the bits and fill the imageData. To test a bit in a byte do like myByte >> position & 1 where position is the bit you wanna test from 0 to 7.

    Login or Signup to reply.
  3. The code you have is very close. Here are a few thoughts about where it might be off.

    The bfOffBits value must include the size of the palette.

    fileHeader.bfOffBits   = sizeof(BITMAPFILEHEADER) + (sizeof(BITMAPINFOHEADER)) + 2*sizeof(RGBQUAD);
    

    Some software may interpret 0 as white and 1 as black, regardless of what the palette says. Even though the file format allows you to go either way, you’re better off specifying the palette in that order and inverting your bits if necessary.

    Each row of a bitmap will start on a 4-byte boundary. If your bitmap width isn’t a multiple of 32, you’re going to need some padding between each row.

    BMP files are ordered from the bottom row to the top row, which is backwards from the way most people organize their arrays.

    The last two recommendations are combined to look something like this:

    int bytes_in = (w + 7) / 8;
    int bytes_out = ((w + 31) / 32) * 4;
    const char * zeros[4] = {0, 0, 0, 0};
    for (int y = h - 1;  y >= 0;  --y)
    {
        file.write(((const char *)ImageData) + (y * bytes_in), bytes_in);
        if (bytes_out != bytes_in)
            file.write(zeros, bytes_out - bytes_in);
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search