// TGA loader, by Ingemar 2009, based on "tga.c" and some other sources. // Updated 2013 to use modern mipmapping // 130423: Added LoadTGATextureData // 130905: Fixed a bug in LoadTGATextureSimple // 140212: Fixed a bug in loading of uncompressed images. // 140520: Supports grayscale images (loads to red channel). // 141007: Added SaveTGA. (Moved from the obsolete LoadTGA2.) // 160302: Uses fopen_s on Windows, as suggested by Jesper Post. Should reduce warnings a bit. // 170220: Changed fopen_s to "rb". This made it fail for some textures. // 170331: Cleaned up a bit to remove warnings. // 170419: Fixed a bug that prevented monochrome images from loading. // 180216: calloc is better than malloc when you may have undefined data in parts of the texture! // 190416: Skipped testing the last four bytes of the 12-byte heading. That is the origin of the // image which is usually 0. It is now ignored. // 220218: Big change: Removed the power of 2. GPUs with that limitation are gone now. // This simplifies both the code and the interface. // NOTE: LoadTGA does NOT support all TGA variants! You may need to re-save your TGA // with different settings to find a suitable format. #include "LoadTGA.h" static bool gMipmap = true; // Note that turning mimpapping on and off refers to the loading stage only. // If you want to turn off mipmapping later, use // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); void LoadTGASetMipmapping(bool active) { gMipmap = active; } bool LoadTGATextureData(const char *filename, TextureData *texture) // Loads A TGA File Into Memory { GLuint i; GLubyte TGAuncompressedheader[12]={ 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Uncompressed TGA Header TGAcompressedheader[12]={ 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Compressed TGA Header TGAuncompressedbwheader[12]={ 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Uncompressed grayscale TGA Header TGAcompressedbwheader[12]={ 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Compressed grayscale TGA Header actualHeader[12], // Used To Compare TGA Header header[6]; // First 6 Useful Bytes From The Header GLuint bytesPerPixel, // Holds Number Of Bytes Per Pixel Used In The TGA File imageSize, // Used To Store The Image Size When Setting Aside Ram temp; // Temporary Variable long rowSize, stepSize, bytesRead; // long w, h; GLubyte *rowP; int err; GLubyte rle; GLuint b; long row, rowLimit; GLubyte pixelData[4]; // Nytt för flipping-stöd 111114 char flipped; long step; // It seems Windows/VS doesn't like fopen any more, but fopen_s is not on the others. FILE *file; #if defined(_WIN32) fopen_s(&file, filename, "rb"); #else file = fopen(filename, "rb"); // rw works everywhere except Windows? #endif // FILE *file = fopen(filename, "rb"); // Open The TGA File err = 0; if (file == NULL) err = 1; // Does File Even Exist? else if (fread(actualHeader, 1, sizeof(actualHeader), file) != sizeof(actualHeader)) err = 2; // Are There 12 Bytes To Read? else if ( // Edit 2019: Ignore the origin! (memcmp(TGAuncompressedheader, actualHeader, sizeof(TGAuncompressedheader)-4) != 0) && (memcmp(TGAcompressedheader, actualHeader, sizeof(TGAcompressedheader)-4) != 0) && (memcmp(TGAuncompressedbwheader, actualHeader, sizeof(TGAuncompressedheader)-4) != 0) && (memcmp(TGAcompressedbwheader, actualHeader, sizeof(TGAcompressedheader)-4) != 0) ) { err = 3; // Does The Header Match What We Want? for (i = 0; i < 12; i++) printf("%d ", actualHeader[i]); printf("\n"); } else if (fread(header, 1, sizeof(header), file) != sizeof(header)) err = 4; // If So Read Next 6 Header Bytes if (err != 0) { switch (err) { case 1: printf("could not open file %s\n", filename); break; case 2: printf("could not read header of %s\n", filename); break; case 3: printf("unsupported format in %s\n", filename); break; case 4: printf("could not read file %s\n", filename); break; } if(file == NULL) // Did The File Even Exist? *Added Jim Strong* return false; else { fclose(file); // If Anything Failed, Close The File return false; } } texture->width = header[1] * 256 + header[0]; // Determine The TGA Width (highbyte*256+lowbyte) texture->height = header[3] * 256 + header[2]; // Determine The TGA Height (highbyte*256+lowbyte) if (texture->width <= 0 || // Is The Width Less Than Or Equal To Zero texture->height <= 0 || // Is The Height Less Than Or Equal To Zero (header[4] != 24 && header[4] != 32 && header[4] != 8)) // Is The TGA 24 or 32 Bit? { fclose(file); // If Anything Failed, Close The File return false; } flipped = (header[5] & 32) != 0; // Testa om flipped // That old power of 2 is no longer needed! // w = 1; // while (w < texture->width) w = w << 1; // h = 1; // while (h < texture->height) h = h << 1; // texture->texWidth = (GLfloat)texture->width / w; // texture->texHeight = (GLfloat)texture->height / h; texture->bpp = header[4]; // Grab The TGA's Bits Per Pixel (24 or 32) bytesPerPixel = texture->bpp/8; // Divide By 8 To Get The Bytes Per Pixel imageSize = texture->width * texture->height * bytesPerPixel; // Calculate The Memory Required For The TGA Data rowSize = texture->width * bytesPerPixel; // Image memory per row stepSize = texture->width * bytesPerPixel; // Memory per row texture->imageData = (GLubyte *)calloc(1, imageSize); // Reserve Memory To Hold The TGA Data if (texture->imageData == NULL) // Does The Storage Memory Exist? { fclose(file); return false; } // Set rowP and step depending on direction // Inverted this 120131, since a texture came out wrong. // I am still not sure this is OK. if (flipped) { step = -stepSize; rowP = &texture->imageData[imageSize - stepSize]; row = 0 + (texture->height -1) * stepSize; } else { step = stepSize; rowP = &texture->imageData[0]; row = 0; } if (actualHeader[2] == 2 || actualHeader[2] == 3) // uncompressed { for (i = 0; i < texture->height; i++) { bytesRead = fread(rowP, 1, rowSize, file); rowP += step; if (bytesRead != rowSize) { free(texture->imageData); // If So, Release The Image Data fclose(file); // Close The File return false; // Return False } } } else { // compressed // row = 0 + (texture->height -1) * stepSize; i = row; rowLimit = row + rowSize; do { bytesRead = fread(&rle, 1, 1, file); if (rle < 128) { // rle+1 raw pixels bytesRead = fread(&texture->imageData[i], 1, (rle+1)*bytesPerPixel, file); i += bytesRead; if (bytesRead == 0) i = imageSize; } else { // range of rle-127 pixels with a color that follows bytesRead = fread(&pixelData, 1, bytesPerPixel, file); do { for (b = 0; b < bytesPerPixel; b++) texture->imageData[i+b] = pixelData[b]; i += bytesPerPixel; rle = rle - 1; } while (rle > 127); } if (i >= rowLimit) { row = row + step; // - stepSize; rowLimit = row + rowSize; i = row; } } while (i < imageSize); } if (bytesPerPixel >= 3) // if not monochrome for (i = 0; i < imageSize; i += bytesPerPixel) // Loop Through The Image Data { // Swaps The 1st And 3rd Bytes ('R'ed and 'B'lue) temp = texture->imageData[i]; // Temporarily Store The Value At Image Data 'i' texture->imageData[i] = texture->imageData[i + 2]; // Set The 1st Byte To The Value Of The 3rd Byte texture->imageData[i + 2] = temp; // Set The 3rd Byte To The Value In 'temp' (1st Byte Value) } fclose (file); //texture->w = w; //texture->h = h; return true; // Texture loading Went Ok, Return True } bool LoadTGATexture(const char *filename, TextureData *texture) // Loads A TGA File Into Memory and creates texture object { char ok; GLuint type = GL_RGBA; // Set The Default GL Mode To RBGA (32 BPP) ok = LoadTGATextureData(filename, texture); // Loads A TGA File Into Memory if (!ok) return false; // Build A Texture From The Data glGenTextures(1, &texture->texID); // Generate OpenGL texture IDs glBindTexture(GL_TEXTURE_2D, texture->texID); // Bind Our Texture glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // Linear Filtered glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Linear Filtered if (texture->bpp == 8) // Was The TGA 8 Bits? Should be grayscale then. { type=GL_RED; // If So Set The 'type' To GL_RED } if (texture->bpp == 24) // Was The TGA 24 Bits? { type=GL_RGB; // If So Set The 'type' To GL_RGB } glTexImage2D(GL_TEXTURE_2D, 0, type, texture->width, texture->height, 0, type, GL_UNSIGNED_BYTE, texture[0].imageData); if (gMipmap) { glGenerateMipmap(GL_TEXTURE_2D); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // Linear Filtered } return true; // Texture Building Went Ok, Return True } void LoadTGATextureSimple(const char *filename, GLuint *tex) // If you really only need the texture object. { TextureData texture; memset(&texture, 0, sizeof(texture)); // Bug fix 130905. if (LoadTGATexture(filename, &texture)) { if(texture.imageData != NULL) free(texture.imageData); *tex = texture.texID; } else *tex = 0; } // saves an array of pixels as a TGA image // Was tgaSave, found in some reusable code. // Limitation: Can NOT save with transparency! Only RGB, not RGBA! // But it should! Why not? int SaveDataToTGA(char *filename, short int width, short int height, unsigned char pixelDepth, unsigned char *imageData) { unsigned char cGarbage = 0, mode,aux; int i, w, ix; FILE *file; char /*GLubyte*/ TGAuncompressedheader[12]={ 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Uncompressed TGA Header // open file and check for errors #if defined(_WIN32) fopen_s(&file, filename, "w"); #else file = fopen(filename, "w"); // rw works everywhere except Windows? #endif // file = fopen(filename, "w"); if (file == NULL) return(TGA_ERROR_FILE_OPEN); // compute image type: 2 for RGB(A), 3 for greyscale mode = pixelDepth / 8; // NOT YET IMPLEMENTED // if ((pixelDepth == 24) || (pixelDepth == 32)) // type = 2; // else // type = 3; // write the header fwrite(&TGAuncompressedheader, 12, 1, file); fwrite(&width, sizeof(short int), 1, file); fwrite(&height, sizeof(short int), 1, file); fwrite(&pixelDepth, sizeof(unsigned char), 1, file); fwrite(&cGarbage, sizeof(unsigned char), 1, file); // convert the image data from RGB(a) to BGR(A) if (mode >= 3) for (i=0; i < width * height * mode ; i+= mode) { aux = imageData[i]; imageData[i] = imageData[i+2]; imageData[i+2] = aux; } // save the image data w = 1; while (w < width) w = w << 1; // bytesPerPixel = pixelDepth/8; // row = width * bytesPerPixel; // Write one row at a time for (i = 0; i < height; i++) { ix = i * w * mode; fwrite(&imageData[ix], sizeof(unsigned char), width * mode, file); } fclose(file); // release the memory // No, this is the responsability of the host! // free(imageData); return(TGA_OK); } // Save a TextureData // Problem: Saves upside down! void SaveTGA(TextureData *tex, char *filename) { SaveDataToTGA(filename, tex->width, tex->height, tex->bpp, tex->imageData); } void SaveFramebufferToTGA(char *filename, GLint x, GLint y, GLint w, GLint h) { int err; void *buffer = malloc(h*w*3); glReadPixels(x, y, w, h, GL_RGB, GL_UNSIGNED_BYTE, buffer); err = SaveDataToTGA(filename, w, h, 3*8, (unsigned char *)buffer); // free(buffer); already done printf("SaveDataToTGA returned %d\n", err); }