在没有其他库的情况下用纯 c/c++ 编写 BMP 图像

Posted

技术标签:

【中文标题】在没有其他库的情况下用纯 c/c++ 编写 BMP 图像【英文标题】:Writing BMP image in pure c/c++ without other libraries 【发布时间】:2011-02-08 21:57:36 【问题描述】:

在我的算法中,我需要创建一个信息输出。我需要将布尔矩阵写入 bmp 文件。 它必须是单色图像,如果此类元素上的矩阵为真,则像素为白色。 主要问题是bmp头以及如何编写。

【问题讨论】:

您可以查看qdbmp.sourceforge.net了解实现细节:)。 也许对访问者有用 谷歌搜索类似的概念是我几乎相关的问题和答案:***.com/questions/17918978/… 除非需要 BMP,否则***.com/questions/16636311/… 中讨论了更简单的格式 【参考方案1】:

看看这是否适合你... 在这段代码中,我有 3 个二维数组,分别称为红色、绿色和蓝色。每个都是 [width][height] 大小,每个元素对应一个像素 - 我希望这是有道理的!

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);

【讨论】:

嗯,cmets 不会受伤的。 我认为这段代码没有考虑 BITMAPFILEHEADER.bfSize 中的填充,因此在加载时,如果您使用它来计算位图数据的数组,则必须将填充添加到 bfSize。 我觉得yres应该换成h memset(img,0,sizeof(img)) 应该是……别的什么吗? memset(img,0,3*w*h)? 这会写入一个 24bpp 的图像,它恰好只使用了 2^24 种可用颜色中的 2 种。这是对“单色”的创造性解释。真正的单色位图实际上只使用 1bpp。【参考方案2】:

用于位图 (BMP) 图像生成的清洁 C 代码


此代码不使用除 stdio.h 之外的任何库。因此,它可以很容易地融入 C 系列的其他语言中,如 C++、C#、Java。


#include <stdio.h>

const int BYTES_PER_PIXEL = 3; /// red, green, & blue
const int FILE_HEADER_SIZE = 14;
const int INFO_HEADER_SIZE = 40;

void generateBitmapImage(unsigned char* image, int height, int width, char* imageFileName);
unsigned char* createBitmapFileHeader(int height, int stride);
unsigned char* createBitmapInfoHeader(int height, int width);


int main ()

    int height = 361;
    int width = 867;
    unsigned char image[height][width][BYTES_PER_PIXEL];
    char* imageFileName = (char*) "bitmapImage.bmp";

    int i, j;
    for (i = 0; i < height; i++) 
        for (j = 0; j < width; j++) 
            image[i][j][2] = (unsigned char) ( i * 255 / height );             ///red
            image[i][j][1] = (unsigned char) ( j * 255 / width );              ///green
            image[i][j][0] = (unsigned char) ( (i+j) * 255 / (height+width) ); ///blue
        
    

    generateBitmapImage((unsigned char*) image, height, width, imageFileName);
    printf("Image generated!!");



void generateBitmapImage (unsigned char* image, int height, int width, char* imageFileName)

    int widthInBytes = width * BYTES_PER_PIXEL;

    unsigned char padding[3] = 0, 0, 0;
    int paddingSize = (4 - (widthInBytes) % 4) % 4;

    int stride = (widthInBytes) + paddingSize;

    FILE* imageFile = fopen(imageFileName, "wb");

    unsigned char* fileHeader = createBitmapFileHeader(height, stride);
    fwrite(fileHeader, 1, FILE_HEADER_SIZE, imageFile);

    unsigned char* infoHeader = createBitmapInfoHeader(height, width);
    fwrite(infoHeader, 1, INFO_HEADER_SIZE, imageFile);

    int i;
    for (i = 0; i < height; i++) 
        fwrite(image + (i*widthInBytes), BYTES_PER_PIXEL, width, imageFile);
        fwrite(padding, 1, paddingSize, imageFile);
    

    fclose(imageFile);


unsigned char* createBitmapFileHeader (int height, int stride)

    int fileSize = FILE_HEADER_SIZE + INFO_HEADER_SIZE + (stride * 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)(FILE_HEADER_SIZE + INFO_HEADER_SIZE);

    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)(INFO_HEADER_SIZE);
    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)(BYTES_PER_PIXEL*8);

    return infoHeader;

【讨论】:

【参考方案3】:

不使用任何其他库您可以查看BMP file format。我过去已经实现了它,无需太多工作即可完成。

位图文件结构

每个位图文件都包含一个 位图文件头,一个 位图信息头,一种颜色 表和一个字节数组 定义位图位。该文件有 以下形式:

BITMAPFILEHEADER bmfh; BITMAPINFOHEADER bmih; RGBQUAD aColors[]; 字节 aBitmapBits[];

...查看文件格式了解更多详情

【讨论】:

