【问题标题】:C++: Convert text file of integers into a bitmap image file in BMP formatC++:将整数的文本文件转换为 BMP 格式的位图图像文件
【发布时间】:2012-08-25 09:03:53
【问题描述】:

我有一个文本文件由包含二维矩阵的矩阵库保存:

1 0 0 
6 0 4
0 1 1

每个数字都用一个彩色像素表示。我正在寻找一些关于如何解决这个问题的见解。如果需要更多信息,请随时询问。

编辑:我尝试过的另一种方法是:fwrite(&intmatrix, size,1, bmp_ptr); 我传入矩阵指针的位置,它似乎不会输出可读的 BMP 文件。 size 的值当然是rows*cols,矩阵的类型是arma::Mat<int>,这是一个来自犰狳线性代数库的矩阵。

编辑二:阅读 this 表明如果我没记错的话,考虑到行的大小,我的大小可能应该是 rows*cols*4,关于这一点的任何指导都会很棒。

【问题讨论】:

  • 我建议你看看这个相关的问题:Writing BMP image in pure c/c++ without other libraries...
  • 尝试获取一些图像格式库并为其提供单个像素?
  • 感谢@EitanT,但是该代码完全没有注释,并且遵循程序员的逻辑被证明是乏味的。不过,我正在努力解决,谢谢。
  • 我不清楚你拥有什么以及你想要做什么。你能澄清一下这个问题吗?
  • 当然,本质上我是在尝试将二维数组(或矩阵)转换为位图图像。在概念上与 cplusplus.com/forum/beginner/12848 所做的相同,但是绝对必须从文本文件中读取这些值。

标签: c++ image bmp armadillo


【解决方案1】:

要输出可读的BMP文件,需要先放一个header:

#include <WinGDI.h>

DWORD dwSizeInBytes = rows*cols*4; // when your matrix contains RGBX data)

// fill in the headers
BITMAPFILEHEADER bmfh;
bmfh.bfType = 0x4D42; // 'BM'
bmfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwSizeInBytes;
bmfh.bfReserved1 = 0;
bmfh.bfReserved2 = 0;
bmfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

BITMAPINFOHEADER bmih;
bmih.biSize = sizeof(BITMAPINFOHEADER);
bmih.biWidth = cols;
bmih.biHeight = rows;
bmih.biPlanes = 1;
bmih.biBitCount = 32;
bmih.biCompression = BI_RGB;
bmih.biSizeImage = 0;
bmih.biXPelsPerMeter = 0;
bmih.biYPelsPerMeter = 0;
bmih.biClrUsed = 0;
bmih.biClrImportant = 0;

现在在你写你的颜色信息之前,只写位图标题

fwrite(&bmfh, sizeof(bmfh),1, bmp_ptr);
fwrite(&bmih, sizeof(bmih),1, bmp_ptr);

最后是颜色信息:

fwrite(&intmatrix, size, sizeof(int), bmp_ptr);

请注意,块大小是 sizeof(int),因为您的矩阵不包含单个字符,而是每个值的整数。根据矩阵的内容,将值转换为 COLORREF 值可能是个好主意(检查 RGB 宏,也可以在 WinGDI.h 中找到)

