用纯c/c++编写BMP图像没有其他库

发布于 2024-08-29 08:04:20 字数 92 浏览 12 评论 0原文

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(14

小红帽 2024-09-05 08:04:21

C++ 答案,灵活的 API,但是假设小端系统。请注意,这使用 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++ answer, flexible API, assumes little-endian system however. Note this uses the bmp native y-axis (0 at the bottom).

#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;
}
溺孤伤于心 2024-09-05 08:04:21

单个函数中的简单 C++ 版本。这没有很多其他答案的缺陷,例如它是线程安全的,不会无意义地使用类或多个函数,防止整数溢出,使用模糊正确的类型,没有不必要的幻数等等

。不过,我对其进行了广泛的测试。

#include <cassert>
#include <cstdint>
#include <fstream>
#include <iostream>

void writeBmp(const uint8_t* bgr, uint32_t width, uint32_t height, std::ostream& out) {
    const unsigned BYTES_PER_PIXEL = 3;
    const unsigned FILE_HEADER_SIZE = 14;
    const unsigned INFO_HEADER_SIZE = 40;

    auto write16 = [&out](uint16_t value) {
        out.put(static_cast<uint8_t>(value));
        out.put(static_cast<uint8_t>(value >> 8));
    };
    auto write32 = [&out](uint32_t value) {
        out.put(static_cast<uint8_t>(value));
        out.put(static_cast<uint8_t>(value >> 8));
        out.put(static_cast<uint8_t>(value >> 16));
        out.put(static_cast<uint8_t>(value >> 24));
    };

    // Ensure no overflow. This is conservative but simple.
    assert(width <= 30000 && height <= 30000);

    const uint32_t input_stride = width * BYTES_PER_PIXEL;
    const uint32_t output_stride = ((input_stride + 3) / 4) * 4;
    const uint32_t padding = output_stride - input_stride;
    const uint32_t image_size = output_stride * height;
    const uint32_t file_size = image_size + FILE_HEADER_SIZE + INFO_HEADER_SIZE;

    // Signature.
    out.put('B');
    out.put('M');
    // Image file size.
    write32(file_size);
    // Reserved.
    write16(0);
    write16(0);
    // Offset to image data.
    write32(FILE_HEADER_SIZE + INFO_HEADER_SIZE);

    // BITMAPINFOHEADER
    write32(INFO_HEADER_SIZE);
    write32(width);
    write32(height);
    // Planes.
    write16(1);
    // Bits per pixel.
    write16(BYTES_PER_PIXEL * 8);
    // Compression.
    write32(0);
    // Image data size.
    write32(image_size);
    // X/Y pixels per meter, for printing. This value is 72 DPI.
    write32(2835);
    write32(2835);
    // Colours in colour table.
    write32(0);
    // Important colours in colour table.
    write32(0);

    // Write the image data.
    for (uint32_t y1 = height; y1 > 0; --y1) {
        out.write(reinterpret_cast<const char*>(&bgr[(y1 - 1) * input_stride]), input_stride);
        for (uint32_t x = 0; x < padding; ++x) {
            out.put(0);
        }
    }
}

int main() {
    const unsigned height = 300;
    const unsigned width = 400;
    uint8_t image[width * height * 3] = {0};
    for (unsigned y = 0; y < height; ++y) {
        for (unsigned x = 0; x < width; ++x) {
            uint8_t* pixel = &image[(y * width + x) * 3];
            pixel[2] = static_cast<uint8_t>(x); // red
            pixel[1] = static_cast<uint8_t>(y); // green
            pixel[0] = static_cast<uint8_t>(x+y); // blue
        }
    }

    // Note, `std::ios::binary` is REQUIRED on Windows. Wasted an hour of my
    // life figuring that out. Stupid C++ (and Windows).
    std::ofstream out("out.bmp", std::ios::binary);
    writeBmp(image, width, height, out);
}

Simple C++ version in a single function. This doesn't have flaws of a lot of the other answers, e.g. it's thread safe, doesn't pointlessly use classes or multiple functions, prevents integer overflow, uses vaguely correct types, doesn't have unnecessarily magic numbers, etc.

Haven't tested it extensively though.

#include <cassert>
#include <cstdint>
#include <fstream>
#include <iostream>

void writeBmp(const uint8_t* bgr, uint32_t width, uint32_t height, std::ostream& out) {
    const unsigned BYTES_PER_PIXEL = 3;
    const unsigned FILE_HEADER_SIZE = 14;
    const unsigned INFO_HEADER_SIZE = 40;

    auto write16 = [&out](uint16_t value) {
        out.put(static_cast<uint8_t>(value));
        out.put(static_cast<uint8_t>(value >> 8));
    };
    auto write32 = [&out](uint32_t value) {
        out.put(static_cast<uint8_t>(value));
        out.put(static_cast<uint8_t>(value >> 8));
        out.put(static_cast<uint8_t>(value >> 16));
        out.put(static_cast<uint8_t>(value >> 24));
    };

    // Ensure no overflow. This is conservative but simple.
    assert(width <= 30000 && height <= 30000);

    const uint32_t input_stride = width * BYTES_PER_PIXEL;
    const uint32_t output_stride = ((input_stride + 3) / 4) * 4;
    const uint32_t padding = output_stride - input_stride;
    const uint32_t image_size = output_stride * height;
    const uint32_t file_size = image_size + FILE_HEADER_SIZE + INFO_HEADER_SIZE;

    // Signature.
    out.put('B');
    out.put('M');
    // Image file size.
    write32(file_size);
    // Reserved.
    write16(0);
    write16(0);
    // Offset to image data.
    write32(FILE_HEADER_SIZE + INFO_HEADER_SIZE);

    // BITMAPINFOHEADER
    write32(INFO_HEADER_SIZE);
    write32(width);
    write32(height);
    // Planes.
    write16(1);
    // Bits per pixel.
    write16(BYTES_PER_PIXEL * 8);
    // Compression.
    write32(0);
    // Image data size.
    write32(image_size);
    // X/Y pixels per meter, for printing. This value is 72 DPI.
    write32(2835);
    write32(2835);
    // Colours in colour table.
    write32(0);
    // Important colours in colour table.
    write32(0);

    // Write the image data.
    for (uint32_t y1 = height; y1 > 0; --y1) {
        out.write(reinterpret_cast<const char*>(&bgr[(y1 - 1) * input_stride]), input_stride);
        for (uint32_t x = 0; x < padding; ++x) {
            out.put(0);
        }
    }
}

