BMP-kuvan kirjoittaminen puhtaana c / c ++ -sovelluksena ilman muita kirjastoja

original title: "Writing BMP image in pure c/c++ without other libraries"


Translate

In my algorithm, I need to create an information output. I need to write a boolean matrix into a bmp file. It must be a monocromic image, where pixels are white if the matrix on such element is true. Main problem is the bmp header and how to write this.



Algoritmissani minun on luotava tietolähtö. Minun on kirjoitettava looginen matriisi bmp-tiedostoon. Sen on oltava monokromaattinen kuva, jossa pikselit ovat valkoisia, jos matriisi on ...

Tämä on yhteenveto käännöksen jälkeen. Jos haluat tarkastella koko käännöstä, napsauta käännä-kuvaketta


Kaikki vastaukset
  • Translate

    Without the use of any other library you can look at the BMP file format. I've implemented it in the past and it can be done without too much work.

    Bitmap-File Structures

    Each bitmap file contains a bitmap-file header, a bitmap-information header, a color table, and an array of bytes that defines the bitmap bits. The file has the following form:

    BITMAPFILEHEADER bmfh;
    BITMAPINFOHEADER bmih;
    RGBQUAD aColors[];
    BYTE aBitmapBits[];

    ... see the file format for more details


  • Translate

    See if this works for you... In this code, I had 3 2-dimensional arrays, called red,green and blue. Each one was of size [width][height], and each element corresponded to a pixel - I hope this makes sense!

    FILE *f;
    unsigned char *img = NULL;
    int filesize = 54 + 3*w*h;  //w is your image width, h is image height, both int
    
    img = (unsigned char *)malloc(3*w*h);
    memset(img,0,3*w*h);
    
    for(int i=0; i<w; i++)
    {
        for(int j=0; j<h; j++)
        {
            x=i; y=(h-1)-j;
            r = red[i][j]*255;
            g = green[i][j]*255;
            b = blue[i][j]*255;
            if (r > 255) r=255;
            if (g > 255) g=255;
            if (b > 255) b=255;
            img[(x+y*w)*3+2] = (unsigned char)(r);
            img[(x+y*w)*3+1] = (unsigned char)(g);
            img[(x+y*w)*3+0] = (unsigned char)(b);
        }
    }
    
    unsigned char bmpfileheader[14] = {'B','M', 0,0,0,0, 0,0, 0,0, 54,0,0,0};
    unsigned char bmpinfoheader[40] = {40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0, 24,0};
    unsigned char bmppad[3] = {0,0,0};
    
    bmpfileheader[ 2] = (unsigned char)(filesize    );
    bmpfileheader[ 3] = (unsigned char)(filesize>> 8);
    bmpfileheader[ 4] = (unsigned char)(filesize>>16);
    bmpfileheader[ 5] = (unsigned char)(filesize>>24);
    
    bmpinfoheader[ 4] = (unsigned char)(       w    );
    bmpinfoheader[ 5] = (unsigned char)(       w>> 8);
    bmpinfoheader[ 6] = (unsigned char)(       w>>16);
    bmpinfoheader[ 7] = (unsigned char)(       w>>24);
    bmpinfoheader[ 8] = (unsigned char)(       h    );
    bmpinfoheader[ 9] = (unsigned char)(       h>> 8);
    bmpinfoheader[10] = (unsigned char)(       h>>16);
    bmpinfoheader[11] = (unsigned char)(       h>>24);
    
    f = fopen("img.bmp","wb");
    fwrite(bmpfileheader,1,14,f);
    fwrite(bmpinfoheader,1,40,f);
    for(int i=0; i<h; i++)
    {
        fwrite(img+(w*(h-i-1)*3),3,w,f);
        fwrite(bmppad,1,(4-(w*3)%4)%4,f);
    }
    
    free(img);
    fclose(f);
    

  • Translate

    Clean C Code for Bitmap (BMP) Image Generation

    Generated Image:

    bitmap image


    The code does not use any library other than stdio.h. So, the code can be easily incorporated in other languages of C-Family, like- C++, C#, Java.


    #include <stdio.h>
    
    const int bytesPerPixel = 3; /// red, green, blue
    const int fileHeaderSize = 14;
    const int infoHeaderSize = 40;
    
    void generateBitmapImage(unsigned char *image, int height, int width, char* imageFileName);
    unsigned char* createBitmapFileHeader(int height, int width, int paddingSize);
    unsigned char* createBitmapInfoHeader(int height, int width);
    
    
    int main(){
        int height = 341;
        int width = 753;
        unsigned char image[height][width][bytesPerPixel];
        char* imageFileName = "bitmapImage.bmp";
    
        int i, j;
        for(i=0; i<height; i++){
            for(j=0; j<width; j++){
                image[i][j][2] = (unsigned char)((double)i/height*255); ///red
                image[i][j][1] = (unsigned char)((double)j/width*255); ///green
                image[i][j][0] = (unsigned char)(((double)i+j)/(height+width)*255); ///blue
            }
        }
    
        generateBitmapImage((unsigned char *)image, height, width, imageFileName);
        printf("Image generated!!");
    }
    
    
    void generateBitmapImage(unsigned char *image, int height, int width, char* imageFileName){
    
        unsigned char padding[3] = {0, 0, 0};
        int paddingSize = (4 - (width*bytesPerPixel) % 4) % 4;
    
        unsigned char* fileHeader = createBitmapFileHeader(height, width, paddingSize);
        unsigned char* infoHeader = createBitmapInfoHeader(height, width);
    
        FILE* imageFile = fopen(imageFileName, "wb");
    
        fwrite(fileHeader, 1, fileHeaderSize, imageFile);
        fwrite(infoHeader, 1, infoHeaderSize, imageFile);
    
        int i;
        for(i=0; i<height; i++){
            fwrite(image+(i*width*bytesPerPixel), bytesPerPixel, width, imageFile);
            fwrite(padding, 1, paddingSize, imageFile);
        }
    
        fclose(imageFile);
    }
    
    unsigned char* createBitmapFileHeader(int height, int width, int paddingSize){
        int fileSize = fileHeaderSize + infoHeaderSize + (bytesPerPixel*width+paddingSize) * height;
    
        static unsigned char fileHeader[] = {
            0,0, /// signature
            0,0,0,0, /// image file size in bytes
            0,0,0,0, /// reserved
            0,0,0,0, /// start of pixel array
        };
    
        fileHeader[ 0] = (unsigned char)('B');
        fileHeader[ 1] = (unsigned char)('M');
        fileHeader[ 2] = (unsigned char)(fileSize    );
        fileHeader[ 3] = (unsigned char)(fileSize>> 8);
        fileHeader[ 4] = (unsigned char)(fileSize>>16);
        fileHeader[ 5] = (unsigned char)(fileSize>>24);
        fileHeader[10] = (unsigned char)(fileHeaderSize + infoHeaderSize);
    
        return fileHeader;
    }
    
    unsigned char* createBitmapInfoHeader(int height, int width){
        static unsigned char infoHeader[] = {
            0,0,0,0, /// header size
            0,0,0,0, /// image width
            0,0,0,0, /// image height
            0,0, /// number of color planes
            0,0, /// bits per pixel
            0,0,0,0, /// compression
            0,0,0,0, /// image size
            0,0,0,0, /// horizontal resolution
            0,0,0,0, /// vertical resolution
            0,0,0,0, /// colors in color table
            0,0,0,0, /// important color count
        };
    
        infoHeader[ 0] = (unsigned char)(infoHeaderSize);
        infoHeader[ 4] = (unsigned char)(width    );
        infoHeader[ 5] = (unsigned char)(width>> 8);
        infoHeader[ 6] = (unsigned char)(width>>16);
        infoHeader[ 7] = (unsigned char)(width>>24);
        infoHeader[ 8] = (unsigned char)(height    );
        infoHeader[ 9] = (unsigned char)(height>> 8);
        infoHeader[10] = (unsigned char)(height>>16);
        infoHeader[11] = (unsigned char)(height>>24);
        infoHeader[12] = (unsigned char)(1);
        infoHeader[14] = (unsigned char)(bytesPerPixel*8);
    
        return infoHeader;
    }
    

  • Translate

    this is the best low level example i know, written by Evercat. copied from https://en.wikipedia.org/wiki/User:Evercat/Buddhabrot.c

    void drawbmp (char * filename) {
    
    unsigned int headers[13];
    FILE * outfile;
    int extrabytes;
    int paddedsize;
    int x; int y; int n;
    int red, green, blue;
    
    extrabytes = 4 - ((WIDTH * 3) % 4);                 // How many bytes of padding to add to each
                                                        // horizontal line - the size of which must
                                                        // be a multiple of 4 bytes.
    if (extrabytes == 4)
       extrabytes = 0;
    
    paddedsize = ((WIDTH * 3) + extrabytes) * HEIGHT;
    
    // Headers...
    // Note that the "BM" identifier in bytes 0 and 1 is NOT included in these "headers".
    
    headers[0]  = paddedsize + 54;      // bfSize (whole file size)
    headers[1]  = 0;                    // bfReserved (both)
    headers[2]  = 54;                   // bfOffbits
    headers[3]  = 40;                   // biSize
    headers[4]  = WIDTH;  // biWidth
    headers[5]  = HEIGHT; // biHeight
    
    // Would have biPlanes and biBitCount in position 6, but they're shorts.
    // It's easier to write them out separately (see below) than pretend
    // they're a single int, especially with endian issues...
    
    headers[7]  = 0;                    // biCompression
    headers[8]  = paddedsize;           // biSizeImage
    headers[9]  = 0;                    // biXPelsPerMeter
    headers[10] = 0;                    // biYPelsPerMeter
    headers[11] = 0;                    // biClrUsed
    headers[12] = 0;                    // biClrImportant
    
    outfile = fopen(filename, "wb");
    
    //
    // Headers begin...
    // When printing ints and shorts, we write out 1 character at a time to avoid endian issues.
    //
    
    fprintf(outfile, "BM");
    
    for (n = 0; n <= 5; n++)
    {
       fprintf(outfile, "%c", headers[n] & 0x000000FF);
       fprintf(outfile, "%c", (headers[n] & 0x0000FF00) >> 8);
       fprintf(outfile, "%c", (headers[n] & 0x00FF0000) >> 16);
       fprintf(outfile, "%c", (headers[n] & (unsigned int) 0xFF000000) >> 24);
    }
    
    // These next 4 characters are for the biPlanes and biBitCount fields.
    
    fprintf(outfile, "%c", 1);
    fprintf(outfile, "%c", 0);
    fprintf(outfile, "%c", 24);
    fprintf(outfile, "%c", 0);
    
    for (n = 7; n <= 12; n++)
    {
       fprintf(outfile, "%c", headers[n] & 0x000000FF);
       fprintf(outfile, "%c", (headers[n] & 0x0000FF00) >> 8);
       fprintf(outfile, "%c", (headers[n] & 0x00FF0000) >> 16);
       fprintf(outfile, "%c", (headers[n] & (unsigned int) 0xFF000000) >> 24);
    }
    
    //
    // Headers done, now write the data...
    //
    
    for (y = HEIGHT - 1; y >= 0; y--)     // BMP image format is written from bottom to top...
    {
       for (x = 0; x <= WIDTH - 1; x++)
       {
    
          red = reduce(redcount[x][y] + COLOUR_OFFSET) * red_multiplier;
          green = reduce(greencount[x][y] + COLOUR_OFFSET) * green_multiplier;
          blue = reduce(bluecount[x][y] + COLOUR_OFFSET) * blue_multiplier;
    
          if (red > 255) red = 255; if (red < 0) red = 0;
          if (green > 255) green = 255; if (green < 0) green = 0;
          if (blue > 255) blue = 255; if (blue < 0) blue = 0;
    
          // Also, it's written in (b,g,r) format...
    
          fprintf(outfile, "%c", blue);
          fprintf(outfile, "%c", green);
          fprintf(outfile, "%c", red);
       }
       if (extrabytes)      // See above - BMP lines must be of lengths divisible by 4.
       {
          for (n = 1; n <= extrabytes; n++)
          {
             fprintf(outfile, "%c", 0);
          }
       }
    }
    
    fclose(outfile);
    return;
    }
    
    
    drawbmp(filename);
    

  • Translate

    Note that the lines are saved from down to up and not the other way around.

    Additionally, the scanlines must have a byte-length of multiples of four, you should insert fill bytes at the end of the lines to ensure this.


  • Translate

    Here is a C++ variant of the code that works for me. Note I had to change the size computation to account for the line padding.

    // mimeType = "image/bmp";
    
    unsigned char file[14] = {
        'B','M', // magic
        0,0,0,0, // size in bytes
        0,0, // app data
        0,0, // app data
        40+14,0,0,0 // start of data offset
    };
    unsigned char info[40] = {
        40,0,0,0, // info hd size
        0,0,0,0, // width
        0,0,0,0, // heigth
        1,0, // number color planes
        24,0, // bits per pixel
        0,0,0,0, // compression is none
        0,0,0,0, // image bits size
        0x13,0x0B,0,0, // horz resoluition in pixel / m
        0x13,0x0B,0,0, // vert resolutions (0x03C3 = 96 dpi, 0x0B13 = 72 dpi)
        0,0,0,0, // #colors in pallete
        0,0,0,0, // #important colors
        };
    
    int w=waterfallWidth;
    int h=waterfallHeight;
    
    int padSize  = (4-(w*3)%4)%4;
    int sizeData = w*h*3 + h*padSize;
    int sizeAll  = sizeData + sizeof(file) + sizeof(info);
    
    file[ 2] = (unsigned char)( sizeAll    );
    file[ 3] = (unsigned char)( sizeAll>> 8);
    file[ 4] = (unsigned char)( sizeAll>>16);
    file[ 5] = (unsigned char)( sizeAll>>24);
    
    info[ 4] = (unsigned char)( w   );
    info[ 5] = (unsigned char)( w>> 8);
    info[ 6] = (unsigned char)( w>>16);
    info[ 7] = (unsigned char)( w>>24);
    
    info[ 8] = (unsigned char)( h    );
    info[ 9] = (unsigned char)( h>> 8);
    info[10] = (unsigned char)( h>>16);
    info[11] = (unsigned char)( h>>24);
    
    info[20] = (unsigned char)( sizeData    );
    info[21] = (unsigned char)( sizeData>> 8);
    info[22] = (unsigned char)( sizeData>>16);
    info[23] = (unsigned char)( sizeData>>24);
    
    stream.write( (char*)file, sizeof(file) );
    stream.write( (char*)info, sizeof(info) );
    
    unsigned char pad[3] = {0,0,0};
    
    for ( int y=0; y<h; y++ )
    {
        for ( int x=0; x<w; x++ )
        {
            long red = lround( 255.0 * waterfall[x][y] );
            if ( red < 0 ) red=0;
            if ( red > 255 ) red=255;
            long green = red;
            long blue = red;
    
            unsigned char pixel[3];
            pixel[0] = blue;
            pixel[1] = green;
            pixel[2] = red;
    
            stream.write( (char*)pixel, 3 );
        }
        stream.write( (char*)pad, padSize );
    }
    

  • Translate

    I edited ralf's htp code so that it would compile (on gcc, running ubuntu 16.04 lts). It was just a matter of initializing the variables.

        int w = 100; /* Put here what ever width you want */
        int h = 100; /* Put here what ever height you want */
        int red[w][h]; 
        int green[w][h];
        int blue[w][h];
    
    
        FILE *f;
        unsigned char *img = NULL;
        int filesize = 54 + 3*w*h;  //w is your image width, h is image height, both int
        if( img )
                free( img );
        img = (unsigned char *)malloc(3*w*h);
        memset(img,0,sizeof(img));
        int x;
        int y;
        int r;
        int g;
        int b;
    
        for(int i=0; i<w; i++)
        {
                for(int j=0; j<h; j++)
                {
                        x=i; y=(h-1)-j;
                        r = red[i][j]*255;
                        g = green[i][j]*255;
                        b = blue[i][j]*255;
                        if (r > 255) r=255;
                        if (g > 255) g=255;
                        if (b > 255) b=255;
                        img[(x+y*w)*3+2] = (unsigned char)(r);
                        img[(x+y*w)*3+1] = (unsigned char)(g);
                        img[(x+y*w)*3+0] = (unsigned char)(b);
                }
        }
    
        unsigned char bmpfileheader[14] = {'B','M', 0,0,0,0, 0,0, 0,0, 54,0,0,0};
        unsigned char bmpinfoheader[40] = {40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0, 24,0};
        unsigned char bmppad[3] = {0,0,0};
    
        bmpfileheader[ 2] = (unsigned char)(filesize    );
        bmpfileheader[ 3] = (unsigned char)(filesize>> 8);
        bmpfileheader[ 4] = (unsigned char)(filesize>>16);
        bmpfileheader[ 5] = (unsigned char)(filesize>>24);
    
        bmpinfoheader[ 4] = (unsigned char)(       w    );
        bmpinfoheader[ 5] = (unsigned char)(       w>> 8);
        bmpinfoheader[ 6] = (unsigned char)(       w>>16);
        bmpinfoheader[ 7] = (unsigned char)(       w>>24);
        bmpinfoheader[ 8] = (unsigned char)(       h    );
        bmpinfoheader[ 9] = (unsigned char)(       h>> 8);
        bmpinfoheader[10] = (unsigned char)(       h>>16);
        bmpinfoheader[11] = (unsigned char)(       h>>24);
    
        f = fopen("img.bmp","wb");
        fwrite(bmpfileheader,1,14,f);
        fwrite(bmpinfoheader,1,40,f);
        for(int i=0; i<h; i++)
        {
                fwrite(img+(w*(h-i-1)*3),3,w,f);
                fwrite(bmppad,1,(4-(w*3)%4)%4,f);
        }
        fclose(f);
    

  • Translate

    If you get strange colors switches in the middle of your image using the above C++ function. Be sure to open the outstream in binary mode: imgFile.open(filename, std::ios_base::out | std::ios_base::binary);
    Otherwise windows inserts unwanted characters in the middle of your file! (been banging my head on this issue for hours)

    See related question here: Why does ofstream insert a 0x0D byte before 0x0A?


  • Translate

    I just wanted to share an improved version of Minhas Kamal's code because although it worked well enough for most applications, I had a few issues with it still. Two highly important things to remember:

    1. The code (at the time of writing) calls free() on two static arrays. This will cause your program to crash. So I commented out those lines.
    2. NEVER assume that your pixel data's pitch is always (Width*BytesPerPixel). It's best to let the user specify the pitch value. Example: when manipulating resources in Direct3D, the RowPitch is never guaranteed to be an even multiple of the byte depth being used. This can cause errors in your generated bitmaps (especially at odd resolutions such as 1366x768).

    Below, you can see my revisions to his code:

    const int bytesPerPixel = 4; /// red, green, blue
    const int fileHeaderSize = 14;
    const int infoHeaderSize = 40;
    
    void generateBitmapImage(unsigned char *image, int height, int width, int pitch, const char* imageFileName);
    unsigned char* createBitmapFileHeader(int height, int width, int pitch, int paddingSize);
    unsigned char* createBitmapInfoHeader(int height, int width);
    
    
    
    void generateBitmapImage(unsigned char *image, int height, int width, int pitch, const char* imageFileName) {
    
        unsigned char padding[3] = { 0, 0, 0 };
        int paddingSize = (4 - (/*width*bytesPerPixel*/ pitch) % 4) % 4;
    
        unsigned char* fileHeader = createBitmapFileHeader(height, width, pitch, paddingSize);
        unsigned char* infoHeader = createBitmapInfoHeader(height, width);
    
        FILE* imageFile = fopen(imageFileName, "wb");
    
        fwrite(fileHeader, 1, fileHeaderSize, imageFile);
        fwrite(infoHeader, 1, infoHeaderSize, imageFile);
    
        int i;
        for (i = 0; i < height; i++) {
            fwrite(image + (i*pitch /*width*bytesPerPixel*/), bytesPerPixel, width, imageFile);
            fwrite(padding, 1, paddingSize, imageFile);
        }
    
        fclose(imageFile);
        //free(fileHeader);
        //free(infoHeader);
    }
    
    unsigned char* createBitmapFileHeader(int height, int width, int pitch, int paddingSize) {
        int fileSize = fileHeaderSize + infoHeaderSize + (/*bytesPerPixel*width*/pitch + paddingSize) * height;
    
        static unsigned char fileHeader[] = {
            0,0, /// signature
            0,0,0,0, /// image file size in bytes
            0,0,0,0, /// reserved
            0,0,0,0, /// start of pixel array
        };
    
        fileHeader[0] = (unsigned char)('B');
        fileHeader[1] = (unsigned char)('M');
        fileHeader[2] = (unsigned char)(fileSize);
        fileHeader[3] = (unsigned char)(fileSize >> 8);
        fileHeader[4] = (unsigned char)(fileSize >> 16);
        fileHeader[5] = (unsigned char)(fileSize >> 24);
        fileHeader[10] = (unsigned char)(fileHeaderSize + infoHeaderSize);
    
        return fileHeader;
    }
    
    unsigned char* createBitmapInfoHeader(int height, int width) {
        static unsigned char infoHeader[] = {
            0,0,0,0, /// header size
            0,0,0,0, /// image width
            0,0,0,0, /// image height
            0,0, /// number of color planes
            0,0, /// bits per pixel
            0,0,0,0, /// compression
            0,0,0,0, /// image size
            0,0,0,0, /// horizontal resolution
            0,0,0,0, /// vertical resolution
            0,0,0,0, /// colors in color table
            0,0,0,0, /// important color count
        };
    
        infoHeader[0] = (unsigned char)(infoHeaderSize);
        infoHeader[4] = (unsigned char)(width);
        infoHeader[5] = (unsigned char)(width >> 8);
        infoHeader[6] = (unsigned char)(width >> 16);
        infoHeader[7] = (unsigned char)(width >> 24);
        infoHeader[8] = (unsigned char)(height);
        infoHeader[9] = (unsigned char)(height >> 8);
        infoHeader[10] = (unsigned char)(height >> 16);
        infoHeader[11] = (unsigned char)(height >> 24);
        infoHeader[12] = (unsigned char)(1);
        infoHeader[14] = (unsigned char)(bytesPerPixel * 8);
    
        return infoHeader;
    }
    

  • Translate

    The best bitmap encoder is the one you do not write yourself. The file format is a lot more involved, than one might expect. This is evidenced by the fact, that all proposed answers do not create a monochrome (1bpp) bitmap, but rather write out 24bpp files, that happen to only use 2 colors.

    The following is a Windows-only solution, using the Windows Imaging Component. It doesn't rely on any external/3rd party libraries, other than what ships with Windows.

    Like every C++ program, we need to include several header files. And link to Windowscodecs.lib while we're at it:

    #include <Windows.h>
    #include <comdef.h>
    #include <comip.h>
    #include <comutil.h>
    #include <wincodec.h>
    
    #include <vector>
    
    #pragma comment(lib, "Windowscodecs.lib")
    

    Next up, we declare our container (a vector, of vectors! Of bool!), and a few smart pointers for convenience:

    using _com_util::CheckError;
    using container = std::vector<std::vector<bool>>;
    
    _COM_SMARTPTR_TYPEDEF(IWICImagingFactory, __uuidof(IWICImagingFactory));
    _COM_SMARTPTR_TYPEDEF(IWICBitmapEncoder, __uuidof(IWICBitmapEncoder));
    _COM_SMARTPTR_TYPEDEF(IWICBitmapFrameEncode, __uuidof(IWICBitmapFrameEncode));
    _COM_SMARTPTR_TYPEDEF(IWICStream, __uuidof(IWICStream));
    _COM_SMARTPTR_TYPEDEF(IWICPalette, __uuidof(IWICPalette));
    

    With that all settled, we can jump right into the implementation. There's a bit of setup required to get a factory, an encoder, a frame, and get everything prepared:

    void write_bitmap(wchar_t const* pathname, container const& data)
    {
        // Create factory
        IWICImagingFactoryPtr sp_factory { nullptr };
        CheckError(sp_factory.CreateInstance(CLSID_WICImagingFactory, nullptr,
                                             CLSCTX_INPROC_SERVER));
    
        // Create encoder
        IWICBitmapEncoderPtr sp_encoder { nullptr };
        CheckError(sp_factory->CreateEncoder(GUID_ContainerFormatBmp, nullptr, &sp_encoder));
    
        // Create stream
        IWICStreamPtr sp_stream { nullptr };
        CheckError(sp_factory->CreateStream(&sp_stream));
        CheckError(sp_stream->InitializeFromFilename(pathname, GENERIC_WRITE));
    
        // Initialize encoder with stream
        CheckError(sp_encoder->Initialize(sp_stream, WICBitmapEncoderNoCache));
    
        // Create new frame
        IWICBitmapFrameEncodePtr sp_frame { nullptr };
        IPropertyBag2Ptr sp_properties { nullptr };
        CheckError(sp_encoder->CreateNewFrame(&sp_frame, &sp_properties));
    
        // Initialize frame with default properties
        CheckError(sp_frame->Initialize(sp_properties));
    
        // Set pixel format
        // SetPixelFormat() requires a pointer to non-const
        auto pf { GUID_WICPixelFormat1bppIndexed };
        CheckError(sp_frame->SetPixelFormat(&pf));
        if (!::IsEqualGUID(pf, GUID_WICPixelFormat1bppIndexed))
        {
            // Report unsupported pixel format
            CheckError(WINCODEC_ERR_UNSUPPORTEDPIXELFORMAT);
        }
    
        // Set size derived from data argument
        auto const width { static_cast<UINT>(data.size()) };
        auto const height { static_cast<UINT>(data[0].size()) };
        CheckError(sp_frame->SetSize(width, height));
    
        // Set palette on frame. This is required since we use an indexed pixel format.
        // Only GIF files support global palettes, so make sure to set it on the frame
        // rather than the encoder.
        IWICPalettePtr sp_palette { nullptr };
        CheckError(sp_factory->CreatePalette(&sp_palette));
        CheckError(sp_palette->InitializePredefined(WICBitmapPaletteTypeFixedBW, FALSE));
        CheckError(sp_frame->SetPalette(sp_palette));
    

    At that point everything is set up, and we have a frame to dump our data into. For 1bpp files, every byte stores the information of 8 pixels. The left-most pixel is stored in the MSB, with pixels following all the way down to the right-most pixel stored in the LSB.

    The code isn't entirely important; you'll be replacing that with whatever suits your needs, when you replace the data layout of your input anyway:

        // Write data to frame
        auto const stride { (width * 1 + 7) / 8 };
        auto const size { height * stride };
        std::vector<unsigned char> buffer(size, 127u);
        // Convert data to match required layout. Each byte stores 8 pixels, with the
        // MSB being the leftmost, the LSB the right-most.
        for (size_t x { 0 }; x < data.size(); ++x)
        {
            for (size_t y { 0 }; y < data[x].size(); ++y)
            {
                auto shift { x % 8 };
                auto mask { 0x80 >> shift };
                auto bit { mask * data[x][y] };
                auto& value { buffer[y * stride + x / 8] };
                value &= ~mask;
                value |= bit;
            }
        }
        CheckError(sp_frame->WritePixels(height, stride,
                                         static_cast<UINT>(buffer.size()), buffer.data()));
    

    What's left is to commit the changes to the frame and the encoder, which will ultimately write the image file to disk:

        // Commit frame
        CheckError(sp_frame->Commit());
    
        // Commit image
        CheckError(sp_encoder->Commit());
    }
    

    This is a test program, writing out an image to a file passed as the first command-line argument:

    #include <iostream>
    
    int wmain(int argc, wchar_t* argv[])
    try
    {
        if (argc != 2)
        {
            return -1;
        }
    
        CheckError(::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED));
    
    
        // Create 64x64 matrix
        container data(64, std::vector<bool>(64, false));
        // Fill with arrow pointing towards the upper left
        for (size_t i { 0 }; i < data.size(); ++i)
        {
            data[0][i] = true;
            data[i][0] = true;
            data[i][i] = true;
        }
        ::write_bitmap(argv[1], data);
    
    
        ::CoUninitialize();
    }
    catch (_com_error const& e)
    {
        std::wcout << L"Error!\n" << L"  Message: " << e.ErrorMessage() << std::endl;
    }
    

    It produces the following image (true 1bpp, 574 bytes in size):

    Program output


  • Translate

    I just made this for fun today. This uses the C standard library. This renders a 24-bit image of color red, green, and blue, from bottom to top, respectively.

    I posted it in gist if you would like to see: https://gist.github.com/harieamjari/b1991624866556ab3e6baaf1ae79a313

    //Memo: http://www.ece.ualberta.ca/~elliott/ee552/studentAppNotes/2003_w/misc/bmp_file_format/bmp_file_format.htm
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    typedef struct __attribute__((__packed__)) {
      char Signature[2];
      uint32_t FileSize;
      uint32_t Reserved;
      uint32_t DataOffset;
    } bmp_header_struct;
    
    typedef struct __attribute__((__packed__)) {
      uint32_t Size;
      uint32_t Width;
      uint32_t Height;
      uint16_t Planes;
      uint16_t BitsPerPixel;
      uint32_t Compression;
      uint32_t ImageSize;
      uint32_t XPixelsPerMeter;
      uint32_t YPixelsPerMeter;
      uint32_t ColorsUsed;
      uint32_t ImportantColors;
    } bmp_infoheader_struct;
    
    enum { red = 0x00ff0000, green = 0x0000ff00, blue = 0x000000ff };
    
    int main() {
      FILE *bmp_file = fopen("write.bmp", "wb+");
      if (!bmp_file) {
        fprintf(stderr, "Couldn't open file!\n");
        return 127;
      }
    
      // uint24_t *pixel_x;
      // uint24_t *pixel_y;
      uint32_t *pixel_all;
    
      bmp_header_struct bmp_header0;
      bmp_infoheader_struct bmp_infoheader0;
    
      strncpy(bmp_header0.Signature, "BM", 2);
      bmp_header0.FileSize = 0;
      bmp_header0.Reserved = 0;
      bmp_header0.DataOffset = 36;
    
      bmp_infoheader0.Size = 40;
      bmp_infoheader0.Width = 400;
      bmp_infoheader0.Height = 1000;
      bmp_infoheader0.Planes = 1;
      bmp_infoheader0.BitsPerPixel = 24;
      bmp_infoheader0.Compression = 0;
      bmp_infoheader0.ImageSize = 0;
      bmp_infoheader0.XPixelsPerMeter = 0;
      bmp_infoheader0.YPixelsPerMeter = 0;
      bmp_infoheader0.ColorsUsed = 0;
      bmp_infoheader0.ImportantColors = 0;
    
      fwrite(&bmp_header0, sizeof(bmp_header0), 1, bmp_file);
      fwrite(&bmp_infoheader0, sizeof(bmp_infoheader0), 1, bmp_file);
    
      pixel_all =
          malloc(sizeof(uint32_t) * bmp_infoheader0.Width * bmp_infoheader0.Height);
      for (uint32_t y = 0; y < bmp_infoheader0.Height; y++) {
        for (uint32_t x = 0; x < bmp_infoheader0.Width; x++) {
          if (y < 1000)
            pixel_all[y] = blue;
          if (y < 620)
            pixel_all[y] = green;
          if (y < 320)
            pixel_all[y] = red;
          fwrite(pixel_all + y, 3, 1, bmp_file);
        }
      }
      uint32_t FileSize = ftell(bmp_file);
      fseek(bmp_file, 2, SEEK_SET);
      fwrite(&FileSize, sizeof(uint32_t), 1, bmp_file);
      free(pixel_all);
      fclose(bmp_file);
    
      printf("writing successful\n");
      return 0;
    }
    

    RGB bmp Soo beautiful...