使用纯C/C++编写BMP图像而不使用其他库

62

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


1
你可以查看http://qdbmp.sourceforge.net/获取实现细节 :)。 - Marco
也许对于搜索类似概念的访问者有用的是我在这里提出的几乎相关的问题和答案:http://stackoverflow.com/questions/17918978/plot-an-array-into-bitmap-in-c-c-for-thermal-printer - Krista K
除非BMP是必需的,否则在https://dev59.com/dWQn5IYBdhLWcg3wr446中讨论了更简单的格式。 - mwfearnley
我可能稍后会写一篇答案,但请注意,如果您正在编写单色BMP,则没有必要通过每个像素使用多于1位(1 bpp)的空间。 BMP格式允许您指定每个像素的位数,无论是1、2、4、8、16、24还是32,因此对于单色BMP,您只需要每个像素1位(仅两种颜色)。您想要的颜色(在您的情况下为白色和黑色)写在BMP的颜色表中,该表紧随文件头之后(每个条目4个字节,对应于蓝色、绿色和红色分量,然后是0)。 - Gregor Hartl Watters
13个回答

67

看看这个能否解决你的问题... 在这段代码中,我有三个二维数组,分别称为red、green和blue。每个数组大小都是[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);

21
嗯,评论不会伤害。 - Mooing Duck
3
我认为这段代码没有考虑BITMAPFILEHEADER.bfSize中的填充,所以在加载时,如果您使用bfSize计算位图数据数组,则必须将填充添加到bfSize中。 - tobi
3
我认为"yres"应该替换成"h"。 - Andrew Bainbridge
1
memset(img,0,sizeof(img)) 应该是...还是其他什么? memset(img,0,3*w*h) - Sam
2
这将写入一个24bpp图像,恰好只使用了其中2^24种可用颜色中的2种。这是“单色”的一种创造性解释。真正的单色位图实际上只使用1bpp。 - IInspectable
显示剩余2条评论

51

生成位图(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;
}

unsigned char image[height][width][BYTES_PER_PIXEL]; 不是有效的,因为 heightwidth 不是常量变量。 - Kaiyakha
@Kaiyakha 这是 C99 中的内容。 - martinkunev
@martinkunev 很少有人使用它,至少那些不知道如何编写位图的人。 - Kaiyakha
如果你只是需要测试一些东西,使用代码比自己编写更快(这也是我的情况)。 - martinkunev

30

你可以查看BMP文件格式,而无需使用其他库。 我曾经实现过它,而且不需要太多的工作。

位图文件结构

每个位图文件包含一个 位图文件头,一个 位图信息头,一个颜色表, 以及一个定义位图位的字节数组。该文件的格式如下:

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

...请参阅文件格式以获取更多详细信息


3
如果您的处理器不是x86架构,请记得使用小端字节序来书写所有内容。 - Mark Ransom
有些硬件支持两种字节序,操作系统可以确定使用哪一种。 - Brian R. Bondy
7
抱歉,但是当外部链接失效或消失时,将其链接并不太有帮助。 - user1949917
6
链接已经失效,这里是Archive.org上原始链接的备份。 - user4223038
如果BMP是单色的,只有黑白两种颜色怎么办?我想将简单的单色文件转换为C数组,在这种情况下该怎么做? - R1S8K

12

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

仅返回翻译文本:仅回答链接不好,您应该提供一些关于答案的信息。 - Nguyen Kien

10

这是我写的一个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 );
}

2
padsize似乎不正确;我认为应该是:int padSize = (4-3*w%4)%4; - etienne
4
这里的“waterfall”是什么? - manujmv

9
请注意,行是从下往上保存的,而不是相反的方向。
此外,扫描线的字节长度必须是四的倍数,您应该在行的末尾插入填充字节以确保这一点。

4
我不理解你写的任何内容。 - den bardadym
23
别担心,一旦你真正尝试实施这个,你就会明白。 - Ben Voigt
2
@Ben,由于大多数现实世界的图像宽度已经是4的倍数,因此在测试中很容易忽略4的倍数要求。 - Mark Ransom
1
@mark:单色图像必须是32的倍数,因为对齐要求是以字节为单位而不是像素。 - Ben Voigt

8

我想分享一下Minhas Kamal代码的改进版本,虽然它对大多数应用程序来说已经足够好了,但我仍然有一些问题。 请记住两点:

  1. 在编写本文时,该代码在两个静态数组上调用了free()函数。 这将导致程序崩溃。 因此,我注释掉了那些行。
  2. 永远不要假定像素数据的间距始终为(宽度*每像素字节数)。最好让用户指定间距值。例如:在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;
}

3
非常感谢你指出我大学时期编写的代码中存在的问题,并做出这些改进。我会进行更新。 - Minhas Kamal

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

3
最好的位图编码器是你自己不写的。文件格式比人们预期的要复杂得多。这可以从所有提出的答案都没有创建单色(1bpp)位图,而是写出了24bpp文件,只使用了2种颜色来证明。
以下是仅适用于Windows的解决方案,使用Windows Imaging Component。它不依赖于任何外部/第三方库,除了随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;
}

它生成以下 64x64 的图像(真正的 1bpp,4096 像素,大小为 574 字节):

Program output


2
这是一个简单的C++ BMP图像文件类。
class bmp_img {
public:
    constexpr static int header_size = 14;
    constexpr static int info_header_size = 40;
    constexpr static size_t bytes_per_pixel = 3;

    bmp_img(size_t width, size_t height) :
        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 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;
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接