加载Targa文件C ++的问题

时间:2012-01-28 17:01:15

标签: c++ opengl textures tga

正如标题所示,我在加载.tga文件时遇到了问题。我一直在使用this作为参考,据我所知(除了使用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;
}

3 个答案:

答案 0 :(得分:3)

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

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

答案 1 :(得分:1)

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

如果你查看下面用于参考的代码的注释,它会读取数据的代码,假设运行它的计算机是大端(如PS3)。我猜这里,但你是在PC上运行(使用小端)?

获取endian-ness错误会使所有数据类型大于一个字节得到错误的值(height_ / width_),您需要检查运行该程序的计算机是否使用big-little-endian并更正相应的价值观。

要将height_和width_转换为正确的字节序,您应该只需使用; ntohs and ntohl(网络字节顺序为大端)

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

答案 2 :(得分:1)

首先让我注意到你做的事情比那里最糟糕的方法更好(读入一个打包的结构)。对于健壮的文件解析器,始终一块一块地读取文件。这是避免漏洞和攻击的唯一方法。

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

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

    unsigned short width_;
    unsigned short height_;

宽度和高度短可能成为问题。魔鬼在细节,但我会及时解释。现在我们应该尊重一些架构short对我们来说实际上可能太短了。为了安全起见,请使用uint16_t({C}标准的一部分)中的stdint.h或更好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));

好的,这些都有点问题,因为他们假设程序在小端机器上运行。假设你要为PowerPC开发(想想PlayStation 3,XBox或以大端模式运行的ARM)这个代码会破坏。解决方案:

char buf[2]; file.read(buf, 2); width = buf[0] | buf[1]<<8;和高度相同。如果在char大小不是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 ++的这种特殊行为是令人讨厌,可利用的错误的最大来源之一。假设我给你一张标题为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个字节,从而导致段错误。避免这种情况的诀窍是将单个变量转换为计算中足够大的类型。或者你按我的建议做,并使用uint32_t宽度和高度。我按规则编码,每个变量应至少保留两倍于其中允许的最大值所需的位。不幸的是,C标准无法“修复”以强制转换为L类型语句的大小,因为这会破坏很多现有代码。

你遇到的另一个问题是你硬编码了3个频道。为什么?你有bpp-header,告诉你格式。所以让我们改变一下: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, ...);