int main() {
    const unsigned height = 300;
    const unsigned width = 400;
    uint8_t image[width * height * 3] = {0};
    for (unsigned y = 0; y < height; ++y) {
        for (unsigned x = 0; x < width; ++x) {
            uint8_t* pixel = &image[(y * width + x) * 3];
            pixel[2] = static_cast<uint8_t>(x); // red
            pixel[1] = static_cast<uint8_t>(y); // green
            pixel[0] = static_cast<uint8_t>(x+y); // blue
        }
    }

    // Note, `std::ios::binary` is REQUIRED on Windows. Wasted an hour of my
    // life figuring that out. Stupid C++ (and Windows).
    std::ofstream out("out.bmp", std::ios::binary);
    writeBmp(image, width, height, out);
}
过去的过去 2024-09-05 08:04:20

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

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

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

FILE *f;
unsigned char *img = NULL;
int filesize = 54 + 3*w*h;  //w is your image width, h is image height, both int

img = (unsigned char *)malloc(3*w*h);
memset(img,0,3*w*h);

for(int i=0; i<w; i++)
{
    for(int j=0; j<h; j++)
    {
        x=i; y=(h-1)-j;
        r = red[i][j]*255;
        g = green[i][j]*255;
        b = blue[i][j]*255;
        if (r > 255) r=255;
        if (g > 255) g=255;
        if (b > 255) b=255;
        img[(x+y*w)*3+2] = (unsigned char)(r);
        img[(x+y*w)*3+1] = (unsigned char)(g);
        img[(x+y*w)*3+0] = (unsigned char)(b);
    }
}

unsigned char bmpfileheader[14] = {'B','M', 0,0,0,0, 0,0, 0,0, 54,0,0,0};
unsigned char bmpinfoheader[40] = {40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0, 24,0};
unsigned char bmppad[3] = {0,0,0};

bmpfileheader[ 2] = (unsigned char)(filesize    );
bmpfileheader[ 3] = (unsigned char)(filesize>> 8);
bmpfileheader[ 4] = (unsigned char)(filesize>>16);
bmpfileheader[ 5] = (unsigned char)(filesize>>24);

bmpinfoheader[ 4] = (unsigned char)(       w    );
bmpinfoheader[ 5] = (unsigned char)(       w>> 8);
bmpinfoheader[ 6] = (unsigned char)(       w>>16);
bmpinfoheader[ 7] = (unsigned char)(       w>>24);
bmpinfoheader[ 8] = (unsigned char)(       h    );
bmpinfoheader[ 9] = (unsigned char)(       h>> 8);
bmpinfoheader[10] = (unsigned char)(       h>>16);
bmpinfoheader[11] = (unsigned char)(       h>>24);

f = fopen("img.bmp","wb");
fwrite(bmpfileheader,1,14,f);
fwrite(bmpinfoheader,1,40,f);
for(int i=0; i<h; i++)
{
    fwrite(img+(w*(h-i-1)*3),3,w,f);
    fwrite(bmppad,1,(4-(w*3)%4)%4,f);
}

free(img);
fclose(f);
相守太难 2024-09-05 08:04:20

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

bitmap image


此代码不使用除 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;
}

Clean C Code for Bitmap (BMP) Image Generation

bitmap image


This code does not use any library other than stdio.h. So, it can be easily incorporated in other languages of C-Family, like- 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;
}
烟酒忠诚 2024-09-05 08:04:20

不使用任何其他库,您可以查看 BMP 文件格式。我过去已经实现过它,不需要太多工作就可以完成。

位图文件结构

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

位图文件头 bmfh;
BITMAPINFHEADER bmih;
RGBQUAD aColors[];
BYTE aBitmapBits[];

...有关更多详细信息,请参阅文件格式

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

Bitmap-File Structures

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

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

... see the file format for more details

极度宠爱 2024-09-05 08:04:20

这是从复制的示例代码
https://en.wikipedia.org/wiki/User:Evercat/Buddhabrot。

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

this is a example code copied from
https://en.wikipedia.org/wiki/User:Evercat/Buddhabrot.c

