Проблемы с загрузкой Targa File C ++ - PullRequest
1 голос
/ 28 января 2012

Как видно из названия, у меня были проблемы с загрузкой файлов .tga.Я использовал this для справки и, насколько я могу судить (кроме использования функций 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 ]

3 голосов
/ 28 января 2012

Когда вы читаете данные, вы предполагаете, что файл targa имеет 24 бита на пиксель (file.read((char*)data_, imageSize * 3)) без проверки значения bpp_.Затем вы передаете данные изображения в OpenGL и говорите, что ваши данные представлены в формате BGRA 32 бит / с.передать правильный формат данных в OpenGL (BGR или BGRA, в зависимости от того, включен ли альфа-канал в изображение или нет).

1 голос
/ 28 января 2012

Прежде всего, позвольте мне заметить, что вы делаете вещи лучше, чем худший метод (чтение в упакованной структуре).Для надежного анализатора файлов всегда читайте файл по частям.Это единственный способ избежать ошибок и эксплойтов.

К сожалению, все еще есть несколько проблем, но их легко исправить:

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 из stdint.h (часть стандарта 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));

Хорошо, этонемного проблематично, потому что они предполагают, что программа работает на машине с прямым порядком байтов.Скажем, вы должны были разработать для PowerPC (думаю, PlayStation 3, XBox или ARM, работающий в режиме с прямым порядком байтов), этот код сломался бы.Решение:

char buf[2]; file.read(buf, 2); width = buf[0] | buf[1]<<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 ++ является одним из крупнейших источников неприятных, эксплуатируемых ошибок.Скажем, я бы дал вам картину, в которой заголовок был объявлен 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-заголовок, который сообщает вам формат.Итак, давайте изменим это: 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, ...);
1 голос
/ 28 января 2012

Да, вы делаете то же самое :)

Если вы посмотрите на комментарии под кодом, который вы использовали для справки, код, который считывает данные, предполагает, что компьютер, на котором он работает, имеет большой порядок байтов (например, PS3). Я предполагаю здесь, но вы работаете на ПК (который использует little-endian)?

Из-за неправильного порядка байтов все ваши типы данных больше байта получат неправильные значения (height_ / width_), вам нужно будет проверить, использует ли компьютер, на котором работает программа, переменные с прямым или младшим порядком байтов и исправить значения соответственно.

Чтобы преобразовать height_ и width_ к правильному порядку байтов, вы должны просто использовать их; ntohs и ntohl (порядок байтов в сети с прямым порядком байтов)

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