1
0
Fork 0
mirror of https://github.com/AquariaOSE/Aquaria.git synced 2024-12-01 15:35:47 +00:00
Aquaria/BBGE/Texture.cpp
Valentin Ochs b9d402199c Get rid of a lot of gcc warnings
Mostly signed/unsigned comparisons. Also some bugs, but I don't remember
where :D
2021-01-11 12:28:27 +01:00

771 lines
19 KiB
C++

/*
Copyright (C) 2007, 2010 - Bit-Blot
This file is part of Aquaria.
Aquaria is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "Texture.h"
#include "Core.h"
#include "../ExternalLibs/glpng.h"
#include "ByteBuffer.h"
#include <assert.h>
#if defined(BBGE_BUILD_UNIX)
#include <stdint.h>
#endif
GLint Texture::filter = GL_LINEAR;
GLint Texture::format = 0;
bool Texture::useMipMaps = true;
Texture::Texture()
{
textures[0] = 0;
width = height = 0;
repeat = false;
repeating = false;
pngSetStandardOrientation(0);
ow = oh = -1;
loadResult = TEX_FAILED;
}
Texture::~Texture()
{
destroy();
}
void Texture::read(int tx, int ty, int w, int h, unsigned char *pixels)
{
if (tx == 0 && ty == 0 && w == this->width && h == this->height)
{
glBindTexture(GL_TEXTURE_2D, textures[0]);
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
glBindTexture(GL_TEXTURE_2D, 0);
}
else
{
std::ostringstream os;
os << "Unable to read a texture subimage (size = "
<< this->width << "x" << this->height << ", requested = "
<< tx << "," << ty << "+" << w << "x" << h << ")";
debugLog(os.str());
}
}
void Texture::write(int tx, int ty, int w, int h, const unsigned char *pixels)
{
glBindTexture(GL_TEXTURE_2D, textures[0]);
glTexSubImage2D(GL_TEXTURE_2D, 0,
tx,
ty,
w,
h,
GL_RGBA,
GL_UNSIGNED_BYTE,
pixels
);
glBindTexture(GL_TEXTURE_2D, 0);
/*
target Specifies the target texture. Must be
GL_TEXTURE_2D.
level Specifies the level-of-detail number. Level 0 is
the base image level. Level n is the nth mipmap
reduction image.
xoffset Specifies a texel offset in the x direction within
the texture array.
yoffset Specifies a texel offset in the y direction within
the texture array.
width Specifies the width of the texture subimage.
height Specifies the height of the texture subimage.
format Specifies the format of the pixel data. The
following symbolic values are accepted:
GL_COLOR_INDEX, GL_RED, GL_GREEN, GL_BLUE,
GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE, and
GL_LUMINANCE_ALPHA.
type Specifies the data type of the pixel data. The
following symbolic values are accepted:
GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP,
GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT,
GL_INT, and GL_FLOAT.
pixels Specifies a pointer to the image data in memory.
*/
}
void Texture::unload()
{
if (textures[0])
{
ow = width;
oh = height;
if (core->debugLogTextures)
{
debugLog("UNLOADING TEXTURE: " + name);
}
glDeleteTextures(1, &textures[0]);
textures[0] = 0;
}
}
void Texture::destroy()
{
unload();
core->removeTexture(this);
}
int Texture::getPixelWidth()
{
int w = 0, h = 0;
unsigned int size = 0;
unsigned char *data = getBufferAndSize(&w, &h, &size);
if (!data)
return 0;
size_t smallestx = -1, largestx = 0;
for (unsigned int x = 0; x < unsigned(w); x++)
{
for (unsigned int y = 0; y < unsigned(h); y++)
{
unsigned int p = (y*unsigned(w)*4) + (x*4) + 3;
if (p < size && data[p] >= 254)
{
if (x < smallestx)
smallestx = x;
if (x > largestx)
largestx = x;
}
}
}
free(data);
return largestx - smallestx;
}
int Texture::getPixelHeight()
{
int w = 0, h = 0;
unsigned int size = 0;
unsigned char *data = getBufferAndSize(&w, &h, &size);
if (!data)
return 0;
size_t smallesty = -1, largesty = 0;
for (unsigned int x = 0; x < unsigned(w); x++)
{
for (unsigned int y = 0; y < unsigned(h); y++)
{
size_t p = (y*unsigned(w)*4) + (x*4) + 3;
if (p < size && data[p] >= 254)
{
if (y < smallesty)
smallesty = y;
if (y > largesty)
largesty = y;
}
}
}
free(data);
return largesty - smallesty;
}
void Texture::reload()
{
debugLog("RELOADING TEXTURE: " + name + " with loadName " + loadName + "...");
unload();
load(loadName);
debugLog("DONE");
}
bool Texture::load(std::string file)
{
loadResult = TEX_FAILED;
if (file.size()<4)
{
errorLog("Texture Name is Empty or Too Short");
return false;
}
stringToLowerUserData(file);
file = core->adjustFilenameCase(file);
loadName = file;
repeating = false;
size_t pos = file.find_last_of('.');
if (pos != std::string::npos)
{
// make sure this didn't catch the '.' in /home/username/.Aquaria/* --ryan.
const std::string userdata = core->getUserDataFolder();
const size_t len = userdata.length();
if (pos < len)
pos = std::string::npos;
}
bool found = exists(file);
if(!found && exists(file + ".png"))
{
found = true;
file += ".png";
}
// .tga/.zga are never used as game graphics anywhere except save slot thumbnails.
// if so, their file names are passed exact, not with a missing extension
if (found)
{
file = localisePathInternalModpath(file);
file = core->adjustFilenameCase(file);
std::string post = file.substr(file.size()-3, 3);
stringToLower(post);
if (post == "png")
{
return loadPNG(file);
}
else if (post == "zga")
{
return loadZGA(file);
}
else if (post == "tga")
{
return loadTGA(file);
}
else
{
debugLog("unknown image file type: " + file);
}
}
else
{
// load default image / leave white
if (core->debugLogTextures)
debugLog("***Could not find texture: " + file);
}
return false;
}
void Texture::apply(bool repeatOverride)
{
glBindTexture(GL_TEXTURE_2D, textures[0]);
if (repeat || repeatOverride)
{
if (!repeating)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
repeating = true;
}
}
else
{
if (repeating)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
repeating = false;
}
}
}
void Texture::unbind()
{
}
bool Texture::loadPNG(const std::string &file)
{
if (file.empty()) return false;
bool good = false;
pngInfo info;
int pngType = PNG_ALPHA;
if (format != 0)
{
if (format == GL_LUMINANCE_ALPHA)
pngType = PNG_LUMINANCEALPHA;
}
unsigned long memsize = 0;
const char *memptr = readFile(file, &memsize);
if(!memptr || !memsize)
goto fail;
if (filter == GL_NEAREST)
{
textures[0] = pngBindMem(memptr, memsize, PNG_NOMIPMAPS, pngType, &info, GL_CLAMP_TO_EDGE, filter, filter);
}
else
{
textures[0] = pngBindMem(memptr, memsize, PNG_BUILDMIPMAPS, pngType, &info, GL_CLAMP_TO_EDGE, GL_LINEAR_MIPMAP_LINEAR, filter);
}
if (textures[0] != 0)
{
width = info.Width;
height = info.Height;
good = true;
loadResult = TEX_SUCCESS;
}
else
{
fail:
debugLog("Can't load PNG file: " + file);
}
if(memptr)
delete [] memptr;
return good;
}
// internal load functions
bool Texture::loadTGA(const std::string &file)
{
return loadTGA(TGAload(file.c_str()));
}
bool Texture::loadZGA(const std::string &file)
{
unsigned long size = 0;
char *buf = readCompressedFile(file, &size);
ImageTGA *tga = TGAloadMem(buf, size);
if (!tga)
{
debugLog("Can't load ZGA File: " + file);
return false;
}
return loadTGA(tga);
}
bool Texture::loadTGA(ImageTGA *imageTGA)
{
if (!imageTGA)
return false;
glGenTextures(1, &textures[0]);
glBindTexture(GL_TEXTURE_2D, textures[0]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,filter); // Linear Filtering
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,filter); // Linear Filtering
if (imageTGA->channels==3)
glTexImage2D(GL_TEXTURE_2D, 0, 3, imageTGA->sizeX, imageTGA->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, imageTGA->data);
else if (imageTGA->channels==4)
glTexImage2D(GL_TEXTURE_2D, 0, 4,imageTGA->sizeX, imageTGA->sizeY, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageTGA->data);
width = imageTGA->sizeX;
height = imageTGA->sizeY;
if (imageTGA->data)
delete[] (imageTGA->data);
free (imageTGA);
loadResult = TEX_SUCCESS;
return true;
}
#define TGA_RGB 2 // This tells us it's a normal RGB (really BGR) file
#define TGA_A 3 // This tells us it's a ALPHA file
#define TGA_RLE 10 // This tells us that the targa is Run-Length Encoded (RLE)
#if defined(BBGE_BUILD_UNIX)
typedef uint8_t byte;
typedef uint16_t WORD;
#endif
#ifdef BBGE_BUILD_WINDOWS
#define byte char
#endif
ImageTGA *Texture::TGAload(const char *filename)
{
unsigned long size = 0;
char *rawbuf = readFile(filename, &size);
ImageTGA *tga = TGAloadMem(rawbuf, size);
if (rawbuf)
delete [] rawbuf;
if (!tga)
{
debugLog("Can't load TGA File!");
return NULL;
}
return tga;
}
ImageTGA *Texture::TGAloadMem(void *mem, int size)
{
if (!mem || size < 20)
return NULL;
ByteBuffer bb(mem, size, ByteBuffer::REUSE);
ImageTGA *pImageData = NULL; // This stores our important image data
WORD width = 0, height = 0; // The dimensions of the image
byte length = 0; // The length in bytes to the pixels
byte imageType = 0; // The image type (RLE, RGB, Alpha...)
byte bits = 0; // The bits per pixel for the image (16, 24, 32)
uint32_t channels = 0; // The channels of the image (3 = RGA : 4 = RGBA)
uint32_t stride = 0; // The stride (channels * width)
size_t i = 0; // A counter
// This function loads in a TARGA (.TGA) file and returns its data to be
// used as a texture or what have you. This currently loads in a 16, 24
// and 32-bit targa file, along with RLE compressed files. Eventually you
// will want to do more error checking to make it more robust. This is
// also a perfect start to go into a modular class for an engine.
// Basically, how it works is, you read in the header information, then
// move your file pointer to the pixel data. Before reading in the pixel
// data, we check to see the if it's an RLE compressed image. This is because
// we will handle it different. If it isn't compressed, then we need another
// check to see if we need to convert it from 16-bit to 24 bit. 24-bit and
// 32-bit textures are very similar, so there's no need to do anything special.
// We do, however, read in an extra bit for each color.
// Allocate the structure that will hold our eventual image data (must free it!)
pImageData = (ImageTGA*)malloc(sizeof(ImageTGA));
// Read in the length in bytes from the header to the pixel data
bb >> length;
// Jump over one byte
bb.skipRead(1);
// Read in the imageType (RLE, RGB, etc...)
//fread(&imageType, sizeof(byte), 1, pFile);
bb >> imageType;
// Skip past general information we don't care about
bb.skipRead(9);
// Read the width, height and bits per pixel (16, 24 or 32)
bb >> width >> height >> bits;
/*
std::ostringstream os;
os << "TGALoad: width: " << width << " height: " << height << " bits: " << bits;
debugLog(os.str());
*/
// Now we move the file pointer to the pixel data
bb.skipRead(length + 1);
// Check if the image is RLE compressed or not
if(imageType != TGA_RLE)
{
// Check if the image is a 24 or 32-bit image
if(bits == 24 || bits == 32)
{
// Calculate the channels (3 or 4) - (use bits >> 3 for more speed).
// Next, we calculate the stride and allocate enough memory for the pixels.
channels = bits / 8;
stride = channels * width;
pImageData->data = new unsigned char[stride * height];
// Load in all the pixel data line by line
for(int y = 0; y < height; y++)
{
// Store a pointer to the current line of pixels
unsigned char *pLine = &(pImageData->data[stride * y]);
// Read in the current line of pixels
if (bb.readable() < stride)
break;
bb.read(pLine, stride);
// Go through all of the pixels and swap the B and R values since TGA
// files are stored as BGR instead of RGB (or use GL_BGR_EXT verses GL_RGB)
for(i = 0; i < stride; i += channels)
{
int temp = pLine[i];
pLine[i] = pLine[i + 2];
pLine[i + 2] = temp;
}
}
}
// Check if the image is a 16 bit image (RGB stored in 1 unsigned short)
else if(bits == 16)
{
unsigned short pixels = 0;
int r=0, g=0, b=0;
// Since we convert 16-bit images to 24 bit, we hardcode the channels to 3.
// We then calculate the stride and allocate memory for the pixels.
channels = 3;
stride = channels * width;
pImageData->data = new unsigned char[stride * height];
// Load in all the pixel data pixel by pixel
for(int i = 0; i < width*height; i++)
{
// Read in the current pixel
if (bb.readable() < sizeof(unsigned char))
break;
bb >> pixels;
// To convert a 16-bit pixel into an R, G, B, we need to
// do some masking and such to isolate each color value.
// 0x1f = 11111 in binary, so since 5 bits are reserved in
// each unsigned short for the R, G and B, we bit shift and mask
// to find each value. We then bit shift up by 3 to get the full color.
b = (pixels & 0x1f) << 3;
g = ((pixels >> 5) & 0x1f) << 3;
r = ((pixels >> 10) & 0x1f) << 3;
// This essentially assigns the color to our array and swaps the
// B and R values at the same time.
pImageData->data[i * 3 + 0] = r;
pImageData->data[i * 3 + 1] = g;
pImageData->data[i * 3 + 2] = b;
}
}
// Else return a NULL for a bad or unsupported pixel format
else
return NULL;
}
// Else, it must be Run-Length Encoded (RLE)
else
{
// First, let me explain real quickly what RLE is.
// For further information, check out Paul Bourke's intro article at:
// http://astronomy.swin.edu.au/~pbourke/dataformats/rle/
//
// Anyway, we know that RLE is a basic type compression. It takes
// colors that are next to each other and then shrinks that info down
// into the color and a integer that tells how much of that color is used.
// For instance:
// aaaaabbcccccccc would turn into a5b2c8
// Well, that's fine and dandy and all, but how is it down with RGB colors?
// Simple, you read in an color count (rleID), and if that number is less than 128,
// it does NOT have any optimization for those colors, so we just read the next
// pixels normally. Say, the color count was 28, we read in 28 colors like normal.
// If the color count is over 128, that means that the next color is optimized and
// we want to read in the same pixel color for a count of (colorCount - 127).
// It's 127 because we add 1 to the color count, as you'll notice in the code.
// Create some variables to hold the rleID, current colors read, channels, & stride.
byte rleID = 0;
int colorsRead = 0;
channels = bits / 8;
stride = channels * width;
// Next we want to allocate the memory for the pixels and create an array,
// depending on the channel count, to read in for each pixel.
pImageData->data = new unsigned char[stride * height];
byte *pColors = new byte [channels];
// Load in all the pixel data
while(i < width*height)
{
// Read in the current color count + 1
bb >> rleID;
// Check if we don't have an encoded string of colors
if(rleID < 128)
{
// Increase the count by 1
rleID++;
// Go through and read all the unique colors found
while(rleID)
{
// Read in the current color
if (bb.readable() < channels)
break;
bb.read(pColors, channels);
// Store the current pixel in our image array
pImageData->data[colorsRead + 0] = pColors[2];
pImageData->data[colorsRead + 1] = pColors[1];
pImageData->data[colorsRead + 2] = pColors[0];
// If we have a 4 channel 32-bit image, assign one more for the alpha
if(bits == 32)
pImageData->data[colorsRead + 3] = pColors[3];
// Increase the current pixels read, decrease the amount
// of pixels left, and increase the starting index for the next pixel.
i++;
rleID--;
colorsRead += channels;
}
}
// Else, let's read in a string of the same character
else
{
// Minus the 128 ID + 1 (127) to get the color count that needs to be read
rleID -= 127;
// Read in the current color, which is the same for a while
if (bb.readable() < channels)
break;
bb.read(pColors, channels);
// Go and read as many pixels as are the same
while(rleID)
{
// Assign the current pixel to the current index in our pixel array
pImageData->data[colorsRead + 0] = pColors[2];
pImageData->data[colorsRead + 1] = pColors[1];
pImageData->data[colorsRead + 2] = pColors[0];
// If we have a 4 channel 32-bit image, assign one more for the alpha
if(bits == 32)
pImageData->data[colorsRead + 3] = pColors[3];
// Increase the current pixels read, decrease the amount
// of pixels left, and increase the starting index for the next pixel.
i++;
rleID--;
colorsRead += channels;
}
}
}
// Free up pColors
delete[] pColors;
}
// Fill in our tImageTGA structure to pass back
pImageData->channels = channels;
pImageData->sizeX = width;
pImageData->sizeY = height;
// Return the TGA data (remember, you must free this data after you are done)
return pImageData;
}
// ceil to next power of 2
static unsigned int clp2(unsigned int x)
{
--x;
x |= (x >> 1);
x |= (x >> 2);
x |= (x >> 4);
x |= (x >> 8);
x |= (x >> 16);
return x + 1;
}
unsigned char * Texture::getBufferAndSize(int *wparam, int *hparam, unsigned int *sizeparam)
{
unsigned char *data = NULL;
unsigned int size = 0;
int tw = 0, th = 0;
int w = 0, h = 0;
// This can't happen. If it does we're doomed.
if(width <= 0 || height <= 0)
goto fail;
glBindTexture(GL_TEXTURE_2D, textures[0]);
// As returned by graphics driver
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
// As we know it - but round to nearest power of 2 - OpenGL does this internally anyways.
tw = clp2(width); // known to be > 0.
th = clp2(height);
if (w != tw || h != th)
{
std::ostringstream os;
os << "Texture::getBufferAndSize() WARNING: width/height disagree: ";
os << "Driver says (" << w << ", " << h << "); ";
os << "Texture says (" << width << ", " << height << "); ";
os << "Rounded to (" << tw << ", " << th << ")";
debugLog(os.str());
// choose max. for size calculation
w = w > tw ? w : tw;
h = h > th ? h : th;
}
size = w * h * 4;
if (!size)
goto fail;
data = (unsigned char*)malloc(size + 32);
if (!data)
{
std::ostringstream os;
os << "Game::fillGridFromQuad allocation failure, size = " << size;
errorLog(os.str());
goto fail;
}
memcpy(data + size, "SAFE", 5);
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
glBindTexture(GL_TEXTURE_2D, 0);
// Not sure but this might be the case with nouveau drivers on linux... still investigating. -- fg
if(memcmp(data + size, "SAFE", 5))
{
errorLog("Texture::getBufferAndSize(): Broken graphics driver! Wrote past end of buffer!");
free(data); // in case we are here, this will most likely cause a crash.
goto fail;
}
*wparam = w;
*hparam = h;
*sizeparam = size;
return data;
fail:
*wparam = 0;
*hparam = 0;
*sizeparam = 0;
return NULL;
}