void drawbmp (char * filename) {

unsigned int headers[13];
FILE * outfile;
int extrabytes;
int paddedsize;
int x; int y; int n;
int red, green, blue;

extrabytes = 4 - ((WIDTH * 3) % 4);                 // How many bytes of padding to add to each
                                                    // horizontal line - the size of which must
                                                    // be a multiple of 4 bytes.
if (extrabytes == 4)
   extrabytes = 0;

paddedsize = ((WIDTH * 3) + extrabytes) * HEIGHT;

// Headers...
// Note that the "BM" identifier in bytes 0 and 1 is NOT included in these "headers".
                     
headers[0]  = paddedsize + 54;      // bfSize (whole file size)
headers[1]  = 0;                    // bfReserved (both)
headers[2]  = 54;                   // bfOffbits
headers[3]  = 40;                   // biSize
headers[4]  = WIDTH;  // biWidth
headers[5]  = HEIGHT; // biHeight

// Would have biPlanes and biBitCount in position 6, but they're shorts.
// It's easier to write them out separately (see below) than pretend
// they're a single int, especially with endian issues...

headers[7]  = 0;                    // biCompression
headers[8]  = paddedsize;           // biSizeImage
headers[9]  = 0;                    // biXPelsPerMeter
headers[10] = 0;                    // biYPelsPerMeter
headers[11] = 0;                    // biClrUsed
headers[12] = 0;                    // biClrImportant

outfile = fopen(filename, "wb");

//
// Headers begin...
// When printing ints and shorts, we write out 1 character at a time to avoid endian issues.
//

fprintf(outfile, "BM");

for (n = 0; n <= 5; n++)
{
   fprintf(outfile, "%c", headers[n] & 0x000000FF);
   fprintf(outfile, "%c", (headers[n] & 0x0000FF00) >> 8);
   fprintf(outfile, "%c", (headers[n] & 0x00FF0000) >> 16);
   fprintf(outfile, "%c", (headers[n] & (unsigned int) 0xFF000000) >> 24);
}

// These next 4 characters are for the biPlanes and biBitCount fields.

fprintf(outfile, "%c", 1);
fprintf(outfile, "%c", 0);
fprintf(outfile, "%c", 24);
fprintf(outfile, "%c", 0);

for (n = 7; n <= 12; n++)
{
   fprintf(outfile, "%c", headers[n] & 0x000000FF);
   fprintf(outfile, "%c", (headers[n] & 0x0000FF00) >> 8);
   fprintf(outfile, "%c", (headers[n] & 0x00FF0000) >> 16);
   fprintf(outfile, "%c", (headers[n] & (unsigned int) 0xFF000000) >> 24);
}

//
// Headers done, now write the data...
//

for (y = HEIGHT - 1; y >= 0; y--)     // BMP image format is written from bottom to top...
{
   for (x = 0; x <= WIDTH - 1; x++)
   {

      red = reduce(redcount[x][y] + COLOUR_OFFSET) * red_multiplier;
      green = reduce(greencount[x][y] + COLOUR_OFFSET) * green_multiplier;
      blue = reduce(bluecount[x][y] + COLOUR_OFFSET) * blue_multiplier;
      
      if (red > 255) red = 255; if (red < 0) red = 0;
      if (green > 255) green = 255; if (green < 0) green = 0;
      if (blue > 255) blue = 255; if (blue < 0) blue = 0;
      
      // Also, it's written in (b,g,r) format...

      fprintf(outfile, "%c", blue);
      fprintf(outfile, "%c", green);
      fprintf(outfile, "%c", red);
   }
   if (extrabytes)      // See above - BMP lines must be of lengths divisible by 4.
   {
      for (n = 1; n <= extrabytes; n++)
      {
         fprintf(outfile, "%c", 0);
      }
   }
}

fclose(outfile);
return;
}


drawbmp(filename);
当梦初醒 2024-09-05 08:04:20

请注意,线条是从下到上保存的,而不是相反。

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

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

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

度的依靠╰つ 2024-09-05 08:04:20

这是适合我的代码的 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 );
}

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

// mimeType = "image/bmp";

unsigned char file[14] = {
    'B','M', // magic
    0,0,0,0, // size in bytes
    0,0, // app data
    0,0, // app data
    40+14,0,0,0 // start of data offset
};
unsigned char info[40] = {
    40,0,0,0, // info hd size
    0,0,0,0, // width
    0,0,0,0, // heigth
    1,0, // number color planes
    24,0, // bits per pixel
    0,0,0,0, // compression is none
    0,0,0,0, // image bits size
    0x13,0x0B,0,0, // horz resoluition in pixel / m
    0x13,0x0B,0,0, // vert resolutions (0x03C3 = 96 dpi, 0x0B13 = 72 dpi)
    0,0,0,0, // #colors in pallete
    0,0,0,0, // #important colors
    };

int w=waterfallWidth;
int h=waterfallHeight;

int padSize  = (4-(w*3)%4)%4;
int sizeData = w*h*3 + h*padSize;
int sizeAll  = sizeData + sizeof(file) + sizeof(info);

file[ 2] = (unsigned char)( sizeAll    );
file[ 3] = (unsigned char)( sizeAll>> 8);
file[ 4] = (unsigned char)( sizeAll>>16);
file[ 5] = (unsigned char)( sizeAll>>24);

info[ 4] = (unsigned char)( w   );
info[ 5] = (unsigned char)( w>> 8);
info[ 6] = (unsigned char)( w>>16);
info[ 7] = (unsigned char)( w>>24);

info[ 8] = (unsigned char)( h    );
info[ 9] = (unsigned char)( h>> 8);
info[10] = (unsigned char)( h>>16);
info[11] = (unsigned char)( h>>24);

info[20] = (unsigned char)( sizeData    );
info[21] = (unsigned char)( sizeData>> 8);
info[22] = (unsigned char)( sizeData>>16);
info[23] = (unsigned char)( sizeData>>24);

stream.write( (char*)file, sizeof(file) );
stream.write( (char*)info, sizeof(info) );

unsigned char pad[3] = {0,0,0};

for ( int y=0; y<h; y++ )
{
    for ( int x=0; x<w; x++ )
    {
        long red = lround( 255.0 * waterfall[x][y] );
        if ( red < 0 ) red=0;
        if ( red > 255 ) red=255;
        long green = red;
        long blue = red;

        unsigned char pixel[3];
        pixel[0] = blue;
        pixel[1] = green;
        pixel[2] = red;

        stream.write( (char*)pixel, 3 );
    }
    stream.write( (char*)pad, padSize );
}
一直在等你来 2024-09-05 08:04:20

