C++ 如何创建位图文件

Posted

技术标签:

【中文标题】C++ 如何创建位图文件【英文标题】:C++ How to create a bitmap file 【发布时间】:2013-09-16 22:46:04 【问题描述】:

我想弄清楚如何在 C++ VS 中创建位图文件。目前我已经接受了文件名并添加了“.bmp”扩展名来创建文件。我想知道如何通过将文件变成不同的颜色或图案(即像棋盘格)来更改文件的像素这是我拥有的功能,我相信我必须一次发送 3 个不同的字节为了建立像素的颜色。

void makeCheckerboardBMP(string fileName, int squaresize, int n) 
    ofstream ofs;
    ofs.open(fileName + ".bmp");
    writeHeader(ofs, n, n);

    for(int row = 0; row < n; row++) 
        for(int col = 0; col < n; col++) 

            if(col % 2 == 0) 
                ofs << 0;
                ofs << 0;
                ofs << 0;
             else 
                ofs << 255;
                ofs << 255;
                ofs << 255;
            
        
    


void writeHeader(ostream& out, int width, int height)
    if (width % 4 != 0) 
        cerr << "ERROR: There is a windows-imposed requirement on BMP that the width be a          
multiple of 4.\n";
        cerr << "Your width does not meet this requirement, hence this will fail.  You can fix     
this\n";
        cerr << "by increasing the width to a multiple of 4." << endl;
        exit(1);
    

    BITMAPFILEHEADER tWBFH;
    tWBFH.bfType = 0x4d42;
    tWBFH.bfSize = 14 + 40 + (width*height*3);
    tWBFH.bfReserved1 = 0;
    tWBFH.bfReserved2 = 0;
    tWBFH.bfOffBits = 14 + 40;

    BITMAPINFOHEADER tW2BH;
    memset(&tW2BH,0,40);
    tW2BH.biSize = 40;
    tW2BH.biWidth = width;
    tW2BH.biHeight = height;
    tW2BH.biPlanes = 1;
    tW2BH.biBitCount = 24;
    tW2BH.biCompression = 0;

    out.write((char*)(&tWBFH),14);
    out.write((char*)(&tW2BH),40);

【问题讨论】:

(1) 你关闭了流吗? (2)可以通过修改header来修改size(是header中的一个属性) @CommuSoft 当一个对象超出范围时,它的析构函数被调用。 ofstreams 析构函数调用close。所以是的,流确实关闭了。 是的,我确实关闭了流 @IInspectable:在某些编程语言中,您必须关闭流,我不会指望这种方法。由于您不知道何时调用析构函数,因此可能需要很长时间。保持文件打开并不是一个好主意,因为它可能会导致与其他程序发生冲突或导致操作系统在打开文件过多时终止程序。 :D @CommuSoft 问题被标记为c++;您对“一些编程语言” 的引用令人惊讶。在上面的代码中,您查看析构函数何时运行。 ofs 是本地范围的;寿命很容易猜到。至于“打开的文件太多”的说法:Get a real OS. 【参考方案1】:

这是我用于代码的两个函数(一个灰度,一个 RGB 保存)。

可能会提示您出了什么问题。 注意:它们是为了工作,而不是为了提高效率。

void SaveBitmapToFile( BYTE* pBitmapBits, LONG lWidth, LONG lHeight,WORD wBitsPerPixel, LPCTSTR lpszFileName )

    RGBQUAD palette[256];
    for(int i = 0; i < 256; ++i)
    
        palette[i].rgbBlue = (byte)i;
        palette[i].rgbGreen = (byte)i;
        palette[i].rgbRed = (byte)i;
    

    BITMAPINFOHEADER bmpInfoHeader = 0;
    // Set the size
    bmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
    // Bit count
    bmpInfoHeader.biBitCount = wBitsPerPixel;
    // Use all colors
    bmpInfoHeader.biClrImportant = 0;
    // Use as many colors according to bits per pixel
    bmpInfoHeader.biClrUsed = 0;
    // Store as un Compressed
    bmpInfoHeader.biCompression = BI_RGB;
    // Set the height in pixels
    bmpInfoHeader.biHeight = lHeight;
    // Width of the Image in pixels
    bmpInfoHeader.biWidth = lWidth;
    // Default number of planes
    bmpInfoHeader.biPlanes = 1;
    // Calculate the image size in bytes
    bmpInfoHeader.biSizeImage = lWidth* lHeight * (wBitsPerPixel/8);

    BITMAPFILEHEADER bfh = 0;
    // This value should be values of BM letters i.e 0x4D42
    // 0x4D = M 0×42 = B storing in reverse order to match with endian

    bfh.bfType = 'B'+('M' << 8);
    // <<8 used to shift ‘M’ to end

    // Offset to the RGBQUAD
    bfh.bfOffBits = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER) + sizeof(RGBQUAD) * 256;
    // Total size of image including size of headers
    bfh.bfSize = bfh.bfOffBits + bmpInfoHeader.biSizeImage;
    // Create the file in disk to write
    HANDLE hFile = CreateFile( lpszFileName,GENERIC_WRITE, 0,NULL,
        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,NULL);

    if( !hFile ) // return if error opening file
    
        return;
    

    DWORD dwWritten = 0;
    // Write the File header
    WriteFile( hFile, &bfh, sizeof(bfh), &dwWritten , NULL );
    // Write the bitmap info header
    WriteFile( hFile, &bmpInfoHeader, sizeof(bmpInfoHeader), &dwWritten, NULL );
    // Write the palette
    WriteFile( hFile, &palette[0], sizeof(RGBQUAD) * 256, &dwWritten, NULL );
    // Write the RGB Data
    if(lWidth%4 == 0)
    
        WriteFile( hFile, pBitmapBits, bmpInfoHeader.biSizeImage, &dwWritten, NULL );
    
    else
    
        char* empty = new char[ 4 - lWidth % 4];
        for(int i = 0; i < lHeight; ++i)
        
            WriteFile( hFile, &pBitmapBits[i * lWidth], lWidth, &dwWritten, NULL );
            WriteFile( hFile, empty,  4 - lWidth % 4, &dwWritten, NULL );
        
    
    // Close the file handle
    CloseHandle( hFile );


