Прежде всего, позвольте мне заметить, что вы делаете вещи лучше, чем худший метод (чтение в упакованной структуре).Для надежного анализатора файлов всегда читайте файл по частям.Это единственный способ избежать ошибок и эксплойтов.
К сожалению, все еще есть несколько проблем, но их легко исправить:
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, ...);