【讨论】:

  • 感谢您的意见,仍在处理您的回复。但是,如果矩阵像问题所问的那样存储在文本文件中怎么办?你会如何喂 intmatrix?
  • 我真的不知道 arma::Mat 是如何工作的,但我知道,有保存/加载方法,所以你可以写这样的东西:std::ifstream f("MYFILE.TXT", std::ios::in); intmatrix.load(f); 或 @987654326 @ 然后用intmatrix.reshape(2,3);调整矩阵大小
  • 你测试过这段代码吗?无论我输入什么矩阵值,我似乎都得到了一个奇异的结果 BMP:i.imgur.com/l4zXE.png
  • 如果输出值的范围很小(
  • 我没有使用 arma::Mat 测试代码,但 x 是一个简单的 int(并逐步通过调试器)。如果 arma 文档没有错,只需流式传输到 arma::Mat 即可。但是,您需要知道矩阵的尺寸(用于矩阵重塑)。在那之后,至少你的矩阵应该没问题。但是,如果您的矩阵仅包含较小的值,您的图像可能仍然没有。您可能希望将值转换为 COLORREF 值(如原始评论中所述)或使用 8 位颜色索引位图,如 @Ghost2 所假设的那样。
【解决方案2】:

这是一个应用程序,它生成一个随机整数的文本文件,读回它们,并将它们作为(大致正方形)每像素 32 位 .BMP 图像写入磁盘。

注意,我对原始文本文件的格式、数字范围等做了一些假设,但它们都记录在代码中。通过这个工作示例,您应该能够在必要时轻松调整它们。

// IntToBMP.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <cstdint>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <random>
#include <ctime>
#include <memory>

#pragma pack( push, 1 ) 
struct BMP
{
    BMP();
    struct
    {
        uint16_t ID;
        uint32_t fileSizeInBytes;
        uint16_t reserved1;
        uint16_t reserved2;
        uint32_t pixelArrayOffsetInBytes;
    } FileHeader;

    enum class CompressionMethod : uint32_t {   BI_RGB              = 0x00, 
                                                BI_RLE8             = 0x01,
                                                BI_RLE4             = 0x02,
                                                BI_BITFIELDS        = 0x03,
                                                BI_JPEG             = 0x04,
                                                BI_PNG              = 0x05,
                                                BI_ALPHABITFIELDS   = 0x06 };

    struct
    {
        uint32_t headerSizeInBytes;
        uint32_t bitmapWidthInPixels;
        uint32_t bitmapHeightInPixels;
        uint16_t colorPlaneCount;
        uint16_t bitsPerPixel;
        CompressionMethod compressionMethod;
        uint32_t bitmapSizeInBytes;
        int32_t horizontalResolutionInPixelsPerMeter;
        int32_t verticalResolutionInPixelsPerMeter;
        uint32_t paletteColorCount;
        uint32_t importantColorCount;
    } DIBHeader;
};
#pragma pack( pop )

BMP::BMP()
{
    //Initialized fields
    FileHeader.ID                                   = 0x4d42; // == 'BM' (little-endian)
    FileHeader.reserved1                            = 0;
    FileHeader.reserved2                            = 0;
    FileHeader.pixelArrayOffsetInBytes              = sizeof( FileHeader ) + sizeof( DIBHeader );
    DIBHeader.headerSizeInBytes                     = 40;
    DIBHeader.colorPlaneCount                       = 1;
    DIBHeader.bitsPerPixel                          = 32;
    DIBHeader.compressionMethod                     = CompressionMethod::BI_RGB;
    DIBHeader.horizontalResolutionInPixelsPerMeter  = 2835; // == 72 ppi
    DIBHeader.verticalResolutionInPixelsPerMeter    = 2835; // == 72 ppi
    DIBHeader.paletteColorCount                     = 0;
    DIBHeader.importantColorCount                   = 0;
}

void Exit( void )
{
    std::cout << "Press a key to exit...";
    std::getchar();

    exit( 0 );
}

void MakeIntegerFile( const std::string& integerFilename )
{
    const uint32_t intCount = 1 << 20; //Generate 1M (2^20) integers
    std::unique_ptr< int32_t[] > buffer( new int32_t[ intCount ] ); 

    std::mt19937 rng;
    uint32_t rngSeed = static_cast< uint32_t >( time( NULL ) );
    rng.seed( rngSeed );

    std::uniform_int_distribution< int32_t > dist( INT32_MIN, INT32_MAX );

    for( size_t i = 0; i < intCount; ++i )
    {
        buffer[ i ] = dist( rng );
    }

    std::ofstream writeFile( integerFilename, std::ofstream::binary );

    if( !writeFile )
    {
        std::cout << "Error writing " << integerFilename << ".\n";
        Exit();
    }

    writeFile << buffer[ 0 ];
    for( size_t i = 1; i < intCount; ++i )
    {
        writeFile << " " << buffer[ i ];
    }
}

int _tmain(int argc, _TCHAR* argv[])  //Replace with int main( int argc, char* argv[] ) if you're not under Visual Studio
{
    //Assumption: 32-bit signed integers
    //Assumption: Distribution of values range from INT32_MIN through INT32_MAX, inclusive
    //Assumption: number of integers contained in file are unknown
    //Assumption: source file of integers is a series of space-delimitied strings representing integers
    //Assumption: source file's contents are valid
    //Assumption: non-rectangular numbers of integers yield non-rectangular bitmaps (final scanline may be short)
    //            This may cause some .bmp parsers to fail; others may pad with 0's.  For simplicity, this implementation
    //            attempts to render square bitmaps.

    const std::string integerFilename = "integers.txt";
    const std::string bitmapFilename = "bitmap.bmp";

    std::cout << "Creating file of random integers...\n";
    MakeIntegerFile( integerFilename );

    std::vector< int32_t >integers; //If quantity of integers being read is known, reserve or resize vector or use array

    //Read integers from file
    std::cout << "Reading integers from file...\n";
    {   //Nested scope will release ifstream resource when no longer needed
        std::ifstream readFile( integerFilename );

        if( !readFile )
        {
            std::cout << "Error reading " << integerFilename << ".\n";
            Exit();
        }

        std::string number;
        while( readFile.good() )
        {
            std::getline( readFile, number, ' ' );
            integers.push_back( std::stoi( number ) );
        }

        if( integers.size() == 0 )
        {
            std::cout << "No integers read from " << integerFilename << ".\n";
            Exit();
        }
    }

    //Construct .bmp
    std::cout << "Constructing .BMP...\n";
    BMP bmp;
    size_t intCount = integers.size();
    bmp.DIBHeader.bitmapSizeInBytes = intCount * sizeof( integers[ 0 ] );
    bmp.FileHeader.fileSizeInBytes = bmp.FileHeader.pixelArrayOffsetInBytes + bmp.DIBHeader.bitmapSizeInBytes;
    bmp.DIBHeader.bitmapWidthInPixels = static_cast< uint32_t >( ceil( sqrt( intCount ) ) );
    bmp.DIBHeader.bitmapHeightInPixels = static_cast< uint32_t >( ceil( intCount / static_cast< float >( bmp.DIBHeader.bitmapWidthInPixels ) ) );

    //Write integers to .bmp file
    std::cout << "Writing .BMP...\n";
    {
        std::ofstream writeFile( bitmapFilename, std::ofstream::binary );

        if( !writeFile )
        {
            std::cout << "Error writing " << bitmapFilename << ".\n";
            Exit();
        }

        writeFile.write( reinterpret_cast< char * >( &bmp ), sizeof( bmp ) );
        writeFile.write( reinterpret_cast< char * >( &integers[ 0 ] ), bmp.DIBHeader.bitmapSizeInBytes );
    }

    //Exit
    Exit();
} 

希望这会有所帮助。

【讨论】:

    【解决方案3】:

    如果您选择正确的图像格式,这很容易。 PGM 有一个 ASCII 变体,看起来几乎与您的矩阵一模一样,但有一个标题。

    P2
    3 3
    6
    1 0 0 
    6 0 4
    0 1 1
    

    P2 是 ASCII PGM 的魔力,大小是 3x3,6 是 maxval。我选择 6 是因为这是您提供的最大值,它使 6 变成白色(而 0 是黑色)。在典型的 255 PGM 中,这与 8 位灰度图像一致。

    PPM 几乎一样简单,每个像素只有 3 个颜色分量,而不是 1 个。

    您可以使用任何需要 PPM(netpbm、ImageMagick、GIMP 等)的方式对这些图像进行操作。您可以将它们重新保存为与等效 BMP 大小基本相同的二进制 PPM。

    【讨论】:

    • 这对于动态生成简单图像非常有用,例如。 G。用于数据分析。然后我使用带有convert /tmp/foo.pgm /tmp/foo.png 的imagemagick 将其转换为PNG。
    【解决方案4】:

    您是否有问题将矩阵视为图像或从代码中写入图像?

    在前一种情况下,照本·杰克逊说的做

    在后一种情况下,您要传递 Arm::Matrix 的数据指针的地址,并且使用 fwrite 假定 Arm::Matrix 将其数据保存为连续的内存数组

    [编辑] 简要看一下犰狳 doc 还告诉我们数据是以列优先模式存储的,但 BMP 采用行优先模式,所以你的图像看起来会翻转

    [编辑2] 使用犰狳矩阵函数,更简单

    // assume A is a matrix
    // and maxVal is the maximum int value in you matrix (you might scale it to maxVal = 255)
    std::ofstream outfile("name.pgm");
    oufile << "P2 " << sd::endl << a.n_rows << " " << a.n_cols << std::endl << maxVal << std::endl;
    outfile << a << std::endl;
    outfile.close();
    

    【讨论】:

      【解决方案5】:

      我已经重写并评论了来自https://stackoverflow.com/a/2654860/586784 的答案。我希望你觉得它足够清楚。

      #include <cstddef>
      #include <armadillo>
      #include <map>
      #include <cstdio>
      #include <cassert>
      
      ///Just a tiny struct to bundle three values in range [0-255].
      struct Color{
        Color(unsigned char red, unsigned char green, unsigned char blue)
          : red(red),green(green),blue(blue)
        {}
      
        ///Defualt constructed Color() is black.
        Color()
          : red(0),green(0),blue(0)
        {}
      
        ///Each color is represented by a combination of red, green, and blue.
        unsigned char red,green,blue;
      };
      
      
      int main(int argc,char**argv)
      {
      
        ///The width of the image. Replace with your own.
        std::size_t w = 7;
        ///The height of the image. Replace with your own
        std::size_t h = 8;
      
        ///http://arma.sourceforge.net/docs.html#Mat
        ///The Armadillo Linear Algebra Library Mat constructor is of the following
        /// signature: mat(n_rows, n_cols).
        arma::Mat<int> intmatrix(h,w);
      
        ///Fill out matrix, replace this with your own.
        {
      
          ///Zero fill matrix
          for(std::size_t i=0; i<h; ++i)
            for(std::size_t j=0;j<w; ++j)
              intmatrix(i,j) = 0;
      
          intmatrix(0,3) = 1;
          intmatrix(1,3) = 1;
      
      
          intmatrix(2,2) = 6;
          intmatrix(2,4) = 6;
      
          intmatrix(3,2) = 4;
          intmatrix(3,4) = 4;
      
      
          intmatrix(4,1) = 6;
          intmatrix(4,2) = 6;
          intmatrix(4,3) = 6;
          intmatrix(4,4) = 6;
          intmatrix(4,5) = 6;
      
          intmatrix(5,1) = 1;
          intmatrix(5,2) = 1;
          intmatrix(5,3) = 1;
          intmatrix(5,4) = 1;
          intmatrix(5,5) = 1;
      
      
          intmatrix(6,0) = 4;
          intmatrix(6,6) = 4;
      
          intmatrix(7,0) = 6;
          intmatrix(7,6) = 6;
      
        }
      
      
        ///Integer to color associations. This is a map
        ///that records the meanings of the integers in the matrix.
        ///It associates a color with each integer.
        std::map<int,Color> int2color;
      
        ///Fill out the color associations. Replace this with your own associations.
        {
          ///When we see 0 in the matrix, we will use this color (red-ish).
          int2color[0] = Color(255,0,0);
          ///When we see 0 in the matrix, we will use this color (green-ish).
          int2color[1] = Color(0,255,0);
          ///When we see 0 in the matrix, we will use this color (blue-ish).
          int2color[4] = Color(0,0,255);
          ///When we see 0 in the matrix, we will use this color (grey-ish).
          int2color[6] = Color(60,60,60);
        }
      
      
        ///The file size will consist of w*h pixels, each pixel will have an RGB,
        /// where each color R,G,B is 1 byte, making the data part of the file to
        /// be of size 3*w*h. In addition there is a header to the file which will
        /// take of 54 bytes as we will see.
        std::size_t filesize = 54 + 3*w*h;
      
      
        ///We make an array of 14 bytes to represent one part of the header.
        ///It is filled out with some default values, and we will fill in the
        ///rest momentarily.
        unsigned char bmpfileheader[14] = {'B','M', 0,0,0,0, 0,0, 0,0, 54,0,0,0};
      
        ///The second part of the header is 40 bytes; again we fill it with some
        ///default values, and will fill in the rest soon.
        unsigned char bmpinfoheader[40] = {40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0, 24,0};
      
      
      
        ///We will now store the filesize,w,h into the header.
        ///We can't just write them to the file directly, because different platforms
        ///encode their integers in different ways. This is called "endianness"
        ///or "byte order". So we chop our integers up into bytes, and put them into
        ///the header byte-by-byte in the way we need to.
      
      
        ///Encode the least significant 8 bits of filesize into this byte.
        ///Because sizeof(unsigned char) is one byte, and one byte is eight bits,
        ///when filesize is casted to (unsigned char) only the least significant
        ///8 bits are kept and stored into the byte.
        bmpfileheader[ 2] = (unsigned char)(filesize    );
        ///... Now we shift filesize to the right 1 byte, meaning and trunctate
        ///that to its least significant 8 bits. This gets stored in the next
        ///byte.
        bmpfileheader[ 3] = (unsigned char)(filesize>> 8);
        ///...
        bmpfileheader[ 4] = (unsigned char)(filesize>>16);
        ///Encodes the most significant 8 bits of filesize into this byte.
        bmpfileheader[ 5] = (unsigned char)(filesize>>24);
      
        ///Now we will store w (the width of the image) in the same way,
        /// but into the byte [5-8] in bmpinfoheader.
        bmpinfoheader[ 4] = (unsigned char)(       w    );
        bmpinfoheader[ 5] = (unsigned char)(       w>> 8);
        bmpinfoheader[ 6] = (unsigned char)(       w>>16);
        bmpinfoheader[ 7] = (unsigned char)(       w>>24);
      
      
      
        ///Now we will store h (the width of the image) in the same way,
        /// but into the byte [9-12] in bmpinfoheader.
        bmpinfoheader[ 8] = (unsigned char)(       h    );
        bmpinfoheader[ 9] = (unsigned char)(       h>> 8);
        bmpinfoheader[10] = (unsigned char)(       h>>16);
        bmpinfoheader[11] = (unsigned char)(       h>>24);
      
        ///Now we open the output file
        FILE* f = fopen("img.bmp","wb");
      
        ///First write the bmpfileheader to the file. It is 14 bytes.
        ///The 1 means we are writing 14 elements of size 1.
        ///Remember, bmpfileheader is an array which is basically
        ///the same thing as saying it is a pointer to the first element
        ///in an array of contiguous elements. We can thus say:
        ///write 14 bytes, starting from the spot where bmpfileheader points
        ///to.
        fwrite(bmpfileheader,1,14,f);
        ///Then write the bmpinfoheader, which is 40 bytes, in the same way.
        fwrite(bmpinfoheader,1,40,f);
      
        ///Now we write the data.
        ///For each row (there are h rows), starting from the last, going
        ///up to the first.
        ///We iterate through the rows in reverse order here,
        ///apparently in the BMP format, the image
        ///is stored upside down.
        for(std::size_t i=h-1; i != std::size_t(-1); --i)
        {
          ///For each column in the row,
          for(std::size_t j=0; j<w; ++j)
          {
      
            ///We retreive the integer of the matrix at (i,j),
            ///and assert that there is a color defined for it.
            assert (int2color.count(intmatrix(i,j)) != 0
              && "Integer in matrix not defined in int2color map");
      
            ///We somehow get the color for pixel (i,j).
            ///In our case, we get it from the intmatrix, and looking
            ///up the integer's color.
            Color color = int2color[intmatrix(i,j)];
      
            ///Now the colors are written in reverse order: BGR
      
            ///We write the color using fwrite, by taking a pointer
            ///of the (unsigned char), which is the same thing as
            ///an array of length 1. Then we write the byte.
            ///First for blue,
            fwrite(&color.blue,1,1,f);
            ///Same for green,
            fwrite(&color.green,1,1,f);
            ///Finally red.
            fwrite(&color.red,1,1,f);
          }
      
      
          ///Now we do some padding, from 0-3 bytes, depending in the width.
          unsigned char bmppad[3] = {0,0,0};
          fwrite(bmppad,1,(4-(w*3)%4)%4,f);
        }
      
        ///Free the file.
        fclose(f);
      
      
        return 0;
      }
      

      【讨论】:

        猜你喜欢
        • 2020-02-10
        • 1970-01-01
        • 2013-05-12
        • 2021-09-02
        • 2012-04-16
        • 1970-01-01
        • 2012-07-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多