void SaveBitmapToFileColor( BYTE* pBitmapBits, LONG lWidth, LONG lHeight,WORD wBitsPerPixel, LPCTSTR lpszFileName )


    BITMAPINFOHEADER bmpInfoHeader = 0;
    // Set the size
    bmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
    // Bit count
    bmpInfoHeader.biBitCount = wBitsPerPixel;
    // Use all colors
    bmpInfoHeader.biClrImportant = 0;
    // Use as many colors according to bits per pixel
    bmpInfoHeader.biClrUsed = 0;
    // Store as un Compressed
    bmpInfoHeader.biCompression = BI_RGB;
    // Set the height in pixels
    bmpInfoHeader.biHeight = lHeight;
    // Width of the Image in pixels
    bmpInfoHeader.biWidth = lWidth;
    // Default number of planes
    bmpInfoHeader.biPlanes = 1;
    // Calculate the image size in bytes
    bmpInfoHeader.biSizeImage = lWidth* lHeight * (wBitsPerPixel/8);

    BITMAPFILEHEADER bfh = 0;
    // This value should be values of BM letters i.e 0x4D42
    // 0x4D = M 0×42 = B storing in reverse order to match with endian

    bfh.bfType = 'B'+('M' << 8);
    // <<8 used to shift ‘M’ to end

    // Offset to the RGBQUAD
    bfh.bfOffBits = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER);
    // Total size of image including size of headers
    bfh.bfSize = bfh.bfOffBits + bmpInfoHeader.biSizeImage;
    // Create the file in disk to write
    HANDLE hFile = CreateFile( lpszFileName,GENERIC_WRITE, 0,NULL,
        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,NULL);

    if( !hFile ) // return if error opening file
    
        return;
    

    DWORD dwWritten = 0;
    // Write the File header
    WriteFile( hFile, &bfh, sizeof(bfh), &dwWritten , NULL );
    // Write the bitmap info header
    WriteFile( hFile, &bmpInfoHeader, sizeof(bmpInfoHeader), &dwWritten, NULL );
    // Write the palette
    //WriteFile( hFile, &palette[0], sizeof(RGBQUAD) * 256, &dwWritten, NULL );
    // Write the RGB Data
    if(lWidth%4 == 0)
    
        WriteFile( hFile, pBitmapBits, bmpInfoHeader.biSizeImage, &dwWritten, NULL );
    
    else
    
        char* empty = new char[ 4 - lWidth % 4];
        for(int i = 0; i < lHeight; ++i)
        
            WriteFile( hFile, &pBitmapBits[i * lWidth], lWidth, &dwWritten, NULL );
            WriteFile( hFile, empty,  4 - lWidth % 4, &dwWritten, NULL );
        
    
    // Close the file handle
    CloseHandle( hFile );

【讨论】:

您的实现运行不可靠。您对biSizeImage 的计算没有考虑扫描线填充。对于未压缩的 RGB 图像,无论如何最好将其设置为 0。您对bfOffBits 的计算未能考虑对齐要求。请改用offsetof(BITMAPINFO, bmiColors[256])。您也在转储真正的随机字节(empty)。还有更多问题,它再次显示:不要自己动手。使用Windows Imaging Component - 它的存在是有原因的。【参考方案2】:

鉴于您的writeHeader 已正确实施,这几乎是正确的。不过,您需要解决 2 个问题:

    您正在为每个颜色通道写一个int。这应该是一个字节。您需要将文字转换为 unsigned char。 位图中的扫描线需要DWORD-aligned。在col 的内部循环之后,您需要写入额外的字节来解决这个问题,除非行的字节大小是四的倍数。

【讨论】:

非常感谢您的建议!!!将 int 更改为 unsigned char 有效。现在我遇到的下一个问题实际上是棋盘本身的大小。与我想要实际生成的位图图像相比,它实际上非常小。【参考方案3】:

您需要强制以 binary 格式而不是文本写入输出,当您打开文件/创建流并将所有值输出为字节而不是整数时,会选择此格式,这可以通过多种方式完成,可能最简单的方法是编写 chr(0)chr(255) - 您还需要以标题部分开始文件 - 有许多格式使得这太长而无法进入在这里回答 - 其中一些取决于偏好。 Wikipedia有很好的总结。

基本上,您必须告知接收应用程序您正在使用哪种格式、行数、列数以及颜色的存储方式。

【讨论】:

以上是关于C++ 如何创建位图文件的主要内容,如果未能解决你的问题,请参考以下文章

如何以编程方式创建位图?

C++如何把位图保存到数组中

使用 libjpeg / C++ 从文件中提取 JPEG 编码位图 (BMP) 图像

从 C++ 中的屏幕选择创建位图时出现黑色图像

为啥在 glfw 窗口上没有使用 opengl 显示位图图像?在 C++ 中读取位图图像文件时出现问题

如何使用 delphi 创建单色的 bmp 文件(位图)