【问题标题】:Reading a Monochrome Bitmap in C++ requires reading every other line?在 C++ 中读取单色位图需要每隔一行读取一次吗?
【发布时间】:2018-03-11 02:02:59
【问题描述】:

首先,这不是重复的。我已经阅读了Converting 1-bit bmp file to array in C/C++,我的问题是关于我在提供给我的公式中看到的不一致。

问题

我正在尝试读取在 MS Paint 中创建的 1 位位图图像。我使用了本网站上其他答案提供的代码,但我必须更改一些内容才能使其正常工作,我想了解原因,

变化1:lineSize必须加倍

原创

int lineSize = (w / 8 + (w / 8) % 4);

我的:

int lineSize = (w/ 8 + (w / 8) % 4) * 2;

更改 2:必须颠倒字节顺序

原文:

for(k = 0 ; k < 8 ; k++)
    ... (data[fpos] >> k ) & 1;

我的:

for (int k = 7; k >= 0; --k) {
    ... (data[rawPos] >> k) & 1;

完整代码

注意:此代码有效。与原作有一些变化,但核心读取部分是一样的。

vector<vector<int>> getBlackAndWhiteBmp(string filename) {
    BmpHeader head;
    ifstream f(filename, ios::binary);

    if (!f) {
        throw "Invalid file given";
    }

    int headSize = sizeof(BmpHeader);
    f.read((char*)&head, headSize);

    if (head.bitsPerPixel != 1) {
        f.close();
        throw "Invalid bitmap loaded";
    }

    int height = head.height;
    int width = head.width;
    
    // Lines are aligned on a 4-byte boundary
    int lineSize = (width / 8 + (width / 8) % 4) * 2;
    int fileSize = lineSize * height;

    vector<unsigned char> rawFile(fileSize);
    vector<vector<int>> img(head.height, vector<int>(width, -1));

    // Skip to where the actual image data is
    f.seekg(head.offset);

    // Read in all of the file
    f.read((char*)&rawFile[0], fileSize);

    // Decode the actual boolean values of the pixesl
    int row;
    int reverseRow; // Because bitmaps are stored bottom to top for some reason
    int columnByte;
    int columnBit;

    for (row = 0, reverseRow = height - 1; row < height; ++row, --reverseRow) {
        columnBit = 0;
        for (columnByte = 0; columnByte < ceil((width / 8.0)); ++columnByte) {
            int rawPos = (row * lineSize) + columnByte;

            for (int k = 7; k >= 0 && columnBit < width; --k, ++columnBit) {
                img[reverseRow][columnBit] = (rawFile[rawPos] >> k) & 1;
            }
        }
    }

    f.close();
    return img;
}

#pragma pack(1)
struct BmpHeader {
    char magic[2];          // 0-1
    uint32_t fileSize;      // 2-5
    uint32_t reserved;      // 6-9
    uint32_t offset;        // 10-13
    uint32_t headerSize;    // 14-17
    uint32_t width;         // 18-21
    uint32_t height;        // 22-25
    uint16_t bitsPerPixel;  // 26-27
    uint16_t bitDepth;      // 28-29
};
#pragma pack()

可能相关的信息:

  • 我使用的是 Visual Studio 2017
  • 我正在为 C++14 编译
  • 我使用的是 Windows 10 操作系统

谢谢。

【问题讨论】:

    标签: c++ algorithm bitmap


    【解决方案1】:

    这两个线条大小公式都不正确。

    例如,对于w = 1(w / 8 + (w / 8) % 4) 的结果为零。如果乘以 2,它仍然是零。宽度 = 1 时预计为 4。

    行大小(或每行字节数)的正确公式是

    ((w * bpp + 31) / 32) * 4 其中bpp 是每像素位数,在本例中为1

    巧合的是,对于一些较小的宽度值,这些值有时是相同的。

    另见 MSDN example:

    DWORD dwBmpSize = ((bmpScreen.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpScreen.bmHeight;
    

    此外,1 位图像有 2 个调色板条目,共 8 个字节。似乎您总是忽略调色板并假设 0 是黑色,1 是白色。

    你翻转位的部分是正确的,其他代码似乎不正确。

    假设我们有一个单字节 1000 0000 这意味着是单行,从 7 个零开始,以 1 结尾。

    您的代码对我来说有点令人困惑(但当您修复 linesize 时似乎还可以)。我写了自己的版本:

    void test(string filename)
    {
        BmpHeader head;
        ifstream f(filename, ios::binary);
        if(!f.good())
            return;
    
        int headsize = sizeof(BmpHeader);
        f.read((char*)&head, headsize);
    
        if(head.bitsPerPixel != 1) 
        {
            f.close();
            throw "Invalid bitmap loaded";
        }
    
        int height = head.height;
        int width = head.width;
    
        int bpp = 1;
        int linesize = ((width * bpp + 31) / 32) * 4;
        int filesize = linesize * height;
    
        vector<unsigned char> data(filesize);
    
        //read color table
        uint32_t color0;
        uint32_t color1;
        uint32_t colortable[2];
        f.seekg(54);
        f.read((char*)&colortable[0], 4);
        f.read((char*)&colortable[1], 4);
        printf("colortable: 0x%06X 0x%06X\n", colortable[0], colortable[1]);
    
        f.seekg(head.offset);
        f.read((char*)&data[0], filesize);
    
        for(int y = height - 1; y >= 0; y--)
        {
            for(int x = 0; x < width; x++)
            {
                int pos = y * linesize + x / 8;
                int bit = 1 << (7 - x % 8);
                int v = (data[pos] & bit) > 0;
                printf("%d", v);
            }
            printf("\n");
        }
    
        f.close();
    }
    


    测试图片:

    (33 x 20 单色位图)
    输出:
    colortable: 0x000000 0xFFFFFF
    000000000000000000000000000000000
    000001111111111111111111111111110
    000001111111111111111111111111110
    000001111111111111111111111111110
    000001111111111111111111111111110
    011111111111111111111111111111110
    011111111111111111111111111111110
    011111111111111111111111111111110
    011111111111111111111111111111110
    011111111111111111111111111111110
    011111111111111111111111111111110
    011111111111111111111111111111110
    011111111111111111111111111111110
    011111111111111111111111111111110
    011111111111111111111111111111110
    011111111111111111111111111111110
    011111111111111111111111111110010
    011111111111111111111111111110010
    011111111111111111111111111111110
    000000000000000000000000000000000
    

    注意上面代码中的这一行:

    int pos = y * linesize + x / 8;
    int bit = 1 << (7 - x % 8);
    int v = (data[pos] & bit) > 0;
    printf("%d", v);
    

    首先我把它写成

    int bit = 1 << (x % 8);
    

    但这显示位的顺序错误,所以我不得不更改为1 &lt;&lt; (7 - x % 8),这基本上也是你所做的。我不知道为什么它是这样设计的。一定有一些历史原因!

    (以上代码仅适用于小端机器)

    【讨论】:

    • 啊,这可以解释很多。奇怪的是这么多人都在用原来的那个。你能解释一下新版本吗?它有效,但我不明白在十六进制级别上跳过了所有其他位是什么。另外,对于调色板,这是否意味着图像可以是黄色和紫色,而不是黑色和白色?
    • 如果你有一个大小为 1 x 1 的位图,那么 with 应该是 4 字节,或 32 位。这意味着 31 位是额外的填充,必须被跳过。你是这个意思吗?调色板条目通常是黑白的。我进行了编辑以显示如何检索颜色。我也不知道为什么必须翻转这些位,但是您在这方面的方法是正确的。
    猜你喜欢
    • 1970-01-01
    • 2011-01-23
    • 2021-03-11
    • 1970-01-01
    • 2012-10-16
    • 1970-01-01
    • 2022-12-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多