如果您的处理器不是 x86,请记住将所有内容都写成 little-endian。 一些硬件同时支持,操作系统可以决定使用哪种字节序。 很抱歉,当外部网站干涸并消失时,链接到外部网站是非常无用的。 链接已失效,here 是 Archive.org 的原始 url 副本。 如果 BMP 是单色的,只有黑色和 while 怎么办?我想把简单的单色文件转成C数组,这种情况怎么办?【参考方案4】:

这是复制自的示例代码 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);

【讨论】:

只有链接的答案不好,你应该提供一些关于答案的信息。【参考方案5】:

请注意,这些行是从下到上保存的,而不是相反。

此外,扫描线的字节长度必须是四的倍数,您应该在行尾插入填充字节以确保这一点。

【讨论】:

你写的我什么都不懂。 别担心,一旦你真正尝试过实现这一点,你就会知道的。 @Ben,在测试中很容易错过四的倍数要求,因为大多数现实世界的图像已经是 4 宽的倍数。 @mark:单色图像必须是 32 的倍数,因为对齐要求以字节为单位,而不是像素。【参考方案6】:

这是适用于我的代码的 C++ 变体。请注意,我必须更改大小计算以考虑行填充。

// 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 );

【讨论】:

padsize 似乎不对;我认为应该是:int padSize = (4 - 3 * w % 4) % 4; 这里的瀑布[][] 是什么?【参考方案7】:

我编辑了 ralf 的 htp 代码,以便它可以编译(在 gcc 上,运行 ubuntu 16.04 lts)。这只是初始化变量的问题。

    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);

【讨论】:

【参考方案8】:

我只是想分享 Minhas Kamal 代码的改进版本,因为尽管它对于大多数应用程序来说都足够好,但我仍然遇到了一些问题。要记住两件非常重要的事情:

    代码(在撰写本文时)在两个静态数组上调用 free()。这将导致您的程序崩溃。所以我把这些行注释掉了。 切勿假设像素数据的间距始终为 (Width*BytesPerPixel)。最好让用户指定音高值。示例:在 Direct3D 中操作资源时,永远不能保证 RowPitch 是正在使用的字节深度的偶数倍。这可能会导致生成的位图出错(尤其是在奇怪的分辨率下,例如 1366x768)。

下面,你可以看到我对他的代码的修改:

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;

【讨论】:

我真的非常感谢指出我的错误(我在大学时的代码)并做出了这些改进。我会更新的。【参考方案9】:

如果您使用上述 C++ 函数在图像中间出现奇怪的颜色切换。一定要以二进制模式打开外流: imgFile.open(filename, std::ios_base::out | std::ios_base::binary); 否则windows会在你的文件中间插入不需要的字符! (在这个问题上已经敲了好几个小时)

在此处查看相关问题:Why does ofstream insert a 0x0D byte before 0x0A?

【讨论】:

【参考方案10】:

最好的位图编码器不是您自己编写的。文件格式比人们预期的要复杂得多。事实证明,所有建议的答案都不会创建单色 (1bpp) 位图,而是写出 24bpp 文件,而这些文件恰好只使用 2 种颜色。

以下是仅适用于 Windows 的解决方案,使用 Windows Imaging Component。它不依赖于任何外部/第 3 方库,除了 Windows 附带的库。

像每个 C++ 程序一样,我们需要包含几个头文件。并在我们使用它时链接到 Windowscodecs.lib

#include <Windows.h>
#include <comdef.h>
#include <comip.h>
#include <comutil.h>
#include <wincodec.h>

#include <vector>

#pragma comment(lib, "Windowscodecs.lib")

接下来,我们声明我们的容器(一个向量,多个向量!属于bool!),以及一些方便起见的智能指针:

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));

所有这些都解决了,我们可以直接进入实施阶段。需要进行一些设置才能获得工厂、编码器、框架并准备好一切:

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));

此时一切都已设置好,并且我们有一个框架可以将数据转储到其中。对于 1bpp 文件,每个字节存储 8 个像素的信息。最左边的像素存储在 MSB 中,像素一直向下到最右边的像素存储在 LSB 中。

代码并不完全重要;当您替换输入的数据布局时,您将用适合您需要的任何内容替换它:

    // 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()));

剩下的就是将更改提交到帧和编码器,最终将图像文件写入磁盘:

    // Commit frame
    CheckError(sp_frame->Commit());

    // Commit image
    CheckError(sp_encoder->Commit());

这是一个测试程序,将图像写入作为第一个命令行参数传递的文件:

#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;

它产生以下图像(真正的 1bpp,大小为 574 字节):

【讨论】:

【参考方案11】:

这是一个简单的 c++ bmp 图像文件类。

