8bpp BMP - 将像素引用到颜色 table;只想读取一行像素; C++

8bpp BMP - refering pixels to the color table; want to read only one row of pixels; C++

我在读取 8 位灰度 bmp 时遇到问题。我能够从 header 获取信息并读取调色板,但我无法将像素值引用到调色板条目。 Here 我已经找到了如何读取像素数据,但实际上并不知道如何在带有调色板的 bmp 的情况下使用它。我是初学者。我的目标是一次只读取一行像素。

代码:

#include <iostream>
#include <fstream>
using namespace std;

int main(int arc, char** argv)
{   const char* filename="Row_tst.bmp";
    remove("test.txt");
    ofstream out("test.txt",ios_base::app);//file for monitoring the results

    FILE* f = fopen(filename, "rb");
    unsigned char info[54];
    fread(info, sizeof(unsigned char), 54, f); // read the header

    int width = *(int*)&info[18];
    int height = *(int*)&info[22];

    unsigned char palette[1024]; //read the palette
    fread(palette, sizeof(unsigned char), 1024, f);
    for(int i=0;i<1024;i++)
    {   out<<"\n";
        out<<(int)palette[i];
    }

    int paletteSmall[256]; //1024-byte palette won't be needed in the future
    for(int i=0;i<256;i++)
    {   paletteSmall[i]=(int)palette[4*i];
        out<<paletteSmall[i]<<"\n";
    }

    int size = width;

    //for(int j=0;j<height;j++)
    {   unsigned char* data = new unsigned char[size];
        fread(data, sizeof(unsigned char), size, f);
        for(int i=0;i<width;i++) 
        {   cout<<"\n"<<i<<"\t"<<paletteSmall[*(int*)&data[i]];
        }
        delete [] data;
     }

    fclose(f);

    return 0;
}

我在 test.txt 中得到的内容看起来不错 - 第一个值从 0 0 0 0 到 255 255 255 0(调色板),下一个值从 0 到 255(paletteSmall)。

问题是我无法将像素值引用到颜色 table 条目。我的应用程序崩溃了,其症状可能表明它试图使用 table 中一些不存在的元素。如果我理解正确,bmp 中颜色为 table 的像素应该包含一些颜色 table 元素,所以我不知道为什么它不起作用。我请求你的帮助。

您强制将 8 位值读取为 int:

cout<<"\n"<<i<<"\t"<<paletteSmall[*(int*)&data[i]];

转换的数量表明您在这里遇到了问题,并且可能解决了一个接一个地添加转换直到 "it compiled"。事实证明,没有错误的编译与没有错误的工作是不同的。

这里发生的是你 强制 数据指针读取 4 个字节(或者与你的本地 int 大小一样多,无论如何)等等值几乎总是会超过 paletteSmall 的大小。 (另外,在所有情况下,最后一对值将无效,因为你读取的字节超出了data的有效范围。)

因为图像数据本身是8位的,这里只需要

cout<<"\n"<<i<<"\t"<<paletteSmall[data[i]];

无需强制转换; data 是一个 unsigned char * 所以它的值限制在 0 到 255 之间,paletteSmall 是正确的大小。


关于选角

casting 的问题在于,如果您告诉编译器将某种类型的值 当作 处理,编译器会报错这完全是另一种类型。通过使用强制转换,你告诉它 "Trust me. I know what I am doing."

如果您确实知道,这可能会导致几个问题:)

例如:你自己的一行

int width = *(int*)&info[18];

似乎有效,因为它 returns 正确的信息,但实际上这是一个快乐的意外。

数组 info 包含几个不相关的 unsigned char 值,您告诉编译器 一个 int 存储在起始位置#18 – 它信任你并读取一个整数。它假设 (1) 您想要组合成一个整数的字节数 实际上是它本身用于一个整数的字节数int (sizeof(int)),以及 (2) 各个字节的 order 与它在内部使用的相同 order (Endianness).

如果这些假设中的任何一个不成立,您会得到令人惊讶的结果;几乎肯定不是你想要的。

正确的过程是扫描 BMP file format 以了解 width 的值是如何存储的,然后使用该信息来获取您想要的数据。在这种情况下,width 是 "stored in little-endian format" 并且在偏移量 18 处为 4 个字节。有了它,您可以改用它:

int width = info[18]+(info[19]<<8)+(info[20]<<16)+(info[21]<<24);

不假设 int 有多大(除非它需要 至少 4 个字节),不假设顺序(移动值 'internally' 不依赖字节顺序)。

那么为什么它仍然有效(至少在您的计算机上)?在这十年中,int 最常见的大小是 4 个字节。最流行的 CPU 类型恰好以与它们在 BMP 中存储相同的顺序存储多字节值。将它们加在一起,你的代码在这十年内可以在大多数计算机上运行。一个快乐的意外。

如果您想在另一种类型的计算机(例如使用另一种字节顺序的嵌入式 ARM 系统)上编译您的代码,或者当使用编译器有一个更小的(.. 到现在应该是 very 旧的编译器)或更大的 int(再等 10 年左右),或者如果你想要调整您的代码以读取其他类型的文件(它们将具有自己的参数,使用的字节顺序就是其中之一)。