Create simple bitmap in C (without external libraries)

后端 未结 1 603
孤街浪徒
孤街浪徒 2021-02-06 19:24

For learning purposes, I want to create a single 2x2 bitmap image using C programming language with no external libraries (such as SDL).

I\'ve read that I need to create

1条回答
  •  心在旅途
    2021-02-06 20:13

    why does the bmp header have those parameters

    The pixel data in bitmap is essentially a one-dimensional array of bytes. It's impossible to interpret this data without header information which reveals width, height, bit-count and other information about the bitmap.

    There are different types of bitmaps, including different palette format and non-palette 16, 24, or 32 bits, and different types within some of these groups.

    The header also includes other information which are there for historical reasons. This has little value for learning C. You just have to accept it.

    The header information is in two structures BITMAPFILEHEADER and BITMAPINFO It's a little more complicated because the structures are expected to be packed.

    The pixel data depends on the bitmap format. There is not a single bitmap format as mentioned earlier. The example below shows a 24-bit bitmap.

    Other oddities is that the width of each row should always be multiple of 4 bytes. This called padding. Also the bitmap rows start at the bottom, not at the top. This is explained in other resources

    #include 
    #include 
    #include 
    
    int main(void)
    {
        //width, height, and bitcount are the key factors:
        int32_t width = 2;
        int32_t height = 2;
        uint16_t bitcount = 24;//<- 24-bit bitmap
    
        //take padding in to account
        int width_in_bytes = ((width * bitcount + 31) / 32) * 4;
    
        //total image size in bytes, not including header
        uint32_t imagesize = width_in_bytes * height;
    
        //this value is always 40, it's the sizeof(BITMAPINFOHEADER)
        const uint32_t biSize = 40;
    
        //bitmap bits start after headerfile, 
        //this is sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)
        const uint32_t bfOffBits = 54; 
    
        //total file size:
        uint32_t filesize = 54 + imagesize;
    
        //number of planes is usually 1
        const uint16_t biPlanes = 1;
    
        //create header:
        //copy to buffer instead of BITMAPFILEHEADER and BITMAPINFOHEADER
        //to avoid problems with structure packing
        unsigned char header[54] = { 0 };
        memcpy(header, "BM", 2);
        memcpy(header + 2 , &filesize, 4);
        memcpy(header + 10, &bfOffBits, 4);
        memcpy(header + 14, &biSize, 4);
        memcpy(header + 18, &width, 4);
        memcpy(header + 22, &height, 4);
        memcpy(header + 26, &biPlanes, 2);
        memcpy(header + 28, &bitcount, 2);
        memcpy(header + 34, &imagesize, 4);
    
        //prepare pixel data:
        unsigned char* buf = malloc(imagesize);
        for(int row = height - 1; row >= 0; row--)
        {
            for(int col = 0; col < width; col++)
            {
                buf[row * width_in_bytes + col * 3 + 0] = 255;//blue
                buf[row * width_in_bytes + col * 3 + 1] = 0;//green
                buf[row * width_in_bytes + col * 3 + 2] = 0;//red
            }
        }
    
        FILE *fout = fopen("test.bmp", "wb");
        fwrite(header, 1, 54, fout);
        fwrite((char*)buf, 1, imagesize, fout);
        fclose(fout);
        free(buf);
    
        return 0;
    }
    

    You can rewrite this code using structures. In this code structure is packed with #pragma pack(push, 1) which is compiler dependent. Use this version if #pragma directive works.

    #include 
    #include 
    #include 
    #include 
    
    #pragma pack(push, 1)
    struct my_BITMAPFILEHEADER {
        uint16_t bfType;
        uint32_t bfSize;
        uint16_t bfReserved1;
        uint16_t bfReserved2;
        uint32_t bfOffBits;
    };
    
    struct my_BITMAPINFOHEADER {
        uint32_t biSize;
        int32_t  biWidth;
        int32_t  biHeight;
        uint16_t biPlanes;
        uint16_t biBitCount;
        uint32_t biCompression;
        uint32_t biSizeImage;
        int32_t  biXPelsPerMeter;
        int32_t  biYPelsPerMeter;
        uint32_t biClrUsed;
        uint32_t biClrImportant;
    };
    #pragma pack(pop)
    
    int main(void)
    {
        if(sizeof(struct my_BITMAPFILEHEADER) != 14 &&
            sizeof(struct my_BITMAPINFOHEADER) != 40)
        {
            printf("bitmap structures not packed properly\n");
            return 0;
        }
    
        //only width and height can be changed in this code:
        int width = 2;
        int height = 2;
    
        int bitcount = 24;//<- 24-bit bitmap
        int width_in_bytes = ((width * bitcount + 31) / 32) * 4;    //for padding
        uint32_t imagesize = width_in_bytes * height;   //total image size
    
        struct my_BITMAPFILEHEADER filehdr = { 0 };
        struct my_BITMAPINFOHEADER infohdr = { 0 };
    
        memcpy(&filehdr, "BM", 2);//bitmap signature
        filehdr.bfSize = 54 + imagesize;//total file size
        filehdr.bfOffBits = 54; //sizeof(filehdr) + sizeof(infohdr)
    
        infohdr.biSize = 40; //sizeof(infohdr)
        infohdr.biPlanes = 1; //number of planes is usually 1
        infohdr.biWidth = width;
        infohdr.biHeight = height;
        infohdr.biBitCount = bitcount;
        infohdr.biSizeImage = imagesize;
    
        //prepare pixel data:
        unsigned char* buf = malloc(imagesize);
        for(int row = height - 1; row >= 0; row--)
        {
            for(int col = 0; col < width; col++)
            {
                buf[row * width_in_bytes + col * 3 + 0] = 255;//blue
                buf[row * width_in_bytes + col * 3 + 1] = 0;//red
                buf[row * width_in_bytes + col * 3 + 2] = 0;//green
            }
        }
    
        FILE *fout = fopen("test.bmp", "wb");
        fwrite(&filehdr, sizeof(filehdr), 1, fout);
        fwrite(&infohdr, sizeof(infohdr), 1, fout);
        fwrite((char*)buf, 1, imagesize, fout);
        fclose(fout);
        free(buf);
    
        return 0;
    }
    

    The first version used unsigned char header[54] to achieve the same thing. See the size of each data and its relative position in memory. The structure is 14 bytes long. bfType starts at 0, and is 2 bytes long (16 bits). bfSize starts at position 2, and is 4 bytes long...

    struct my_BITMAPFILEHEADER {
        uint16_t bfType; //starts at 0, 2 bytes long
        uint32_t bfSize; //starts at 2, 4 bytes long
        uint16_t bfReserved1; //starts at 6, 2 bytes long
        uint16_t bfReserved2; //starts at 8, 2 bytes long
        uint32_t bfOffBits; //starts at 10, 4 bytes long
    };
    

    Then you have the next structure which is 40 bytes long:

    struct my_BITMAPINFOHEADER {
        uint32_t biSize;//starts at 14
        int32_t  biWidth;//starts at 18
        int32_t  biHeight;//starts at 22
        uint16_t biPlanes;//starts at 26
        uint16_t biBitCount;//starts at 28
        uint32_t biCompression;//starts at 30
        uint32_t biSizeImage;//starts at 34
        int32_t  biXPelsPerMeter;
        int32_t  biYPelsPerMeter;
        uint32_t biClrUsed;
        uint32_t biClrImportant;
    };
    

    biSize starts at 0. But the previous structure was 14 bytes. So biSzie starts at 14. This should reveal the mystery of the numbers used in previous version to copy integers to header

    0 讨论(0)
提交回复
热议问题