我只是想分享 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;
}

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

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

Below, you can see my revisions to his code:

const int bytesPerPixel = 4; /// red, green, blue
const int fileHeaderSize = 14;
const int infoHeaderSize = 40;

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



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

    unsigned char padding[3] = { 0, 0, 0 };
    int paddingSize = (4 - (/*width*bytesPerPixel*/ pitch) % 4) % 4;

    unsigned char* fileHeader = createBitmapFileHeader(height, width, pitch, paddingSize);
    unsigned char* infoHeader = createBitmapInfoHeader(height, width);

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

    fwrite(fileHeader, 1, fileHeaderSize, imageFile);
    fwrite(infoHeader, 1, infoHeaderSize, imageFile);

    int i;
    for (i = 0; i < height; i++) {
        fwrite(image + (i*pitch /*width*bytesPerPixel*/), bytesPerPixel, width, imageFile);
        fwrite(padding, 1, paddingSize, imageFile);
    }

    fclose(imageFile);
    //free(fileHeader);
    //free(infoHeader);
}

unsigned char* createBitmapFileHeader(int height, int width, int pitch, int paddingSize) {
    int fileSize = fileHeaderSize + infoHeaderSize + (/*bytesPerPixel*width*/pitch + paddingSize) * height;

    static unsigned char fileHeader[] = {
        0,0, /// signature
        0,0,0,0, /// image file size in bytes
        0,0,0,0, /// reserved
        0,0,0,0, /// start of pixel array
    };

    fileHeader[0] = (unsigned char)('B');
    fileHeader[1] = (unsigned char)('M');
    fileHeader[2] = (unsigned char)(fileSize);
    fileHeader[3] = (unsigned char)(fileSize >> 8);
    fileHeader[4] = (unsigned char)(fileSize >> 16);
    fileHeader[5] = (unsigned char)(fileSize >> 24);
    fileHeader[10] = (unsigned char)(fileHeaderSize + infoHeaderSize);

    return fileHeader;
}

unsigned char* createBitmapInfoHeader(int height, int width) {
    static unsigned char infoHeader[] = {
        0,0,0,0, /// header size
        0,0,0,0, /// image width
        0,0,0,0, /// image height
        0,0, /// number of color planes
        0,0, /// bits per pixel
        0,0,0,0, /// compression
        0,0,0,0, /// image size
        0,0,0,0, /// horizontal resolution
        0,0,0,0, /// vertical resolution
        0,0,0,0, /// colors in color table
        0,0,0,0, /// important color count
    };

    infoHeader[0] = (unsigned char)(infoHeaderSize);
    infoHeader[4] = (unsigned char)(width);
    infoHeader[5] = (unsigned char)(width >> 8);
    infoHeader[6] = (unsigned char)(width >> 16);
    infoHeader[7] = (unsigned char)(width >> 24);
    infoHeader[8] = (unsigned char)(height);
    infoHeader[9] = (unsigned char)(height >> 8);
    infoHeader[10] = (unsigned char)(height >> 16);
    infoHeader[11] = (unsigned char)(height >> 24);
    infoHeader[12] = (unsigned char)(1);
    infoHeader[14] = (unsigned char)(bytesPerPixel * 8);

    return infoHeader;
}
寻梦旅人 2024-09-05 08:04:20

我编辑了 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);

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

    int w = 100; /* Put here what ever width you want */
    int h = 100; /* Put here what ever height you want */
    int red[w][h]; 
    int green[w][h];
    int blue[w][h];


    FILE *f;
    unsigned char *img = NULL;
    int filesize = 54 + 3*w*h;  //w is your image width, h is image height, both int
    if( img )
            free( img );
    img = (unsigned char *)malloc(3*w*h);
    memset(img,0,sizeof(img));
    int x;
    int y;
    int r;
    int g;
    int b;

    for(int i=0; i<w; i++)
    {
            for(int j=0; j<h; j++)
            {
                    x=i; y=(h-1)-j;
                    r = red[i][j]*255;
                    g = green[i][j]*255;
                    b = blue[i][j]*255;
                    if (r > 255) r=255;
                    if (g > 255) g=255;
                    if (b > 255) b=255;
                    img[(x+y*w)*3+2] = (unsigned char)(r);
                    img[(x+y*w)*3+1] = (unsigned char)(g);
                    img[(x+y*w)*3+0] = (unsigned char)(b);
            }
    }

    unsigned char bmpfileheader[14] = {'B','M', 0,0,0,0, 0,0, 0,0, 54,0,0,0};
    unsigned char bmpinfoheader[40] = {40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0, 24,0};
    unsigned char bmppad[3] = {0,0,0};

    bmpfileheader[ 2] = (unsigned char)(filesize    );
    bmpfileheader[ 3] = (unsigned char)(filesize>> 8);
    bmpfileheader[ 4] = (unsigned char)(filesize>>16);
    bmpfileheader[ 5] = (unsigned char)(filesize>>24);

    bmpinfoheader[ 4] = (unsigned char)(       w    );
    bmpinfoheader[ 5] = (unsigned char)(       w>> 8);
    bmpinfoheader[ 6] = (unsigned char)(       w>>16);
    bmpinfoheader[ 7] = (unsigned char)(       w>>24);
    bmpinfoheader[ 8] = (unsigned char)(       h    );
    bmpinfoheader[ 9] = (unsigned char)(       h>> 8);
    bmpinfoheader[10] = (unsigned char)(       h>>16);
    bmpinfoheader[11] = (unsigned char)(       h>>24);

    f = fopen("img.bmp","wb");
    fwrite(bmpfileheader,1,14,f);
    fwrite(bmpinfoheader,1,40,f);
    for(int i=0; i<h; i++)
    {
            fwrite(img+(w*(h-i-1)*3),3,w,f);
            fwrite(bmppad,1,(4-(w*3)%4)%4,f);
    }
    fclose(f);
