加载Targa文件C++时出现问题

Problems Loading Targa File C++

本文关键字:问题 C++ Targa 文件 加载      更新时间:2023-10-16

正如标题所示,我在加载.tga文件时遇到了问题。我一直在使用这个作为参考,据我所知(除了使用c++函数而不是c函数之外)我也在做同样的事情。我确实有一个纹理,但它是一个混乱的版本,我真的不知道为什么。请原谅所有的错误,我只是想让它发挥作用。

标题:

struct TGA
{
    GLuint Load(const char* filename);
    unsigned char header_[12];
    unsigned char bpp_;
    unsigned char id_;
    unsigned short width_;
    unsigned short height_;
    unsigned char* data_;
};

Cpp:

GLuint TGA::Load(const char* filename)
{
width_ = 0; height_ = 0;
bpp_ = 32; id_ = 8;
data_ = 0;
std::ifstream file;
file.open(filename, std::ios::binary | std::ios::ate);
if(file.fail())
{
    std::cout<<filename<<" could not be opened."<<std::endl;
    return 0;
}
file.seekg(0, std::ios::beg);
file.read((char*)header_, sizeof(unsigned char)*12);
file.read((char*)&width_, sizeof(unsigned short));
file.read((char*)&height_, sizeof(unsigned short));
file.read((char*)&bpp_, sizeof(unsigned char));
file.read((char*)&id_, sizeof(unsigned char));
int imageSize = width_ * height_;
std::cout<<imageSize<<std::endl;
data_ = new unsigned char[imageSize * 3];  //3 means RGB 
file.read((char*)data_, imageSize * 3);
file.close();
GLuint texture;
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width_, height_, 0, GL_BGRA, GL_UNSIGNED_BYTE, data_);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, data_);

delete [] data_;
return texture;
}

读取数据时,假设targa文件每像素有24位(file.read((char*)data_, imageSize * 3)),而不检查bpp_的值。然后,您将图像数据传递给OpenGL,并表示您的数据为32 bpp BGRA格式。

您应该检查bpp_的值,根据该值分配和读取数据,并将正确的数据格式传递给OpenGL(BGR或BGRA,取决于图像中是否包含alpha通道)。

是的,您正在做同样的事情:)

如果你看一下你用来引用的代码下面的注释,它读取的代码假设运行它的计算机是big-endian(像PS3)。我猜在这里,但你是在PC上运行的吗(它使用小端序)?

如果endian-ness错误,则所有大于一个字节的数据类型都会得到错误的值(height_/width_),您需要检查运行该程序的计算机是否使用大或小endian,并相应地更正这些值。

要将height_和width_转换为正确的端序,您应该能够简单地使用;ntohs和ntohl(网络字节顺序为big-endian)

height_ = ntohs(height_);
width_  = ntohs(width_);

首先让我注意到,您做的事情比最糟糕的方法要好得多(读取压缩结构)。对于一个健壮的文件解析器,总是逐段读取文件。这是避免漏洞和漏洞的唯一方法。

不幸的是,仍然有一些问题,但这些问题很容易解决:

struct TGA
{
    GLuint Load(const char* filename);
    unsigned char header_[12];
    unsigned char bpp_;
    unsigned char id_;
    unsigned short width_;
    unsigned short height_;

宽度和高度短可能成为一个问题。魔鬼在细节上,但我会及时解释的。目前,我们应该注意的是,在某些架构上,short实际上对我们来说可能太短了。为了安全起见,使用stdint.h中的uint16_t(C99标准的一部分),或者更好的是使用uint32_t。我们拭目以待。

    unsigned char* data_;
};
GLuint TGA::Load(const char* filename)
{
width_ = 0; height_ = 0;
bpp_ = 32; id_ = 8;
data_ = 0;

无论如何,我们都会覆盖它们,所以不需要清除它们,但也没有害处。

std::ifstream file;
file.open(filename, std::ios::binary | std::ios::ate);
if(file.fail())
{
    std::cout<<filename<<" could not be opened."<<std::endl;
    return 0;
}
file.seekg(0, std::ios::beg);
file.read((char*)header_, sizeof(unsigned char)*12);
file.read((char*)&width_, sizeof(unsigned short));
file.read((char*)&height_, sizeof(unsigned short));

好吧,这些有点问题,因为它们假设程序在一个小的endian机器上运行。假设你要为PowerPC开发(想想PlayStation 3、XBox或以big-endian模式运行的ARM),这段代码会崩溃。解决方案:

CCD_ 8,高度相同。如果在字符大小不是8的机器上运行,此代码仍然会出现问题,但目前这些问题很少。如果你想保险起见,可以在某个地方添加一个#if CHAR_BITS!=8 #error "char bits not 8" #endif

file.read((char*)&bpp_, sizeof(unsigned char));
file.read((char*)&id_, sizeof(unsigned char));

现在下面的行是错误的:

int imageSize = width_ * height_; // <<<<<<<<<

由于width_height_都是类型short,因此乘法将共同接收到宽度较短。C和C++的这种特殊行为是可利用的恶意bug的最大来源之一。假设我给你一张图片,其中标头被声明为width = height 2^15-1,这个数字很适合预期的16位。如果你把这两个相乘,强迫它变短,你得到…1。还有一个小问题。

std::cout<<imageSize<<std::endl;
data_ = new unsigned char[imageSize * 3];  //3 means RGB 
file.read((char*)data_, imageSize * 3);

好的方面是,您只读取通过代理变量分配的字节数,所以我不能将代码注入到您的程序中。然而,我可以崩溃它,因为稍后您的glTexImage2D调用将尝试从未分配的存储中读取(2^15-1)^2个字节,从而导致segfault。避免这种情况的诀窍是在计算中将单个变量强制转换为足够大的类型。或者,按照我的建议,使用uint32_t作为宽度和高度。我的编码规则是,每个变量都应该至少包含两倍于其中允许的最大值所需的位。不幸的是,C标准不能"固定"为语句的L类型的大小,因为这会破坏许多现有代码。

你遇到的另一个问题是,你对3个频道进行了硬编码。为什么?你有bpp头,它告诉你格式。因此,让我们更改一下:uint32_t imageSize = (uint_32)width_ * height_ * bpp_/8;。请注意,只需要将表达式中的一个变量强制转换为较大的类型。但是,不要犯这个错误:(uint32_t)(width_ * height_ * bpp_/8)会将短类型的结果强制转换为uint32_t,请注意括号。

最后但同样重要的是,我们可以将其传递给glTexImage2D或gluBuildMipMap2D。我强烈建议不要使用gluBuildMipmap,因为它会将你的纹理转换为2维幂,这在OpenGL-2之后就不再需要了。而是调用glGenerateMipmap。格式参数不应该是通道的数量,而是GL_RED、GL_RG、GL_RGB、GL_RGBA的令牌。因此,使用一个小的switch语句:

GLenum internal_format;
GLenum format;
GLenum buffer_format;
switch(bpp_) {
case 8:
    internal_format = GL_R8;
    format = GL_RED;
    buffer_format = GL_UNSIGNED_BYTE;
    break;
case 16:
    internal_format = GL_RGB5;
    format = GL_RGB;
    buffer_format = GL_UNSIGNED_SHORT_5_6_5;
    break;
case 24:
    internal_format = GL_RGB8;
    format = RGB;
    buffer_format = GL_UNSIGNED_BYTE;
    break;
case 32:
    internal_format = GL_RGBA8;
    format = RGB;
    buffer_format = GL_UNSIGNED_BYTE;
    break;
default:
    format_unsupported_error(); return;
}
glTexImage2D(..., internal_format, ..., internal_format, format, ...);