c++ - 在 C++ 中读取单色位图需要每隔一行读取一次?

标签 c++ algorithm bitmap

首先,这不是重复的。我已经读过 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 操作系统

谢谢。

最佳答案

这两个线宽公式都不正确。

例如,对于 w = 1 , (w / 8 + (w / 8) % 4)结果为零。如果你乘以二,它仍然是零。 width = 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();
}


测试图片:
enter image description here
(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 << (7 - x % 8)这基本上也是你所做的。不知道为什么要这样设计。这一定有一些历史原因!

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

关于c++ - 在 C++ 中读取单色位图需要每隔一行读取一次?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49215933/

相关文章:

c++ - 从线程中获取 native 句柄?

arrays - 从二维数组中删除相互引用

sql - 未显示检索队列算法

algorithm - 将优先表转换为适合递归下降的语法?

android - 在 RelativeLayout 中使用 ImageView 重复图像

c++ - 提升 MSM : using state machine itself as implicit top level state

c++ - glMultMatrix/glLoadMatrix 比 glRotatef 或 glTranslatef 更有效?

c++ - 您将如何优化此矢量化的谐波和?

delphi - 以编程方式逐个像素地交换小位图(原始)的颜色

android - 无法解析方法 setImageBitmap