class bmp_img 
public:
    constexpr static int header_size = 14;
    constexpr static int info_header_size = 40;

    bmp_img(size_t width, size_t height, size_t bpp = 3) :
        bytes_per_pixel bpp , image_px_width width , image_px_height height , row_width image_px_width * bytes_per_pixel ,
        row_padding (4 - row_width % 4) % 4 , row_stride row_width + row_padding , file_size header_size + info_header_size + (image_px_height * row_stride) ,
        image(image_px_height, std::vector<unsigned char>(row_width))
    
        //header file type
        file_header[0] = 'B';
        file_header[1] = 'M';


        //header file size info
        file_header[2] = static_cast<unsigned char>(file_size);
        file_header[3] = static_cast<unsigned char>(file_size >> 8);
        file_header[4] = static_cast<unsigned char>(file_size >> 16);
        file_header[5] = static_cast<unsigned char>(file_size >> 24);

        //header offset to pixel data
        file_header[10] = header_size + info_header_size;

        //info header size
        info_header[0] = info_header_size;

        //info header image width
        info_header[4] = static_cast<unsigned char>(image_px_width);
        info_header[5] = static_cast<unsigned char>(image_px_width >> 8);
        info_header[6] = static_cast<unsigned char>(image_px_width >> 16);
        info_header[7] = static_cast<unsigned char>(image_px_width >> 24);

        //info header image height
        info_header[8] = static_cast<unsigned char>(image_px_height);
        info_header[9] = static_cast<unsigned char>(image_px_height >> 8);
        info_header[10] = static_cast<unsigned char>(image_px_height >> 16);
        info_header[11] = static_cast<unsigned char>(image_px_height >> 24);

        //info header planes
        info_header[12] = 1;

        //info header bits per pixel
        info_header[14] = 8 * bytes_per_pixel;
    

    size_t width() const 
        return image_px_width;
    

    size_t height() const 
        return image_px_height;
    

    void set_pixel(size_t x, size_t y, int r, int g, int b) 
        image[y][x * bytes_per_pixel + 2] = r;
        image[y][x * bytes_per_pixel + 1] = g;
        image[y][x * bytes_per_pixel + 0] = b;
    

    void fill(int r, int g, int b) 
        for (int y = 0; y < image_px_height; ++y) 
            for (int x = 0; x < image_px_width; ++x) 
                set_pixel(x, y, r, g, b);
            
        
    

    void write_to_file(const char* file_name) const 
        std::ofstream img_file(file_name, std::ios_base::binary | std::ios_base::out);

        img_file.write((char*)file_header, header_size);
        img_file.write((char*)info_header, info_header_size);

        std::vector<char> allignment(row_padding);

        for (int y = image_px_height - 1; y >= 0; --y) 
            img_file.write((char*)image[y].data(), row_width);

            img_file.write(allignment.data(), row_padding);
        

        img_file.close();
    
private:
    size_t bytes_per_pixel;

    size_t image_px_width;
    size_t image_px_height;

    size_t row_width;

    size_t row_padding;

    size_t row_stride;

    size_t file_size;

    unsigned char file_header[header_size] =  0 ;
    unsigned char info_header[info_header_size] =  0 ;
    std::vector<std::vector<unsigned char>> image;
;

【讨论】:

【参考方案12】:

C++ 答案,灵活的 API,假设 little-endian 系统对其进行编码。请注意,这使用 bmp 原生 y 轴(底部为 0)。

#include <vector>
#include <fstream>

struct image
   
    image(int width, int height)
    :   w(width), h(height), rgb(w * h * 3)
    
    uint8_t & r(int x, int y)  return rgb[(x + y*w)*3 + 2]; 
    uint8_t & g(int x, int y)  return rgb[(x + y*w)*3 + 1]; 
    uint8_t & b(int x, int y)  return rgb[(x + y*w)*3 + 0]; 

    int w, h;
    std::vector<uint8_t> rgb;
;

template<class Stream>
Stream & operator<<(Stream & out, image const& img)
   
    uint32_t w = img.w, h = img.h;
    uint32_t pad = w * -3 & 3;
    uint32_t total = 54 + 3*w*h + pad*h;
    uint32_t head[13] = total, 0, 54, 40, w, h, (24<<16)|1;
    char const* rgb = (char const*)img.rgb.data();

    out.write("BM", 2);
    out.write((char*)head, 52);
    for(uint32_t i=0 ; i<h ; i++)
       out.write(rgb + (3 * w * i), 3 * w);
        out.write((char*)&pad, pad);
    
    return out;


int main()

    image img(100, 100);
    for(int x=0 ; x<100 ; x++)
       for(int y=0 ; y<100 ; y++)
           img.r(x,y) = x;
            img.g(x,y) = y;
            img.b(x,y) = 100-x;
        
    
    std::ofstream("/tmp/out.bmp") << img;

【讨论】:

以上是关于在没有其他库的情况下用纯 c/c++ 编写 BMP 图像的主要内容,如果未能解决你的问题,请参考以下文章

在没有库的情况下读取 Excel 文件

Visual Studio 2015:在没有运行时库的情况下编译 C/C++

是否可以在没有标准库的情况下写入控制台? c/c++

c/c++编写dll供其他语言调用

简单的约束规划求解器

golang cgo 使用总结