在没有其他库的情况下用纯 c/c++ 编写 BMP 图像
Posted
技术标签:
【中文标题】在没有其他库的情况下用纯 c/c++ 编写 BMP 图像【英文标题】:Writing BMP image in pure c/c++ without other libraries 【发布时间】:2011-02-08 21:57:36 【问题描述】:在我的算法中,我需要创建一个信息输出。我需要将布尔矩阵写入 bmp 文件。 它必须是单色图像,如果此类元素上的矩阵为真,则像素为白色。 主要问题是bmp头以及如何编写。
【问题讨论】:
您可以查看qdbmp.sourceforge.net了解实现细节:)。 也许对访问者有用 谷歌搜索类似的概念是我几乎相关的问题和答案:***.com/questions/17918978/… 除非需要 BMP,否则***.com/questions/16636311/… 中讨论了更简单的格式 【参考方案1】:看看这是否适合你... 在这段代码中,我有 3 个二维数组,分别称为红色、绿色和蓝色。每个都是 [width][height] 大小,每个元素对应一个像素 - 我希望这是有道理的!
FILE *f;
unsigned char *img = NULL;
int filesize = 54 + 3*w*h; //w is your image width, h is image height, both int
img = (unsigned char *)malloc(3*w*h);
memset(img,0,3*w*h);
for(int i=0; i<w; i++)
for(int j=0; j<h; j++)
x=i; y=(h-1)-j;
r = red[i][j]*255;
g = green[i][j]*255;
b = blue[i][j]*255;
if (r > 255) r=255;
if (g > 255) g=255;
if (b > 255) b=255;
img[(x+y*w)*3+2] = (unsigned char)(r);
img[(x+y*w)*3+1] = (unsigned char)(g);
img[(x+y*w)*3+0] = (unsigned char)(b);
unsigned char bmpfileheader[14] = 'B','M', 0,0,0,0, 0,0, 0,0, 54,0,0,0;
unsigned char bmpinfoheader[40] = 40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0, 24,0;
unsigned char bmppad[3] = 0,0,0;
bmpfileheader[ 2] = (unsigned char)(filesize );
bmpfileheader[ 3] = (unsigned char)(filesize>> 8);
bmpfileheader[ 4] = (unsigned char)(filesize>>16);
bmpfileheader[ 5] = (unsigned char)(filesize>>24);
bmpinfoheader[ 4] = (unsigned char)( w );
bmpinfoheader[ 5] = (unsigned char)( w>> 8);
bmpinfoheader[ 6] = (unsigned char)( w>>16);
bmpinfoheader[ 7] = (unsigned char)( w>>24);
bmpinfoheader[ 8] = (unsigned char)( h );
bmpinfoheader[ 9] = (unsigned char)( h>> 8);
bmpinfoheader[10] = (unsigned char)( h>>16);
bmpinfoheader[11] = (unsigned char)( h>>24);
f = fopen("img.bmp","wb");
fwrite(bmpfileheader,1,14,f);
fwrite(bmpinfoheader,1,40,f);
for(int i=0; i<h; i++)
fwrite(img+(w*(h-i-1)*3),3,w,f);
fwrite(bmppad,1,(4-(w*3)%4)%4,f);
free(img);
fclose(f);
【讨论】:
嗯,cmets 不会受伤的。 我认为这段代码没有考虑 BITMAPFILEHEADER.bfSize 中的填充,因此在加载时,如果您使用它来计算位图数据的数组,则必须将填充添加到 bfSize。 我觉得yres应该换成hmemset(img,0,sizeof(img))
应该是……别的什么吗? memset(img,0,3*w*h)
?
这会写入一个 24bpp 的图像,它恰好只使用了 2^24 种可用颜色中的 2 种。这是对“单色”的创造性解释。真正的单色位图实际上只使用 1bpp。【参考方案2】:
用于位图 (BMP) 图像生成的清洁 C 代码
此代码不使用除 stdio.h 之外的任何库。因此,它可以很容易地融入 C 系列的其他语言中,如 C++、C#、Java。
#include <stdio.h>
const int BYTES_PER_PIXEL = 3; /// red, green, & blue
const int FILE_HEADER_SIZE = 14;
const int INFO_HEADER_SIZE = 40;
void generateBitmapImage(unsigned char* image, int height, int width, char* imageFileName);
unsigned char* createBitmapFileHeader(int height, int stride);
unsigned char* createBitmapInfoHeader(int height, int width);
int main ()
int height = 361;
int width = 867;
unsigned char image[height][width][BYTES_PER_PIXEL];
char* imageFileName = (char*) "bitmapImage.bmp";
int i, j;
for (i = 0; i < height; i++)
for (j = 0; j < width; j++)
image[i][j][2] = (unsigned char) ( i * 255 / height ); ///red
image[i][j][1] = (unsigned char) ( j * 255 / width ); ///green
image[i][j][0] = (unsigned char) ( (i+j) * 255 / (height+width) ); ///blue
generateBitmapImage((unsigned char*) image, height, width, imageFileName);
printf("Image generated!!");
void generateBitmapImage (unsigned char* image, int height, int width, char* imageFileName)
int widthInBytes = width * BYTES_PER_PIXEL;
unsigned char padding[3] = 0, 0, 0;
int paddingSize = (4 - (widthInBytes) % 4) % 4;
int stride = (widthInBytes) + paddingSize;
FILE* imageFile = fopen(imageFileName, "wb");
unsigned char* fileHeader = createBitmapFileHeader(height, stride);
fwrite(fileHeader, 1, FILE_HEADER_SIZE, imageFile);
unsigned char* infoHeader = createBitmapInfoHeader(height, width);
fwrite(infoHeader, 1, INFO_HEADER_SIZE, imageFile);
int i;
for (i = 0; i < height; i++)
fwrite(image + (i*widthInBytes), BYTES_PER_PIXEL, width, imageFile);
fwrite(padding, 1, paddingSize, imageFile);
fclose(imageFile);
unsigned char* createBitmapFileHeader (int height, int stride)
int fileSize = FILE_HEADER_SIZE + INFO_HEADER_SIZE + (stride * height);
static unsigned char fileHeader[] =
0,0, /// signature
0,0,0,0, /// image file size in bytes
0,0,0,0, /// reserved
0,0,0,0, /// start of pixel array
;
fileHeader[ 0] = (unsigned char)('B');
fileHeader[ 1] = (unsigned char)('M');
fileHeader[ 2] = (unsigned char)(fileSize );
fileHeader[ 3] = (unsigned char)(fileSize >> 8);
fileHeader[ 4] = (unsigned char)(fileSize >> 16);
fileHeader[ 5] = (unsigned char)(fileSize >> 24);
fileHeader[10] = (unsigned char)(FILE_HEADER_SIZE + INFO_HEADER_SIZE);
return fileHeader;
unsigned char* createBitmapInfoHeader (int height, int width)
static unsigned char infoHeader[] =
0,0,0,0, /// header size
0,0,0,0, /// image width
0,0,0,0, /// image height
0,0, /// number of color planes
0,0, /// bits per pixel
0,0,0,0, /// compression
0,0,0,0, /// image size
0,0,0,0, /// horizontal resolution
0,0,0,0, /// vertical resolution
0,0,0,0, /// colors in color table
0,0,0,0, /// important color count
;
infoHeader[ 0] = (unsigned char)(INFO_HEADER_SIZE);
infoHeader[ 4] = (unsigned char)(width );
infoHeader[ 5] = (unsigned char)(width >> 8);
infoHeader[ 6] = (unsigned char)(width >> 16);
infoHeader[ 7] = (unsigned char)(width >> 24);
infoHeader[ 8] = (unsigned char)(height );
infoHeader[ 9] = (unsigned char)(height >> 8);
infoHeader[10] = (unsigned char)(height >> 16);
infoHeader[11] = (unsigned char)(height >> 24);
infoHeader[12] = (unsigned char)(1);
infoHeader[14] = (unsigned char)(BYTES_PER_PIXEL*8);
return infoHeader;
【讨论】:
【参考方案3】:不使用任何其他库您可以查看BMP file format。我过去已经实现了它,无需太多工作即可完成。
位图文件结构
每个位图文件都包含一个 位图文件头,一个 位图信息头,一种颜色 表和一个字节数组 定义位图位。该文件有 以下形式:
BITMAPFILEHEADER bmfh; BITMAPINFOHEADER bmih; RGBQUAD aColors[]; 字节 aBitmapBits[];
...查看文件格式了解更多详情
【讨论】:
如果您的处理器不是 x86,请记住将所有内容都写成 little-endian。 一些硬件同时支持,操作系统可以决定使用哪种字节序。 很抱歉,当外部网站干涸并消失时,链接到外部网站是非常无用的。 链接已失效,here 是 Archive.org 的原始 url 副本。 如果 BMP 是单色的,只有黑色和 while 怎么办?我想把简单的单色文件转成C数组,这种情况怎么办?【参考方案4】:这是复制自的示例代码 https://en.wikipedia.org/wiki/User:Evercat/Buddhabrot.c
void drawbmp (char * filename)
unsigned int headers[13];
FILE * outfile;
int extrabytes;
int paddedsize;
int x; int y; int n;
int red, green, blue;
extrabytes = 4 - ((WIDTH * 3) % 4); // How many bytes of padding to add to each
// horizontal line - the size of which must
// be a multiple of 4 bytes.
if (extrabytes == 4)
extrabytes = 0;
paddedsize = ((WIDTH * 3) + extrabytes) * HEIGHT;
// Headers...
// Note that the "BM" identifier in bytes 0 and 1 is NOT included in these "headers".
headers[0] = paddedsize + 54; // bfSize (whole file size)
headers[1] = 0; // bfReserved (both)
headers[2] = 54; // bfOffbits
headers[3] = 40; // biSize
headers[4] = WIDTH; // biWidth
headers[5] = HEIGHT; // biHeight
// Would have biPlanes and biBitCount in position 6, but they're shorts.
// It's easier to write them out separately (see below) than pretend
// they're a single int, especially with endian issues...
headers[7] = 0; // biCompression
headers[8] = paddedsize; // biSizeImage
headers[9] = 0; // biXPelsPerMeter
headers[10] = 0; // biYPelsPerMeter
headers[11] = 0; // biClrUsed
headers[12] = 0; // biClrImportant
outfile = fopen(filename, "wb");
//
// Headers begin...
// When printing ints and shorts, we write out 1 character at a time to avoid endian issues.
//
fprintf(outfile, "BM");
for (n = 0; n <= 5; n++)
fprintf(outfile, "%c", headers[n] & 0x000000FF);
fprintf(outfile, "%c", (headers[n] & 0x0000FF00) >> 8);
fprintf(outfile, "%c", (headers[n] & 0x00FF0000) >> 16);
fprintf(outfile, "%c", (headers[n] & (unsigned int) 0xFF000000) >> 24);
// These next 4 characters are for the biPlanes and biBitCount fields.
fprintf(outfile, "%c", 1);
fprintf(outfile, "%c", 0);
fprintf(outfile, "%c", 24);
fprintf(outfile, "%c", 0);
for (n = 7; n <= 12; n++)
fprintf(outfile, "%c", headers[n] & 0x000000FF);
fprintf(outfile, "%c", (headers[n] & 0x0000FF00) >> 8);
fprintf(outfile, "%c", (headers[n] & 0x00FF0000) >> 16);
fprintf(outfile, "%c", (headers[n] & (unsigned int) 0xFF000000) >> 24);
//
// Headers done, now write the data...
//
for (y = HEIGHT - 1; y >= 0; y--) // BMP image format is written from bottom to top...
for (x = 0; x <= WIDTH - 1; x++)
red = reduce(redcount[x][y] + COLOUR_OFFSET) * red_multiplier;
green = reduce(greencount[x][y] + COLOUR_OFFSET) * green_multiplier;
blue = reduce(bluecount[x][y] + COLOUR_OFFSET) * blue_multiplier;
if (red > 255) red = 255; if (red < 0) red = 0;
if (green > 255) green = 255; if (green < 0) green = 0;
if (blue > 255) blue = 255; if (blue < 0) blue = 0;
// Also, it's written in (b,g,r) format...
fprintf(outfile, "%c", blue);
fprintf(outfile, "%c", green);
fprintf(outfile, "%c", red);
if (extrabytes) // See above - BMP lines must be of lengths divisible by 4.
for (n = 1; n <= extrabytes; n++)
fprintf(outfile, "%c", 0);
fclose(outfile);
return;
drawbmp(filename);
【讨论】:
只有链接的答案不好,你应该提供一些关于答案的信息。【参考方案5】:请注意,这些行是从下到上保存的,而不是相反。
此外,扫描线的字节长度必须是四的倍数,您应该在行尾插入填充字节以确保这一点。
【讨论】:
你写的我什么都不懂。 别担心,一旦你真正尝试过实现这一点,你就会知道的。 @Ben,在测试中很容易错过四的倍数要求,因为大多数现实世界的图像已经是 4 宽的倍数。 @mark:单色图像必须是 32 的倍数,因为对齐要求以字节为单位,而不是像素。【参考方案6】:这是适用于我的代码的 C++ 变体。请注意,我必须更改大小计算以考虑行填充。
// mimeType = "image/bmp";
unsigned char file[14] =
'B','M', // magic
0,0,0,0, // size in bytes
0,0, // app data
0,0, // app data
40+14,0,0,0 // start of data offset
;
unsigned char info[40] =
40,0,0,0, // info hd size
0,0,0,0, // width
0,0,0,0, // heigth
1,0, // number color planes
24,0, // bits per pixel
0,0,0,0, // compression is none
0,0,0,0, // image bits size
0x13,0x0B,0,0, // horz resoluition in pixel / m
0x13,0x0B,0,0, // vert resolutions (0x03C3 = 96 dpi, 0x0B13 = 72 dpi)
0,0,0,0, // #colors in pallete
0,0,0,0, // #important colors
;
int w=waterfallWidth;
int h=waterfallHeight;
int padSize = (4-(w*3)%4)%4;
int sizeData = w*h*3 + h*padSize;
int sizeAll = sizeData + sizeof(file) + sizeof(info);
file[ 2] = (unsigned char)( sizeAll );
file[ 3] = (unsigned char)( sizeAll>> 8);
file[ 4] = (unsigned char)( sizeAll>>16);
file[ 5] = (unsigned char)( sizeAll>>24);
info[ 4] = (unsigned char)( w );
info[ 5] = (unsigned char)( w>> 8);
info[ 6] = (unsigned char)( w>>16);
info[ 7] = (unsigned char)( w>>24);
info[ 8] = (unsigned char)( h );
info[ 9] = (unsigned char)( h>> 8);
info[10] = (unsigned char)( h>>16);
info[11] = (unsigned char)( h>>24);
info[20] = (unsigned char)( sizeData );
info[21] = (unsigned char)( sizeData>> 8);
info[22] = (unsigned char)( sizeData>>16);
info[23] = (unsigned char)( sizeData>>24);
stream.write( (char*)file, sizeof(file) );
stream.write( (char*)info, sizeof(info) );
unsigned char pad[3] = 0,0,0;
for ( int y=0; y<h; y++ )
for ( int x=0; x<w; x++ )
long red = lround( 255.0 * waterfall[x][y] );
if ( red < 0 ) red=0;
if ( red > 255 ) red=255;
long green = red;
long blue = red;
unsigned char pixel[3];
pixel[0] = blue;
pixel[1] = green;
pixel[2] = red;
stream.write( (char*)pixel, 3 );
stream.write( (char*)pad, padSize );
【讨论】:
padsize 似乎不对;我认为应该是:int padSize = (4 - 3 * w % 4) % 4;
这里的瀑布[][] 是什么?【参考方案7】:
我编辑了 ralf 的 htp 代码,以便它可以编译(在 gcc 上,运行 ubuntu 16.04 lts)。这只是初始化变量的问题。
int w = 100; /* Put here what ever width you want */
int h = 100; /* Put here what ever height you want */
int red[w][h];
int green[w][h];
int blue[w][h];
FILE *f;
unsigned char *img = NULL;
int filesize = 54 + 3*w*h; //w is your image width, h is image height, both int
if( img )
free( img );
img = (unsigned char *)malloc(3*w*h);
memset(img,0,sizeof(img));
int x;
int y;
int r;
int g;
int b;
for(int i=0; i<w; i++)
for(int j=0; j<h; j++)
x=i; y=(h-1)-j;
r = red[i][j]*255;
g = green[i][j]*255;
b = blue[i][j]*255;
if (r > 255) r=255;
if (g > 255) g=255;
if (b > 255) b=255;
img[(x+y*w)*3+2] = (unsigned char)(r);
img[(x+y*w)*3+1] = (unsigned char)(g);
img[(x+y*w)*3+0] = (unsigned char)(b);
unsigned char bmpfileheader[14] = 'B','M', 0,0,0,0, 0,0, 0,0, 54,0,0,0;
unsigned char bmpinfoheader[40] = 40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0, 24,0;
unsigned char bmppad[3] = 0,0,0;
bmpfileheader[ 2] = (unsigned char)(filesize );
bmpfileheader[ 3] = (unsigned char)(filesize>> 8);
bmpfileheader[ 4] = (unsigned char)(filesize>>16);
bmpfileheader[ 5] = (unsigned char)(filesize>>24);
bmpinfoheader[ 4] = (unsigned char)( w );
bmpinfoheader[ 5] = (unsigned char)( w>> 8);
bmpinfoheader[ 6] = (unsigned char)( w>>16);
bmpinfoheader[ 7] = (unsigned char)( w>>24);
bmpinfoheader[ 8] = (unsigned char)( h );
bmpinfoheader[ 9] = (unsigned char)( h>> 8);
bmpinfoheader[10] = (unsigned char)( h>>16);
bmpinfoheader[11] = (unsigned char)( h>>24);
f = fopen("img.bmp","wb");
fwrite(bmpfileheader,1,14,f);
fwrite(bmpinfoheader,1,40,f);
for(int i=0; i<h; i++)
fwrite(img+(w*(h-i-1)*3),3,w,f);
fwrite(bmppad,1,(4-(w*3)%4)%4,f);
fclose(f);
【讨论】:
【参考方案8】:我只是想分享 Minhas Kamal 代码的改进版本,因为尽管它对于大多数应用程序来说都足够好,但我仍然遇到了一些问题。要记住两件非常重要的事情:
-
代码(在撰写本文时)在两个静态数组上调用 free()。这将导致您的程序崩溃。所以我把这些行注释掉了。
切勿假设像素数据的间距始终为 (Width*BytesPerPixel)。最好让用户指定音高值。示例:在 Direct3D 中操作资源时,永远不能保证 RowPitch 是正在使用的字节深度的偶数倍。这可能会导致生成的位图出错(尤其是在奇怪的分辨率下,例如 1366x768)。
下面,你可以看到我对他的代码的修改:
const int bytesPerPixel = 4; /// red, green, blue
const int fileHeaderSize = 14;
const int infoHeaderSize = 40;
void generateBitmapImage(unsigned char *image, int height, int width, int pitch, const char* imageFileName);
unsigned char* createBitmapFileHeader(int height, int width, int pitch, int paddingSize);
unsigned char* createBitmapInfoHeader(int height, int width);
void generateBitmapImage(unsigned char *image, int height, int width, int pitch, const char* imageFileName)
unsigned char padding[3] = 0, 0, 0 ;
int paddingSize = (4 - (/*width*bytesPerPixel*/ pitch) % 4) % 4;
unsigned char* fileHeader = createBitmapFileHeader(height, width, pitch, paddingSize);
unsigned char* infoHeader = createBitmapInfoHeader(height, width);
FILE* imageFile = fopen(imageFileName, "wb");
fwrite(fileHeader, 1, fileHeaderSize, imageFile);
fwrite(infoHeader, 1, infoHeaderSize, imageFile);
int i;
for (i = 0; i < height; i++)
fwrite(image + (i*pitch /*width*bytesPerPixel*/), bytesPerPixel, width, imageFile);
fwrite(padding, 1, paddingSize, imageFile);
fclose(imageFile);
//free(fileHeader);
//free(infoHeader);
unsigned char* createBitmapFileHeader(int height, int width, int pitch, int paddingSize)
int fileSize = fileHeaderSize + infoHeaderSize + (/*bytesPerPixel*width*/pitch + paddingSize) * height;
static unsigned char fileHeader[] =
0,0, /// signature
0,0,0,0, /// image file size in bytes
0,0,0,0, /// reserved
0,0,0,0, /// start of pixel array
;
fileHeader[0] = (unsigned char)('B');
fileHeader[1] = (unsigned char)('M');
fileHeader[2] = (unsigned char)(fileSize);
fileHeader[3] = (unsigned char)(fileSize >> 8);
fileHeader[4] = (unsigned char)(fileSize >> 16);
fileHeader[5] = (unsigned char)(fileSize >> 24);
fileHeader[10] = (unsigned char)(fileHeaderSize + infoHeaderSize);
return fileHeader;
unsigned char* createBitmapInfoHeader(int height, int width)
static unsigned char infoHeader[] =
0,0,0,0, /// header size
0,0,0,0, /// image width
0,0,0,0, /// image height
0,0, /// number of color planes
0,0, /// bits per pixel
0,0,0,0, /// compression
0,0,0,0, /// image size
0,0,0,0, /// horizontal resolution
0,0,0,0, /// vertical resolution
0,0,0,0, /// colors in color table
0,0,0,0, /// important color count
;
infoHeader[0] = (unsigned char)(infoHeaderSize);
infoHeader[4] = (unsigned char)(width);
infoHeader[5] = (unsigned char)(width >> 8);
infoHeader[6] = (unsigned char)(width >> 16);
infoHeader[7] = (unsigned char)(width >> 24);
infoHeader[8] = (unsigned char)(height);
infoHeader[9] = (unsigned char)(height >> 8);
infoHeader[10] = (unsigned char)(height >> 16);
infoHeader[11] = (unsigned char)(height >> 24);
infoHeader[12] = (unsigned char)(1);
infoHeader[14] = (unsigned char)(bytesPerPixel * 8);
return infoHeader;
【讨论】:
我真的非常感谢指出我的错误(我在大学时的代码)并做出了这些改进。我会更新的。【参考方案9】:如果您使用上述 C++ 函数在图像中间出现奇怪的颜色切换。一定要以二进制模式打开外流:
imgFile.open(filename, std::ios_base::out | std::ios_base::binary);
否则windows会在你的文件中间插入不需要的字符! (在这个问题上已经敲了好几个小时)
在此处查看相关问题:Why does ofstream insert a 0x0D byte before 0x0A?
【讨论】:
【参考方案10】:最好的位图编码器不是您自己编写的。文件格式比人们预期的要复杂得多。事实证明,所有建议的答案都不会创建单色 (1bpp) 位图,而是写出 24bpp 文件,而这些文件恰好只使用 2 种颜色。
以下是仅适用于 Windows 的解决方案,使用 Windows Imaging Component。它不依赖于任何外部/第 3 方库,除了 Windows 附带的库。
像每个 C++ 程序一样,我们需要包含几个头文件。并在我们使用它时链接到 Windowscodecs.lib:
#include <Windows.h>
#include <comdef.h>
#include <comip.h>
#include <comutil.h>
#include <wincodec.h>
#include <vector>
#pragma comment(lib, "Windowscodecs.lib")
接下来,我们声明我们的容器(一个向量,多个向量!属于bool
!),以及一些方便起见的智能指针:
using _com_util::CheckError;
using container = std::vector<std::vector<bool>>;
_COM_SMARTPTR_TYPEDEF(IWICImagingFactory, __uuidof(IWICImagingFactory));
_COM_SMARTPTR_TYPEDEF(IWICBitmapEncoder, __uuidof(IWICBitmapEncoder));
_COM_SMARTPTR_TYPEDEF(IWICBitmapFrameEncode, __uuidof(IWICBitmapFrameEncode));
_COM_SMARTPTR_TYPEDEF(IWICStream, __uuidof(IWICStream));
_COM_SMARTPTR_TYPEDEF(IWICPalette, __uuidof(IWICPalette));
所有这些都解决了,我们可以直接进入实施阶段。需要进行一些设置才能获得工厂、编码器、框架并准备好一切:
void write_bitmap(wchar_t const* pathname, container const& data)
// Create factory
IWICImagingFactoryPtr sp_factory nullptr ;
CheckError(sp_factory.CreateInstance(CLSID_WICImagingFactory, nullptr,
CLSCTX_INPROC_SERVER));
// Create encoder
IWICBitmapEncoderPtr sp_encoder nullptr ;
CheckError(sp_factory->CreateEncoder(GUID_ContainerFormatBmp, nullptr, &sp_encoder));
// Create stream
IWICStreamPtr sp_stream nullptr ;
CheckError(sp_factory->CreateStream(&sp_stream));
CheckError(sp_stream->InitializeFromFilename(pathname, GENERIC_WRITE));
// Initialize encoder with stream
CheckError(sp_encoder->Initialize(sp_stream, WICBitmapEncoderNoCache));
// Create new frame
IWICBitmapFrameEncodePtr sp_frame nullptr ;
IPropertyBag2Ptr sp_properties nullptr ;
CheckError(sp_encoder->CreateNewFrame(&sp_frame, &sp_properties));
// Initialize frame with default properties
CheckError(sp_frame->Initialize(sp_properties));
// Set pixel format
// SetPixelFormat() requires a pointer to non-const
auto pf GUID_WICPixelFormat1bppIndexed ;
CheckError(sp_frame->SetPixelFormat(&pf));
if (!::IsEqualGUID(pf, GUID_WICPixelFormat1bppIndexed))
// Report unsupported pixel format
CheckError(WINCODEC_ERR_UNSUPPORTEDPIXELFORMAT);
// Set size derived from data argument
auto const width static_cast<UINT>(data.size()) ;
auto const height static_cast<UINT>(data[0].size()) ;
CheckError(sp_frame->SetSize(width, height));
// Set palette on frame. This is required since we use an indexed pixel format.
// Only GIF files support global palettes, so make sure to set it on the frame
// rather than the encoder.
IWICPalettePtr sp_palette nullptr ;
CheckError(sp_factory->CreatePalette(&sp_palette));
CheckError(sp_palette->InitializePredefined(WICBitmapPaletteTypeFixedBW, FALSE));
CheckError(sp_frame->SetPalette(sp_palette));
此时一切都已设置好,并且我们有一个框架可以将数据转储到其中。对于 1bpp 文件,每个字节存储 8 个像素的信息。最左边的像素存储在 MSB 中,像素一直向下到最右边的像素存储在 LSB 中。
代码并不完全重要;当您替换输入的数据布局时,您将用适合您需要的任何内容替换它:
// Write data to frame
auto const stride (width * 1 + 7) / 8 ;
auto const size height * stride ;
std::vector<unsigned char> buffer(size, 127u);
// Convert data to match required layout. Each byte stores 8 pixels, with the
// MSB being the leftmost, the LSB the right-most.
for (size_t x 0 ; x < data.size(); ++x)
for (size_t y 0 ; y < data[x].size(); ++y)
auto shift x % 8 ;
auto mask 0x80 >> shift ;
auto bit mask * data[x][y] ;
auto& value buffer[y * stride + x / 8] ;
value &= ~mask;
value |= bit;
CheckError(sp_frame->WritePixels(height, stride,
static_cast<UINT>(buffer.size()), buffer.data()));
剩下的就是将更改提交到帧和编码器,最终将图像文件写入磁盘:
// Commit frame
CheckError(sp_frame->Commit());
// Commit image
CheckError(sp_encoder->Commit());
这是一个测试程序,将图像写入作为第一个命令行参数传递的文件:
#include <iostream>
int wmain(int argc, wchar_t* argv[])
try
if (argc != 2)
return -1;
CheckError(::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED));
// Create 64x64 matrix
container data(64, std::vector<bool>(64, false));
// Fill with arrow pointing towards the upper left
for (size_t i 0 ; i < data.size(); ++i)
data[0][i] = true;
data[i][0] = true;
data[i][i] = true;
::write_bitmap(argv[1], data);
::CoUninitialize();
catch (_com_error const& e)
std::wcout << L"Error!\n" << L" Message: " << e.ErrorMessage() << std::endl;
它产生以下图像(真正的 1bpp,大小为 574 字节):
【讨论】:
【参考方案11】:这是一个简单的 c++ bmp 图像文件类。
class bmp_img
public:
constexpr static int header_size = 14;
constexpr static int info_header_size = 40;
bmp_img(size_t width, size_t height, size_t bpp = 3) :
bytes_per_pixel bpp , image_px_width width , image_px_height height , row_width image_px_width * bytes_per_pixel ,
row_padding (4 - row_width % 4) % 4 , row_stride row_width + row_padding , file_size header_size + info_header_size + (image_px_height * row_stride) ,
image(image_px_height, std::vector<unsigned char>(row_width))
//header file type
file_header[0] = 'B';
file_header[1] = 'M';
//header file size info
file_header[2] = static_cast<unsigned char>(file_size);
file_header[3] = static_cast<unsigned char>(file_size >> 8);
file_header[4] = static_cast<unsigned char>(file_size >> 16);
file_header[5] = static_cast<unsigned char>(file_size >> 24);
//header offset to pixel data
file_header[10] = header_size + info_header_size;
//info header size
info_header[0] = info_header_size;
//info header image width
info_header[4] = static_cast<unsigned char>(image_px_width);
info_header[5] = static_cast<unsigned char>(image_px_width >> 8);
info_header[6] = static_cast<unsigned char>(image_px_width >> 16);
info_header[7] = static_cast<unsigned char>(image_px_width >> 24);
//info header image height
info_header[8] = static_cast<unsigned char>(image_px_height);
info_header[9] = static_cast<unsigned char>(image_px_height >> 8);
info_header[10] = static_cast<unsigned char>(image_px_height >> 16);
info_header[11] = static_cast<unsigned char>(image_px_height >> 24);
//info header planes
info_header[12] = 1;
//info header bits per pixel
info_header[14] = 8 * bytes_per_pixel;
size_t width() const
return image_px_width;
size_t height() const
return image_px_height;
void set_pixel(size_t x, size_t y, int r, int g, int b)
image[y][x * bytes_per_pixel + 2] = r;
image[y][x * bytes_per_pixel + 1] = g;
image[y][x * bytes_per_pixel + 0] = b;
void fill(int r, int g, int b)
for (int y = 0; y < image_px_height; ++y)
for (int x = 0; x < image_px_width; ++x)
set_pixel(x, y, r, g, b);
void write_to_file(const char* file_name) const
std::ofstream img_file(file_name, std::ios_base::binary | std::ios_base::out);
img_file.write((char*)file_header, header_size);
img_file.write((char*)info_header, info_header_size);
std::vector<char> allignment(row_padding);
for (int y = image_px_height - 1; y >= 0; --y)
img_file.write((char*)image[y].data(), row_width);
img_file.write(allignment.data(), row_padding);
img_file.close();
private:
size_t bytes_per_pixel;
size_t image_px_width;
size_t image_px_height;
size_t row_width;
size_t row_padding;
size_t row_stride;
size_t file_size;
unsigned char file_header[header_size] = 0 ;
unsigned char info_header[info_header_size] = 0 ;
std::vector<std::vector<unsigned char>> image;
;
【讨论】:
【参考方案12】:C++ 答案,灵活的 API,假设 little-endian 系统对其进行编码。请注意,这使用 bmp 原生 y 轴(底部为 0)。
#include <vector>
#include <fstream>
struct image
image(int width, int height)
: w(width), h(height), rgb(w * h * 3)
uint8_t & r(int x, int y) return rgb[(x + y*w)*3 + 2];
uint8_t & g(int x, int y) return rgb[(x + y*w)*3 + 1];
uint8_t & b(int x, int y) return rgb[(x + y*w)*3 + 0];
int w, h;
std::vector<uint8_t> rgb;
;
template<class Stream>
Stream & operator<<(Stream & out, image const& img)
uint32_t w = img.w, h = img.h;
uint32_t pad = w * -3 & 3;
uint32_t total = 54 + 3*w*h + pad*h;
uint32_t head[13] = total, 0, 54, 40, w, h, (24<<16)|1;
char const* rgb = (char const*)img.rgb.data();
out.write("BM", 2);
out.write((char*)head, 52);
for(uint32_t i=0 ; i<h ; i++)
out.write(rgb + (3 * w * i), 3 * w);
out.write((char*)&pad, pad);
return out;
int main()
image img(100, 100);
for(int x=0 ; x<100 ; x++)
for(int y=0 ; y<100 ; y++)
img.r(x,y) = x;
img.g(x,y) = y;
img.b(x,y) = 100-x;
std::ofstream("/tmp/out.bmp") << img;
【讨论】:
以上是关于在没有其他库的情况下用纯 c/c++ 编写 BMP 图像的主要内容,如果未能解决你的问题,请参考以下文章