聊慰 2024-09-05 08:04:20

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

以下是仅适用于 Windows 的解决方案,使用 Windows 映像组件。除了 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 字节):

程序输出

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

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

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

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

#include <vector>

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

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

using _com_util::CheckError;
using container = std::vector<std::vector<bool>>;

_COM_SMARTPTR_TYPEDEF(IWICImagingFactory, __uuidof(IWICImagingFactory));
_COM_SMARTPTR_TYPEDEF(IWICBitmapEncoder, __uuidof(IWICBitmapEncoder));
_COM_SMARTPTR_TYPEDEF(IWICBitmapFrameEncode, __uuidof(IWICBitmapFrameEncode));
_COM_SMARTPTR_TYPEDEF(IWICStream, __uuidof(IWICStream));
_COM_SMARTPTR_TYPEDEF(IWICPalette, __uuidof(IWICPalette));

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

void write_bitmap(wchar_t const* pathname, container const& data)
{
    // Create factory
    IWICImagingFactoryPtr sp_factory { nullptr };
    CheckError(sp_factory.CreateInstance(CLSID_WICImagingFactory, nullptr,
                                         CLSCTX_INPROC_SERVER));

    // Create encoder
    IWICBitmapEncoderPtr sp_encoder { nullptr };
    CheckError(sp_factory->CreateEncoder(GUID_ContainerFormatBmp, nullptr, &sp_encoder));

    // Create stream
    IWICStreamPtr sp_stream { nullptr };
    CheckError(sp_factory->CreateStream(&sp_stream));
    CheckError(sp_stream->InitializeFromFilename(pathname, GENERIC_WRITE));

    // Initialize encoder with stream
    CheckError(sp_encoder->Initialize(sp_stream, WICBitmapEncoderNoCache));

    // Create new frame
    IWICBitmapFrameEncodePtr sp_frame { nullptr };
    IPropertyBag2Ptr sp_properties { nullptr };
    CheckError(sp_encoder->CreateNewFrame(&sp_frame, &sp_properties));

    // Initialize frame with default properties
    CheckError(sp_frame->Initialize(sp_properties));

    // Set pixel format
    // SetPixelFormat() requires a pointer to non-const
    auto pf { GUID_WICPixelFormat1bppIndexed };
    CheckError(sp_frame->SetPixelFormat(&pf));
    if (!::IsEqualGUID(pf, GUID_WICPixelFormat1bppIndexed))
    {
        // Report unsupported pixel format
        CheckError(WINCODEC_ERR_UNSUPPORTEDPIXELFORMAT);
    }

    // Set size derived from data argument
    auto const width { static_cast<UINT>(data.size()) };
    auto const height { static_cast<UINT>(data[0].size()) };
    CheckError(sp_frame->SetSize(width, height));

    // Set palette on frame. This is required since we use an indexed pixel format.
    // Only GIF files support global palettes, so make sure to set it on the frame
    // rather than the encoder.
    IWICPalettePtr sp_palette { nullptr };
    CheckError(sp_factory->CreatePalette(&sp_palette));
    CheckError(sp_palette->InitializePredefined(WICBitmapPaletteTypeFixedBW, FALSE));
    CheckError(sp_frame->SetPalette(sp_palette));

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

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

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

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

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

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

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

#include <iostream>

int wmain(int argc, wchar_t* argv[])
try
{
    if (argc != 2)
    {
        return -1;
    }

    CheckError(::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED));


    // Create 64x64 matrix
    container data(64, std::vector<bool>(64, false));
    // Fill with arrow pointing towards the upper left
    for (size_t i { 0 }; i < data.size(); ++i)
    {
        data[0][i] = true;
        data[i][0] = true;
        data[i][i] = true;
    }
    ::write_bitmap(argv[1], data);


    ::CoUninitialize();
}
catch (_com_error const& e)
{
    std::wcout << L"Error!\n" << L"  Message: " << e.ErrorMessage() << std::endl;
}

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

Program output

浅黛梨妆こ 2024-09-05 08:04:20

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

请参阅此处的相关问题:为什么ofstream要在0x0A之前插入0x0D字节?

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

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

安静被遗忘 2024-09-05 08:04:20

这是一个简单的 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;
};

Here's a simple c++ bmp image file class.

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;
};
寻找一个思念的角度 2024-09-05 08:04:20

此代码使用了一些较新的 C++ 功能。我用它来创建 8 位和 24 位 bmp 文件。它只写入 bmp 文件,有一天我们也可以读取它们!

我不喜欢所有的移位和容易出错的字节序安全性。

它可以使用更多注释,但代码非常简单。所谓的运行时字节序检测会导致代码在我(不久前)测试的所有编译器上被优化。

endian_type.h >>> Endian 安全 POD 类型。

#ifndef ENDIAN_TYPE_H
#define ENDIAN_TYPE_H

#include <algorithm>
#include <type_traits>

namespace endian_type {

template <typename T, bool store_as_big_endian>
struct EndianType {
  using value_type = T;
  static_assert(std::is_fundamental_v<value_type>, 
      "EndianType works for fundamental data types");
  
  EndianType() = default;

  EndianType(const value_type& value)
    : value{ convert_to(value) } {}

  struct TypeAsBytes {
    unsigned char value[sizeof(value_type)];
  };

