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

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

【问题讨论】:

标签: c++ c image graphics bmp


【解决方案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 代码的改进版本,因为尽管它对于大多数应用程序来说都足够好,但我仍然遇到了一些问题。要记住两件非常重要的事情:

      1. 代码(在撰写本文时)在两个静态数组上调用 free()。这将导致您的程序崩溃。所以我把这些行注释掉了。
      2. 切勿假设像素数据的间距始终为 (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;
            }
            

            【讨论】:

              猜你喜欢
              • 2021-12-27
              • 2015-10-28
              • 1970-01-01
              • 1970-01-01
              • 2016-12-08
              • 2011-08-09
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多