  static constexpr bool is_big_endian() {
    union { int ival; char cval; } uval;
    uval.ival = 1;
    return 0 == uval.cval;
  }

  static TypeAsBytes convert_to(const value_type& ivalue) {
    TypeAsBytes ovalue;
    const unsigned char* p_ivalue = (const unsigned char*)&ivalue;
    if (store_as_big_endian != is_big_endian()) {
      std::reverse_copy(p_ivalue, p_ivalue + sizeof(value_type), ovalue.value);
    } else {
      std::copy(p_ivalue, p_ivalue + sizeof(value_type), ovalue.value);
    }
    return ovalue;
  }

  static value_type convert_from(const TypeAsBytes& ivalue) {
    value_type ovalue;
    unsigned char* p_ovalue = (unsigned char*) &ovalue;
    const unsigned char* p_ivalue = (const unsigned char*)&ivalue;
    if (store_as_big_endian != is_big_endian()) {
      std::reverse_copy(p_ivalue, p_ivalue + sizeof(value_type), p_ovalue);
    }
    else {
      std::copy(p_ivalue, p_ivalue + sizeof(value_type), p_ovalue);
    }
    return ovalue;
  }

  value_type get() const {
    return convert_from(value);
  }

  EndianType& set(const value_type& ivalue) {
    value = convert_to(ivalue);
    return *this;
  }

  operator value_type() const {
    return get();
  }

  EndianType& operator=(const value_type& ivalue) {
    set(ivalue);
    return *this;
  }

private:
  TypeAsBytes value;
};

template <typename T>
using BigEndian = EndianType<T, true>;

template <typename T>
using LittleEndian = EndianType<T, false>;

}  // namespace endian_type
#endif  // ENDIAN_TYPE_H

以下包含 write_bmp 函数。

bmp_writer.h>> BMP writer header

#ifndef BMP_WRITER
#define BMP_WRITER

#include "endian_type.h"

#include <cctype>
#include <vector>
#include <fstream>

namespace bmp_writer {

template <typename T>
using LittleEndian = endian_type::LittleEndian<T>;

struct Header {
  char magic[2]{ 'B', 'M' };
  LittleEndian<std::uint32_t> size;
  LittleEndian<std::uint16_t> app_data1;
  LittleEndian<std::uint16_t> app_data2;
  LittleEndian<std::uint32_t> offset;
};

struct Info {
  LittleEndian<std::uint32_t> info_size{ 40 };
  LittleEndian<std::uint32_t> width;
  LittleEndian<std::uint32_t> height;
  LittleEndian<std::uint16_t> count_colour_planes{ 1 };
  LittleEndian<std::uint16_t> bits_per_pixel;
  LittleEndian<std::uint32_t> compression{};
  LittleEndian<std::uint32_t> image_bytes_size;
  LittleEndian<std::uint32_t> resolution_horizontal{ 2835 };
  LittleEndian<std::uint32_t> resolution_vertical{ 2835 };
  LittleEndian<std::uint32_t> count_pallete_entries{ 0 };
  LittleEndian<std::uint32_t> important_colours{ 0 };
};

template <std::size_t count>
class Palette {
public:
  static constexpr std::uint32_t NUM_CHANNELS = 4;
  using Entry = std::uint8_t[NUM_CHANNELS];
private:
  Palette() {
    for (auto i = 0; i < count; ++i) {
      auto& entry = table[i];
      for (auto j = 0; j < NUM_CHANNELS - 1; ++j) {
        entry[j] = i;
      }
    }
  }

  Palette(const Palette&) = delete;
  Palette(const Palette&&) = delete;
  Palette& operator=(const Palette&) = delete;
  Palette& operator=(const Palette&&) = delete;

public:
  static const Palette& get() {
    static const Palette palette;
    return palette;
  }

  Entry table[count];
};


static_assert(sizeof(Info) == 40, "");

template <typename T>
void write_bmp(
  std::ofstream& out,
  std::uint32_t width,
  std::uint32_t height,
  std::uint16_t count_colour_planes,
  const T* data,
  std::uint32_t data_size
) {

  auto& palette = Palette<256>::get();

  Header header;

  Info info;
  info.width = width;
  info.height = height;
  //info.count_colour_planes = count_colour_planes;

  const std::uint32_t t_per_pixel = data_size / (width * height);
  info.bits_per_pixel = std::uint16_t(sizeof(T) * 8 * t_per_pixel);
  const std::uint32_t row_len = width * sizeof(T) * t_per_pixel;
  // Round row up to next multiple of 4.
  const std::uint32_t padded_row_len = (row_len + 3) & ~3u;
  const std::uint32_t data_size_bytes = padded_row_len * height;
  info.image_bytes_size = data_size_bytes;

  if (count_colour_planes == 1) {
    header.offset = sizeof(Info) + sizeof(Header) + sizeof(palette);
  } else {
    header.offset = sizeof(Info) + sizeof(Header);
  }
  header.size = header.offset + height * padded_row_len;

  out.write(reinterpret_cast<const char*>(&header), sizeof(header));
  out.write(reinterpret_cast<const char*>(&info), sizeof(info));

  if (count_colour_planes == 1) {
    out.write(reinterpret_cast<const char*>(&palette), sizeof(palette));
  }

  const char padding[3] = {};
  for (int i = height; i > 0;) {
    --i;
    const char* p_row =
      reinterpret_cast<const char*>(data + i * width);
    out.write(p_row, row_len);
    if (padded_row_len != row_len) {
      out.write(padding, padded_row_len - row_len);
    }
  }

};


template <typename T>
void write_bmp(
  std::ofstream& out,
  std::uint32_t width,
  std::uint32_t height,
  std::uint16_t count_colour_planes,
  const std::vector<T>& data
) {
  write_bmp(out, width, height, count_colour_planes,
    &*data.cbegin(), data.size());
}

template <typename T>
void write_bmp(
  const std::string& outfilename,
  std::uint32_t width,
  std::uint32_t height,
  std::uint16_t count_colour_planes,
  const std::vector<T>& data
) {

  std::ofstream out{ outfilename, std::ios_base::binary };
  if (!out) {
    throw std::runtime_error("Failed to open: " + outfilename);
  }

  write_bmp(out, width, height, count_colour_planes,
    &*data.begin(), static_cast<std::uint32_t>(data.size()));

  out.close();
}

}  // namespace

#endif // BMP_WRITER

和使用示例:

#include "bmp_writer.h"

struct PixelType {
  PixelType(std::uint8_t r, std::uint8_t g, std::uint8_t b)
    : c{ b, g, r } {}

  PixelType(std::uint32_t c)
    : c{ (c >> 16) & 0xffu, (c >> 8) & 0xffu, c & 0xffu } {}

  PixelType() = default;

  std::uint8_t c[3] = {};
};

void bmp_writer_test1() {
  const int size_x = 20;
  const int size_y = 10;
  std::vector<PixelType> data(size_x * size_y);

  // Write some pixels.
  data[2] = PixelType(0xff0000);   // red
  data[10] = PixelType(0x00ff00);  // green

  bmp_writer::write_bmp(
    "test_bmp_writer1.bmp",
    std::uint32_t(size_x),
    std::uint32_t(size_y),
    std::uint16_t(sizeof(PixelType)),
    data
  );
}

void bmp_writer_test2() {
  const int size_x = 20;
  const int size_y = 10;
  PixelType data[size_x * size_y];

  // Write some pixels.
  data[15] = PixelType(0xff, 0, 0);  // red
  data[17] = PixelType(0, 0xff, 0);  // green

  std::ofstream out{ "test_bmp_writer2.bmp", std::ios_base::binary };
  if (!out) {
    throw std::runtime_error("Failed to open: "  "test_bmp_writer2.bmp");
  }
  bmp_writer::write_bmp(
    out,
    std::uint32_t(size_x),
    std::uint32_t(size_y),
    std::uint16_t(sizeof(PixelType)),
    data,
    sizeof(data) / sizeof PixelType
  );
}

This code uses some newer C++ features. I've used it to create 8bit and 24bit bmp files. It only writes bmp files, one day we may read them too!

I didn't like all the shifting and error proneess for endian safety.

It could use lots more comments but the code is pretty straight forward. The supposedly run-time detection of endianness results in code being optimized away on all the compilers I tested (a while ago).

endian_type.h >> Endian safe POD type.

#ifndef ENDIAN_TYPE_H
#define ENDIAN_TYPE_H

#include <algorithm>
#include <type_traits>

namespace endian_type {

template <typename T, bool store_as_big_endian>
struct EndianType {
  using value_type = T;
  static_assert(std::is_fundamental_v<value_type>, 
      "EndianType works for fundamental data types");
  
  EndianType() = default;

  EndianType(const value_type& value)
    : value{ convert_to(value) } {}

  struct TypeAsBytes {
    unsigned char value[sizeof(value_type)];
  };

  static constexpr bool is_big_endian() {
    union { int ival; char cval; } uval;
    uval.ival = 1;
    return 0 == uval.cval;
  }

  static TypeAsBytes convert_to(const value_type& ivalue) {
    TypeAsBytes ovalue;
    const unsigned char* p_ivalue = (const unsigned char*)&ivalue;
    if (store_as_big_endian != is_big_endian()) {
      std::reverse_copy(p_ivalue, p_ivalue + sizeof(value_type), ovalue.value);
    } else {
      std::copy(p_ivalue, p_ivalue + sizeof(value_type), ovalue.value);
    }
    return ovalue;
  }

  static value_type convert_from(const TypeAsBytes& ivalue) {
    value_type ovalue;
    unsigned char* p_ovalue = (unsigned char*) &ovalue;
    const unsigned char* p_ivalue = (const unsigned char*)&ivalue;
    if (store_as_big_endian != is_big_endian()) {
      std::reverse_copy(p_ivalue, p_ivalue + sizeof(value_type), p_ovalue);
    }
    else {
      std::copy(p_ivalue, p_ivalue + sizeof(value_type), p_ovalue);
    }
    return ovalue;
  }

  value_type get() const {
    return convert_from(value);
  }

  EndianType& set(const value_type& ivalue) {
    value = convert_to(ivalue);
    return *this;
  }

  operator value_type() const {
    return get();
  }

  EndianType& operator=(const value_type& ivalue) {
    set(ivalue);
    return *this;
  }

private:
  TypeAsBytes value;
};

template <typename T>
using BigEndian = EndianType<T, true>;

template <typename T>
using LittleEndian = EndianType<T, false>;

}  // namespace endian_type
#endif  // ENDIAN_TYPE_H

The following contains the write_bmp functions.

bmp_writer.h >> the BMP writer header

#ifndef BMP_WRITER
#define BMP_WRITER

#include "endian_type.h"

#include <cctype>
#include <vector>
#include <fstream>

namespace bmp_writer {

template <typename T>
using LittleEndian = endian_type::LittleEndian<T>;

struct Header {
  char magic[2]{ 'B', 'M' };
  LittleEndian<std::uint32_t> size;
  LittleEndian<std::uint16_t> app_data1;
  LittleEndian<std::uint16_t> app_data2;
  LittleEndian<std::uint32_t> offset;
};

struct Info {
  LittleEndian<std::uint32_t> info_size{ 40 };
  LittleEndian<std::uint32_t> width;
  LittleEndian<std::uint32_t> height;
  LittleEndian<std::uint16_t> count_colour_planes{ 1 };
  LittleEndian<std::uint16_t> bits_per_pixel;
  LittleEndian<std::uint32_t> compression{};
  LittleEndian<std::uint32_t> image_bytes_size;
  LittleEndian<std::uint32_t> resolution_horizontal{ 2835 };
  LittleEndian<std::uint32_t> resolution_vertical{ 2835 };
  LittleEndian<std::uint32_t> count_pallete_entries{ 0 };
  LittleEndian<std::uint32_t> important_colours{ 0 };
};

template <std::size_t count>
class Palette {
public:
  static constexpr std::uint32_t NUM_CHANNELS = 4;
  using Entry = std::uint8_t[NUM_CHANNELS];
private:
  Palette() {
    for (auto i = 0; i < count; ++i) {
      auto& entry = table[i];
      for (auto j = 0; j < NUM_CHANNELS - 1; ++j) {
        entry[j] = i;
      }
    }
  }

  Palette(const Palette&) = delete;
  Palette(const Palette&&) = delete;
  Palette& operator=(const Palette&) = delete;
  Palette& operator=(const Palette&&) = delete;

public:
  static const Palette& get() {
    static const Palette palette;
    return palette;
  }

  Entry table[count];
};


static_assert(sizeof(Info) == 40, "");

template <typename T>
void write_bmp(
  std::ofstream& out,
  std::uint32_t width,
  std::uint32_t height,
  std::uint16_t count_colour_planes,
  const T* data,
  std::uint32_t data_size
) {

  auto& palette = Palette<256>::get();

  Header header;

  Info info;
  info.width = width;
  info.height = height;
  //info.count_colour_planes = count_colour_planes;

  const std::uint32_t t_per_pixel = data_size / (width * height);
  info.bits_per_pixel = std::uint16_t(sizeof(T) * 8 * t_per_pixel);
  const std::uint32_t row_len = width * sizeof(T) * t_per_pixel;
  // Round row up to next multiple of 4.
  const std::uint32_t padded_row_len = (row_len + 3) & ~3u;
  const std::uint32_t data_size_bytes = padded_row_len * height;
  info.image_bytes_size = data_size_bytes;

  if (count_colour_planes == 1) {
    header.offset = sizeof(Info) + sizeof(Header) + sizeof(palette);
  } else {
    header.offset = sizeof(Info) + sizeof(Header);
  }
  header.size = header.offset + height * padded_row_len;

  out.write(reinterpret_cast<const char*>(&header), sizeof(header));
  out.write(reinterpret_cast<const char*>(&info), sizeof(info));

  if (count_colour_planes == 1) {
    out.write(reinterpret_cast<const char*>(&palette), sizeof(palette));
  }

  const char padding[3] = {};
  for (int i = height; i > 0;) {
    --i;
    const char* p_row =
      reinterpret_cast<const char*>(data + i * width);
    out.write(p_row, row_len);
    if (padded_row_len != row_len) {
      out.write(padding, padded_row_len - row_len);
    }
  }

};


template <typename T>
void write_bmp(
  std::ofstream& out,
  std::uint32_t width,
  std::uint32_t height,
  std::uint16_t count_colour_planes,
  const std::vector<T>& data
) {
  write_bmp(out, width, height, count_colour_planes,
    &*data.cbegin(), data.size());
}

template <typename T>
void write_bmp(
  const std::string& outfilename,
  std::uint32_t width,
  std::uint32_t height,
  std::uint16_t count_colour_planes,
  const std::vector<T>& data
) {

  std::ofstream out{ outfilename, std::ios_base::binary };
  if (!out) {
    throw std::runtime_error("Failed to open: " + outfilename);
  }

  write_bmp(out, width, height, count_colour_planes,
    &*data.begin(), static_cast<std::uint32_t>(data.size()));

  out.close();
}

}  // namespace

#endif // BMP_WRITER

And an example of use:

#include "bmp_writer.h"

struct PixelType {
  PixelType(std::uint8_t r, std::uint8_t g, std::uint8_t b)
    : c{ b, g, r } {}

  PixelType(std::uint32_t c)
    : c{ (c >> 16) & 0xffu, (c >> 8) & 0xffu, c & 0xffu } {}

  PixelType() = default;

  std::uint8_t c[3] = {};
};

void bmp_writer_test1() {
  const int size_x = 20;
  const int size_y = 10;
  std::vector<PixelType> data(size_x * size_y);

  // Write some pixels.
  data[2] = PixelType(0xff0000);   // red
  data[10] = PixelType(0x00ff00);  // green

  bmp_writer::write_bmp(
    "test_bmp_writer1.bmp",
    std::uint32_t(size_x),
    std::uint32_t(size_y),
    std::uint16_t(sizeof(PixelType)),
    data
  );
}

void bmp_writer_test2() {
  const int size_x = 20;
  const int size_y = 10;
  PixelType data[size_x * size_y];

  // Write some pixels.
  data[15] = PixelType(0xff, 0, 0);  // red
  data[17] = PixelType(0, 0xff, 0);  // green

  std::ofstream out{ "test_bmp_writer2.bmp", std::ios_base::binary };
  if (!out) {
    throw std::runtime_error("Failed to open: "  "test_bmp_writer2.bmp");
  }
  bmp_writer::write_bmp(
    out,
    std::uint32_t(size_x),
    std::uint32_t(size_y),
    std::uint16_t(sizeof(PixelType)),
    data,
    sizeof(data) / sizeof PixelType
  );
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文