diff --git a/src/gx/Texture.cpp b/src/gx/Texture.cpp index a97c494..3f7664f 100644 --- a/src/gx/Texture.cpp +++ b/src/gx/Texture.cpp @@ -2,6 +2,7 @@ #include "gx/Device.hpp" #include "gx/Gx.hpp" #include "gx/texture/CBLPFile.hpp" +#include "gx/texture/CTgaFile.hpp" #include "util/Filesystem.hpp" #include "util/SFile.hpp" #include @@ -272,6 +273,22 @@ int32_t GxTexCreate(EGxTexTarget target, uint32_t width, uint32_t height, uint32 ); } +int32_t GxTexCreate(uint32_t width, uint32_t height, EGxTexFormat format, CGxTexFlags flags, void* userArg, void (*userFunc)(EGxTexCommand, uint32_t, uint32_t, uint32_t, uint32_t, void*, uint32_t&, const void*&), CGxTex*& texId) { + return GxTexCreate( + GxTex_2d, + width, + height, + 0, + format, + format, + flags, + userArg, + userFunc, + "Unknown", + texId + ); +} + void GxTexDestroy(CGxTex* texId) { g_theGxDevicePtr->TexDestroy(texId); } @@ -351,6 +368,14 @@ CGxTex* TextureAllocGxTex(EGxTexTarget target, uint32_t width, uint32_t height, return nullptr; } +MipBits* TextureAllocMippedImg(PIXEL_FORMAT format, uint32_t width, uint32_t height) { + // TODO: optimizations + // MipBits cache + // Free list + + return MippedImgAllocA(format, width, height, __FILE__, __LINE__); +} + void GxTexUpdate(CGxTex* texId, int32_t minX, int32_t minY, int32_t maxX, int32_t maxY, int32_t immediate) { CiRect rect = { minY, minX, maxY, maxX }; GxTexUpdate(texId, rect, immediate); @@ -504,6 +529,19 @@ uint32_t MippedImgCalcSize(uint32_t fourCC, uint32_t width, uint32_t height) { return imgSize; } +void MippedImgSet(uint32_t fourCC, uint32_t width, uint32_t height, MipBits* bits) { + uint32_t levelDataSize; + uint32_t offset = 0; + + auto levelCount = CalcLevelCount(width, height); + + for (int32_t level = 0; level < levelCount; level++) { + bits->mip[level] = reinterpret_cast(reinterpret_cast(bits->mip[levelCount]) + offset); + levelDataSize = CalcLevelSize(level, width, height, fourCC); + offset += levelDataSize; + } +} + // TODO // - order: width, height or height, width? void RequestImageDimensions(uint32_t* width, uint32_t* height, uint32_t* bestMip) { @@ -1090,3 +1128,389 @@ int32_t TextureIsSame(HTEXTURE textureHandle, const char* fileName) { return SStrCmpI(buf, TextureGetTexturePtr(textureHandle)->filename, sizeof(buf)) == 0; } + +int32_t TextureCalcMipCount(uint32_t width, uint32_t height) { + int32_t mips = 1; + + while (width > 1 || height > 1) { + mips++; + + width >>= 1; + if (!width) { + width = 1; + } + + height >>= 1; + if (!height) { + height = 1; + } + } + + return mips; +} + +static void RemoveExtension(char* path) { + auto dot = SStrChrR(path, '.'); + if (dot) { + *dot = '\0'; + } +} + +static void GenerateMipMask(const char* mipZeroName, char* mipMask) { + SStrCopy(mipMask, mipZeroName, STORM_MAX_PATH); + RemoveExtension(mipMask); + SStrPack(mipMask, "_mip%d.tga", STORM_MAX_PATH); +} + +static uint32_t LoadPredrawnMips(const CTgaFile& mipZero, const char* filemask, int32_t a3, MipBits* buffer) { + STORM_ASSERT(filemask); + STORM_ASSERT(buffer); + + char pathName[STORM_MAX_PATH]; + + auto mipZeroWidth = mipZero.Width(); + auto mipZeroHeight = mipZero.Height(); + + uint32_t index = 1; + + auto levels = TextureCalcMipCount(mipZeroWidth, mipZeroHeight); + STORM_ASSERT(levels); + + levels--; + + auto width = mipZeroWidth >> 1; + auto height = mipZeroHeight >> 1; + + if (levels) { + auto v23 = a3 != 0; + while (1) { + levels--; + SStrPrintf(pathName, STORM_MAX_PATH, filemask, index); + if (!SFile::FileExistsEx(pathName, v23)) { + break; + } + + CTgaFile mipTga; + + if (!mipTga.Open(pathName, a3) || width != mipTga.Width() || height != mipTga.Height()) { + mipTga.Close(); + return index; + } + + if (!mipTga.LoadImageData(2)) { + mipTga.Close(); + return index; + } + + if (mipTga.AlphaBits() == 0) { + mipTga.AddAlphaChannel(nullptr); + } + + mipTga.SetTopDown(1); + + auto mipTgaPixel = mipTga.ImageTGA32Pixel(); + + // TODO: suppress UBsan warnings + auto bufferPixel = buffer->mip[index]; + + index++; + + for (auto i = width * height; i != 0; i--) { + bufferPixel->a = mipTgaPixel->a; + bufferPixel->r = mipTgaPixel->r; + bufferPixel->g = mipTgaPixel->g; + bufferPixel->b = mipTgaPixel->b; + + bufferPixel++; + mipTgaPixel++; + } + + if (width > 1) { + width >>= 1; + } + + if (height > 1) { + height >>= 1; + } + + mipTga.Close(); + + if (levels == 0) { + return index; + } + + } + } + + return index; +} + +void FullShrink(C4Pixel* dest, uint32_t destWidth, uint32_t destHeight, C4Pixel* source, uint32_t sourceWidth, uint32_t sourceHeight) { + auto xScale = sourceWidth / destWidth; + auto yScale = sourceHeight / destHeight; + + C4LargePixel unweighted; + C4LargePixel weighted; + C4Pixel result; + + STORM_ASSERT(destWidth * xScale == sourceWidth); + STORM_ASSERT(destHeight * yScale == sourceHeight); + + for (auto y = destHeight; y != 0; y--) { + for (auto x = destWidth; x != 0; x--) { + memset(&weighted, 0, sizeof(weighted)); + memset(&unweighted, 0, sizeof(unweighted)); + + auto currSource = source; + + for (uint32_t i = yScale; i != 0; i--) { + auto pixel = currSource; + + for (uint32_t j = xScale; j != 0; j--) { + weighted.a += pixel->a; + weighted.r += pixel->r * pixel->a; + weighted.g += pixel->g * pixel->a; + weighted.b += pixel->b * pixel->a; + + unweighted.a++; + unweighted.r += pixel->r; + unweighted.g += pixel->g; + unweighted.b += pixel->b; + + pixel++; + } + + currSource += sourceWidth; + } + + if (weighted.a) { + STORM_ASSERT(xScale * yScale); + + result.r = weighted.r / weighted.a; + result.g = weighted.g / weighted.a; + result.b = weighted.b / weighted.a; + result.a = weighted.a / (xScale * yScale); + } else { + result.r = unweighted.r / unweighted.a; + result.g = unweighted.g / unweighted.a; + result.b = unweighted.b / unweighted.a; + result.a = 0; + } + + *dest++ = result; + source += xScale; + } + + source += (yScale - 1) * sourceWidth; + } +} + +void TextureGenerateMips(uint32_t width, uint32_t height, uint32_t levelsProvided, uint32_t levelsDesired, MipBits* levelBits) { + uint32_t last_good_level = 0; + + auto mipHeight = height; + auto mipWidth = width; + + if (levelsDesired > 1) { + for (uint32_t i = 0; i < levelsDesired; i++) { + mipWidth = mipWidth >>= 1 != 0 ? mipWidth : 1; + mipHeight = mipHeight >>= 1 != 0 ? mipHeight : 1; + + if (i >= levelsProvided) { + FullShrink(levelBits->mip[i], mipWidth, mipHeight, levelBits->mip[last_good_level], width, height); + } + } + } +} + +int32_t LoadTgaMips(char* ext, const char* filename, int32_t a3, MipBits*& mipBits, uint32_t* width, uint32_t* height, EGxTexFormat* texFormat, int32_t* isOpaque, uint32_t* alphaBits, PIXEL_FORMAT* dataFormat) { + STORM_ASSERT(filename); + + if (ext) { + ext[0] = '.'; + ext[1] = 't'; + ext[2] = 'g'; + ext[3] = 'a'; + ext[4] = '\0'; + } + + CTgaFile texFile; + + if (!texFile.Open(filename, a3)) { + texFile.Close(); + return 0; + } + + if (isOpaque) { + *isOpaque = texFile.AlphaBits() == 0; + } + + if (!texFile.LoadImageData(3)) { + texFile.Close(); + return 0; + } + + texFile.SetTopDown(1); + + auto mipCount = TextureCalcMipCount(texFile.Width(), texFile.Height()); + + if (mipBits) { + MippedImgSet(2, texFile.Width(), texFile.Height(), mipBits); + } else { + mipBits = TextureAllocMippedImg(PIXEL_ARGB8888, texFile.Width(), texFile.Height()); + } + + auto size = texFile.Size(); + + if (size) { + auto tga32Pixels = texFile.ImageTGA32Pixel(); + auto mipPixels = mipBits->mip[0]; + + for (int32_t i = 0; i < size; i++) { + auto& mipPixel = mipPixels[i]; + auto& tga32Pixel = tga32Pixels[i]; + + mipPixel.b = tga32Pixel.b; + mipPixel.g = tga32Pixel.g; + mipPixel.r = tga32Pixel.r; + mipPixel.a = tga32Pixel.a; + } + } + + texFile.Close(); + + char mipFileMask[STORM_MAX_PATH]; + + GenerateMipMask(filename, mipFileMask); + + auto predrawnLevels = LoadPredrawnMips(texFile, mipFileMask, a3, mipBits); + + TextureGenerateMips(texFile.Width(), texFile.Height(), predrawnLevels, mipCount, mipBits); + + if (width) { + *width = texFile.Width(); + } + + if (height) { + *height = texFile.Height(); + } + + if (texFormat) { + *texFormat = GxTex_Argb8888; + } + + if (alphaBits) { + *alphaBits = texFile.AlphaBits(); + } + + if (dataFormat) { + *dataFormat = PIXEL_ARGB8888; + } + + return 1; +} + +int32_t LoadBlpMips(char* ext, const char* filename, int32_t a3, MipBits*& mipBits, uint32_t* width, uint32_t* height, int32_t* isOpaque, PIXEL_FORMAT* dataFormat) { + STORM_ASSERT(filename); + + if (ext) { + ext[0] = '.'; + ext[1] = 'b'; + ext[2] = 'l'; + ext[3] = 'p'; + ext[4] = '\0'; + } + + uint32_t bestMip; + + CBLPFile texFile; + + if (!texFile.Open(filename, a3)) { + texFile.Close(); + return 0; + } + + auto imgWidth = texFile.Width(); + auto imgHeight = texFile.Height(); + + PIXEL_FORMAT format; + if (!dataFormat || (format = *dataFormat, format == PIXEL_UNSPECIFIED)) { + if (texFile.AlphaBits()) { + format = texFile.AlphaBits() == 1 ? PIXEL_ARGB1555 : PIXEL_ARGB4444; + } else { + format = PIXEL_RGB565; + } + } + + auto image = TextureAllocMippedImg(format, imgWidth, imgHeight); + + if (!texFile.LockChain2(filename, format, mipBits, 0, 0)) { + texFile.Close(); + return 0; + } + + if (width) { + *width = imgWidth; + } + + if (height) { + *height = imgHeight; + } + + if (isOpaque) { + *isOpaque = texFile.AlphaBits() == 0; + } + + if (dataFormat) { + *dataFormat = format; + } + + texFile.Close(); + + return 1; +} + +MipBits* TextureLoadImage(const char* filename, uint32_t* width, uint32_t* height, PIXEL_FORMAT* dataFormat, int32_t* isOpaque, CStatus* status, uint32_t* alphaBits, int32_t a8) { + STORM_ASSERT(filename); + STORM_ASSERT(width); + STORM_ASSERT(height); + + // OsOutputDebugString("TextureLoadImage() blocking load: %s.\n", filename) + + char loadFileName[STORM_MAX_PATH]; + + SStrCopy(loadFileName, filename, STORM_MAX_PATH); + + auto ext = OsPathFindExtensionWithDot(loadFileName); + + *ext = '\0'; + + uint32_t imageFormat = IMAGE_FORMAT_BLP; + MipBits* mipImages = nullptr; + + for (uint32_t i = 0; i < NUM_IMAGE_FORMATS; i++) { + if (imageFormat == IMAGE_FORMAT_TGA) { + LoadTgaMips(ext, loadFileName, a8, mipImages, width, height, nullptr, isOpaque, alphaBits, dataFormat); + } else if (imageFormat == IMAGE_FORMAT_BLP) { + LoadBlpMips(ext, loadFileName, a8, mipImages, width, height, isOpaque, dataFormat); + } + + imageFormat++; + imageFormat %= 2; + + if (mipImages) { + return mipImages; + } + } + + status->Add(STATUS_FATAL, "Error loading texure file \"%s\": unsupported image format\n", filename); + return nullptr; +} + +void TextureFreeMippedImg(MipBits* image, PIXEL_FORMAT format, uint32_t width, uint32_t height) { + // TODO: mip bits cache free list + + if (image) { + SMemFree(image, __FILE__, __LINE__, 0x0); + } +} diff --git a/src/gx/Texture.hpp b/src/gx/Texture.hpp index cb68564..9ab2b3c 100644 --- a/src/gx/Texture.hpp +++ b/src/gx/Texture.hpp @@ -5,6 +5,12 @@ #include "gx/texture/CGxTex.hpp" #include "gx/texture/CTexture.hpp" +enum EImageFormat { + IMAGE_FORMAT_TGA = 0x0, + IMAGE_FORMAT_BLP = 0x1, + NUM_IMAGE_FORMATS = 0x2 +}; + typedef HOBJECT HTEXTURE; typedef void (TEXTURE_CALLBACK)(EGxTexCommand, uint32_t, uint32_t, uint32_t, uint32_t, void*, uint32_t&, const void*&); @@ -27,6 +33,8 @@ int32_t GxTexCreate(CGxTexParms const&, CGxTex*&); int32_t GxTexCreate(EGxTexTarget, uint32_t, uint32_t, uint32_t, EGxTexFormat, EGxTexFormat, CGxTexFlags, void*, void (*)(EGxTexCommand, uint32_t, uint32_t, uint32_t, uint32_t, void*, uint32_t&, const void*&), const char*, CGxTex*&); +int32_t GxTexCreate(uint32_t, uint32_t, EGxTexFormat, CGxTexFlags, void*, void (*)(EGxTexCommand, uint32_t, uint32_t, uint32_t, uint32_t, void*, uint32_t&, const void*&), CGxTex*&); + void GxTexDestroy(CGxTex* texId); void GxTexParameters(const CGxTex* texId, CGxTexParms& parms); @@ -45,6 +53,8 @@ MipBits* MippedImgAllocA(uint32_t, uint32_t, uint32_t, const char*, int32_t); uint32_t MippedImgCalcSize(uint32_t, uint32_t, uint32_t); +void MippedImgSet(uint32_t fourCC, uint32_t width, uint32_t height, MipBits* bits); + CGxTex* TextureAllocGxTex(EGxTexTarget, uint32_t, uint32_t, uint32_t, EGxTexFormat, CGxTexFlags, void*, void (*userFunc)(EGxTexCommand, uint32_t, uint32_t, uint32_t, uint32_t, void*, uint32_t&, const void*&), EGxTexFormat); HTEXTURE TextureCacheGetTexture(char*, char*, CGxTexFlags); @@ -71,8 +81,12 @@ void TextureInitialize(void); int32_t TextureIsSame(HTEXTURE textureHandle, const char* fileName); +MipBits* TextureLoadImage(const char* filename, uint32_t* width, uint32_t* height, PIXEL_FORMAT* dataFormat, int32_t* isOpaque, CStatus* status, uint32_t* alphaBits, int32_t a8); + void TextureFreeGxTex(CGxTex* texId); +void TextureFreeMippedImg(MipBits* image, PIXEL_FORMAT format, uint32_t width, uint32_t height); + CGxTex* TextureGetGxTex(CTexture*, int32_t, CStatus*); CGxTex* TextureGetGxTex(HTEXTURE, int32_t, CStatus*); diff --git a/src/gx/texture/CBLPFile.cpp b/src/gx/texture/CBLPFile.cpp index c237b71..ec6899b 100644 --- a/src/gx/texture/CBLPFile.cpp +++ b/src/gx/texture/CBLPFile.cpp @@ -1,11 +1,72 @@ #include "gx/Texture.hpp" #include "gx/texture/CBLPFile.hpp" #include "util/SFile.hpp" +#include "util/Unimplemented.hpp" #include #include #include -TSGrowableArray CBLPFile::s_blpFileLoadBuffer; +TSGrowableArray CBLPFile::s_blpFileLoadBuffer; + +uint8_t CBLPFile::s_oneBitAlphaLookup[2] = { + 0x00, + 0xFF +}; + +uint8_t CBLPFile::s_eightBitAlphaLookup[16] = { + 0x00, + 0x11, + 0x22, + 0x33, + 0x44, + 0x55, + 0x66, + 0x77, + 0x88, + 0x99, + 0xAA, + 0xBB, + 0xCC, + 0xDD, + 0xEE, + 0xFF +}; + +uint32_t CBLPFile::AlphaBits() { + return this->m_header.alphaSize; +} + +uint32_t CBLPFile::Width() { + return this->m_header.width; +} + +uint32_t CBLPFile::Width(uint32_t mipLevel) { + auto width = this->m_header.width >> mipLevel; + if (width <= 1) { + width = 1; + } + return width; +} + +uint32_t CBLPFile::Height() { + return this->m_header.height; +} + +uint32_t CBLPFile::Height(uint32_t mipLevel) { + auto height = this->m_header.height >> mipLevel; + if (height <= 1) { + height = 1; + } + return height; +} + +uint32_t CBLPFile::Pixels() { + return this->Width() * this->Height(); +} + +uint32_t CBLPFile::Pixels(uint32_t mipLevel) { + return this->Width(mipLevel) * this->Height(mipLevel); +} void CBLPFile::Close() { this->m_inMemoryImage = nullptr; @@ -17,54 +78,229 @@ void CBLPFile::Close() { this->m_images = nullptr; } -int32_t CBLPFile::Lock2(const char* fileName, PIXEL_FORMAT format, uint32_t mipLevel, unsigned char* data, uint32_t& stride) { +MIPS_TYPE CBLPFile::HasMips() { + return static_cast(this->m_header.hasMips); +} + +int32_t CBLPFile::IsValidMip(uint32_t level) { + if (level) { + if (!(this->HasMips() & 0xF) || level >= this->m_numLevels) { + return 0; + } + } + + return 1; +} + +int32_t CBLPFile::GetFormatSize(PIXEL_FORMAT format, uint32_t mipLevel, uint32_t* size, uint32_t* stride) { + auto width = this->Width(mipLevel); + auto height = this->Height(mipLevel); + + auto pixels = width * height; + + auto v8 = pixels >> 2; + + if (!v8) { + v8 = 1; + } + + switch (format) { + case PIXEL_ARGB8888: + *size = 4 * pixels; + *stride = 4 * width; + return 1; + case PIXEL_ARGB1555: + case PIXEL_ARGB4444: + case PIXEL_RGB565: + *size = 2 * pixels; + *stride = 2 * width; + return 1; + case PIXEL_ARGB2565: + *size = v8 + 2 * pixels; + *stride = 2 * width; + return 1; + default: + *size = 0; + *stride = 0; + return 0; + } +} + +void CBLPFile::DecompPalFastPath(uint8_t* data, void* tempbuffer, uint32_t colorSize) { + auto bytes = reinterpret_cast(tempbuffer); + + for (auto i = colorSize; i; i--) { + *reinterpret_cast(data) = this->m_header.extended.palette[*bytes]; + data[3] = *(bytes + colorSize); + bytes++; + data += 4; + } +} + +void CBLPFile::DecompPalARGB8888(uint8_t* data, void* tempbuffer, uint32_t colorSize) { + auto pixels = data; + auto bytes = reinterpret_cast(tempbuffer); + + for (auto i = colorSize; i; i--) { + *reinterpret_cast(pixels) = this->m_header.extended.palette[*bytes]; + pixels[3] = 0xFF; + pixels += 4; + bytes++; + } + + auto alphaBits = this->AlphaBits(); + + if (alphaBits == 1) { + auto v14 = colorSize >> 3; + + for (auto a = 0; a < v14; a++) { + auto byte = bytes[a]; + data[3] = s_oneBitAlphaLookup[byte & 1]; + data[7] = s_oneBitAlphaLookup[(byte >> 1) & 1]; + data[11] = s_oneBitAlphaLookup[(byte >> 2) & 1]; + data[15] = s_oneBitAlphaLookup[(byte >> 3) & 1]; + data[19] = s_oneBitAlphaLookup[(byte >> 4) & 1]; + data[23] = s_oneBitAlphaLookup[(byte >> 5) & 1]; + data[27] = s_oneBitAlphaLookup[(byte >> 6) & 1]; + data[31] = s_oneBitAlphaLookup[(byte >> 7) & 1]; + + data += 32; + } + + auto v20 = colorSize & 7; + if (v20) { + auto byte = bytes[v14]; + auto dest = data + 3; + do { + *dest = s_oneBitAlphaLookup[byte & 1]; + byte >>= 1; + dest += 4; + v20--; + } while (v20); + } + } else if (alphaBits == 4) { + for (auto i = colorSize >> 1; i; i--) { + auto byte = *bytes; + data[3] = s_eightBitAlphaLookup[byte & 0xF]; + data[7] = s_eightBitAlphaLookup[byte >> 4]; + + data += 8; + bytes++; + } + + if (colorSize & 1) { + auto byte = *bytes; + data[3] = s_eightBitAlphaLookup[byte & 0xF]; + return; + } + } else if (alphaBits == 8 && colorSize) { + auto dest = data + 3; + do { + *dest = *bytes; + dest += 4; + bytes++; + colorSize--; + } while (colorSize); + } +} + +void CBLPFile::DecompPalARGB1555DitherFloydSteinberg(uint8_t* data, void* tempbuffer, uint32_t width, uint32_t height) { + WHOA_UNIMPLEMENTED(); +} + +void CBLPFile::DecompPalARGB2565DitherFloydSteinberg(uint8_t* data, void* tempbuffer, uint32_t width, uint32_t height) { + WHOA_UNIMPLEMENTED(); +} + +void CBLPFile::DecompPalARGB4444DitherFloydSteinberg(uint8_t* data, void* tempbuffer, uint32_t width, uint32_t height) { + WHOA_UNIMPLEMENTED(); +} + +void CBLPFile::DecompPalARGB565DitherFloydSteinberg(uint8_t* data, void* tempbuffer, uint32_t width, uint32_t height) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CBLPFile::DecompPal(PIXEL_FORMAT format, uint32_t mipLevel, uint8_t* data, void* tempBuffer) { + switch (format) { + case PIXEL_ARGB8888: + if (this->AlphaBits() == 8) { + this->DecompPalFastPath(data, tempBuffer, this->Pixels(mipLevel)); + } else { + this->DecompPalARGB8888(data, tempBuffer, this->Pixels(mipLevel)); + } + return 1; + case PIXEL_ARGB1555: + this->DecompPalARGB1555DitherFloydSteinberg(data, tempBuffer, this->Width(mipLevel), this->Height(mipLevel)); + return 1; + case PIXEL_ARGB4444: + this->DecompPalARGB4444DitherFloydSteinberg(data, tempBuffer, this->Width(mipLevel), this->Height(mipLevel)); + return 1; + case PIXEL_RGB565: + this->DecompPalARGB565DitherFloydSteinberg(data, tempBuffer, this->Width(mipLevel), this->Height(mipLevel)); + return 1; + case PIXEL_ARGB2565: + this->DecompPalARGB2565DitherFloydSteinberg(data, tempBuffer, this->Width(mipLevel), this->Height(mipLevel)); + return 1; + default: + return 0; + } +} + +int32_t CBLPFile::Lock2(const char* fileName, PIXEL_FORMAT format, uint32_t mipLevel, uint8_t* data, uint32_t& stride) { STORM_ASSERT(this->m_inMemoryImage); - if (mipLevel && (!(this->m_header.hasMips & 0xF) || mipLevel >= this->m_numLevels)) { + if (!this->IsValidMip(mipLevel)) { return 0; } - unsigned char* mipData = static_cast(this->m_inMemoryImage) + this->m_header.mipOffsets[mipLevel]; + uint8_t* mipData = static_cast(this->m_inMemoryImage) + this->m_header.mipOffsets[mipLevel]; size_t mipSize = this->m_header.mipSizes[mipLevel]; + uint32_t formatSize; switch (this->m_header.colorEncoding) { - case COLOR_PAL: - // TODO - return 0; + case COLOR_JPEG: + STORM_PANIC("%s: JPEG decompression not enabled", fileName); + return 0; - case COLOR_DXT: - switch (format) { - case PIXEL_DXT1: - case PIXEL_DXT3: - case PIXEL_DXT5: - memcpy(data, mipData, mipSize); - return 1; + case COLOR_PAL: + if (this->GetFormatSize(format, mipLevel, &formatSize, &stride)) { + this->m_lockDecompMem = data; + return this->DecompPal(format, mipLevel, data, mipData); + } + return 0; + case COLOR_DXT: + switch (format) { + case PIXEL_DXT1: + case PIXEL_DXT3: + case PIXEL_DXT5: + memcpy(data, mipData, mipSize); + return 1; - case PIXEL_ARGB8888: - case PIXEL_ARGB1555: - case PIXEL_ARGB4444: - case PIXEL_RGB565: - // TODO - return 0; + case PIXEL_ARGB8888: + case PIXEL_ARGB1555: + case PIXEL_ARGB4444: + case PIXEL_RGB565: + // TODO + return 0; - case PIXEL_ARGB2565: - return 0; + case PIXEL_ARGB2565: + return 0; - default: - return 0; - } + default: + return 0; + } - case COLOR_3: - memcpy(data, mipData, mipSize); - return 1; + case COLOR_3: + memcpy(data, mipData, mipSize); + return 1; - default: - return 0; + default: + return 0; } } int32_t CBLPFile::LockChain2(const char* fileName, PIXEL_FORMAT format, MipBits*& images, uint32_t mipLevel, int32_t a6) { - if (mipLevel && (!(this->m_header.hasMips & 0xF) || mipLevel >= this->m_numLevels)) { + if (!this->IsValidMip(mipLevel)) { return 0; } @@ -74,9 +310,8 @@ int32_t CBLPFile::LockChain2(const char* fileName, PIXEL_FORMAT format, MipBits* uint32_t* offset = this->m_header.mipOffsets; for (int32_t i = 0; *offset; offset++, i++) { - void* address = static_cast(this->m_inMemoryImage) + *offset; - MipBits* image = static_cast(address); - reinterpret_cast(images)[i] = image; + void* address = static_cast(this->m_inMemoryImage) + *offset; + images->mip[i] = reinterpret_cast(address); } this->m_inMemoryImage = nullptr; @@ -84,44 +319,17 @@ int32_t CBLPFile::LockChain2(const char* fileName, PIXEL_FORMAT format, MipBits* } } - uint32_t v13 = this->m_header.height >> mipLevel; - - if (v13 <= 1) { - v13 = 1; - } - - uint32_t v14 = this->m_header.width >> mipLevel; - - if (v14 <= 1) { - v14 = 1; - } - - // TODO - // MippedImgSet(format, v14, v13, mipLevel); + MippedImgSet(format, this->Width(mipLevel), this->Height(mipLevel), images); } else { - uint32_t v9 = this->m_header.height >> mipLevel; - - if (v9 <= 1) { - v9 = 1; - } - - uint32_t v10 = this->m_header.width >> mipLevel; - - if (v10 <= 1) { - v10 = 1; - } - - images = MippedImgAllocA(format, v10, v9, __FILE__, __LINE__); + images = MippedImgAllocA(format, this->Width(mipLevel), this->Height(mipLevel), __FILE__, __LINE__); if (!images) { return 0; } } - MipBits** ptr = reinterpret_cast(images); - for (int32_t level = mipLevel, i = 0; level < this->m_numLevels; level++, i++) { - if (!this->Lock2(fileName, format, level, reinterpret_cast(ptr[i]), mipLevel)) { + if (!this->Lock2(fileName, format, level, reinterpret_cast(images->mip[i]), mipLevel)) { return 0; } } diff --git a/src/gx/texture/CBLPFile.hpp b/src/gx/texture/CBLPFile.hpp index 4ad468b..1301094 100644 --- a/src/gx/texture/CBLPFile.hpp +++ b/src/gx/texture/CBLPFile.hpp @@ -6,6 +6,12 @@ #include #include +enum MIPS_TYPE { + MIPS_NONE = 0x0, + MIPS_GENERATED = 0x1, + MIPS_HANDMADE = 0x2 +}; + enum MipMapAlgorithm { MMA_BOX = 0x0, MMA_CUBIC = 0x1, @@ -15,20 +21,20 @@ enum MipMapAlgorithm { }; struct BlpPalPixel { - char b; - char g; - char r; - char pad; + uint8_t b; + uint8_t g; + uint8_t r; + uint8_t pad; }; class CBLPFile { struct BLPHeader { - uint32_t magic; - uint32_t formatVersion; - char colorEncoding; - char alphaSize; - char preferredFormat; - char hasMips; + uint32_t magic = 0x32504C42; + uint32_t formatVersion = 1; + uint8_t colorEncoding; + uint8_t alphaSize; + uint8_t preferredFormat = 2; + uint8_t hasMips; uint32_t width; uint32_t height; uint32_t mipOffsets[16]; @@ -39,31 +45,50 @@ class CBLPFile { struct { uint32_t headerSize; - char headerData[1020]; + uint8_t headerData[1020]; } jpeg; } extended; }; public: // Static variables - static TSGrowableArray s_blpFileLoadBuffer; + static TSGrowableArray s_blpFileLoadBuffer; + static uint8_t s_oneBitAlphaLookup[2]; + static uint8_t s_eightBitAlphaLookup[16]; // Member variables MipBits* m_images = nullptr; - BLPHeader m_header; + BLPHeader m_header = {}; void* m_inMemoryImage = nullptr; int32_t m_inMemoryNeedsFree; uint32_t m_numLevels; uint32_t m_quality = 100; void* m_colorMapping; MipMapAlgorithm m_mipMapAlgorithm = MMA_BOX; - char* m_lockDecompMem; + uint8_t* m_lockDecompMem; // Member functions - void Close(void); - int32_t Lock2(const char*, PIXEL_FORMAT, uint32_t, unsigned char*, uint32_t&); + uint32_t AlphaBits(); + uint32_t Width(); + uint32_t Width(uint32_t mipLevel); + uint32_t Height(); + uint32_t Height(uint32_t mipLevel); + uint32_t Pixels(); + uint32_t Pixels(uint32_t mipLevel); + MIPS_TYPE HasMips(); + int32_t IsValidMip(uint32_t level); + int32_t GetFormatSize(PIXEL_FORMAT format, uint32_t mipLevel, uint32_t* size, uint32_t* stride); + void DecompPalFastPath(uint8_t* data, void* tempbuffer, uint32_t colorSize); + void DecompPalARGB8888(uint8_t* data, void* tempbuffer, uint32_t colorSize); + void DecompPalARGB1555DitherFloydSteinberg(uint8_t* data, void* tempbuffer, uint32_t width, uint32_t height); + void DecompPalARGB2565DitherFloydSteinberg(uint8_t* data, void* tempbuffer, uint32_t width, uint32_t height); + void DecompPalARGB4444DitherFloydSteinberg(uint8_t* data, void* tempbuffer, uint32_t width, uint32_t height); + void DecompPalARGB565DitherFloydSteinberg(uint8_t* data, void* tempbuffer, uint32_t width, uint32_t height); + int32_t DecompPal(PIXEL_FORMAT format, uint32_t mipLevel, uint8_t* data, void* tempBuffer); + int32_t Lock2(const char*, PIXEL_FORMAT, uint32_t, uint8_t*, uint32_t&); int32_t LockChain2(const char*, PIXEL_FORMAT, MipBits*&, uint32_t, int32_t); int32_t Open(const char*, int32_t); + void Close(void); int32_t Source(void*); }; diff --git a/src/gx/texture/CGxTex.hpp b/src/gx/texture/CGxTex.hpp index b85aa96..eda0d6e 100644 --- a/src/gx/texture/CGxTex.hpp +++ b/src/gx/texture/CGxTex.hpp @@ -8,16 +8,16 @@ class CGxTexFlags { public: // Member variables - uint32_t m_filter : 3; - uint32_t m_wrapU : 1; - uint32_t m_wrapV : 1; - uint32_t m_forceMipTracking : 1; - uint32_t m_generateMipMaps : 1; - uint32_t m_renderTarget : 1; - uint32_t m_maxAnisotropy : 5; - uint32_t m_bit13 : 1; - uint32_t m_bit14 : 1; - uint32_t m_bit15 : 1; + uint32_t m_filter : 3; + uint32_t m_wrapU : 1; + uint32_t m_wrapV : 1; + uint32_t m_forceMipTracking : 1; + uint32_t m_generateMipMaps : 1; + uint32_t m_renderTarget : 1; + uint32_t m_maxAnisotropy : 5; + uint32_t m_bit13 : 1; + uint32_t m_bit14 : 1; + uint32_t m_bit15 : 1; // Member functions CGxTexFlags() diff --git a/src/gx/texture/CTgaFile.cpp b/src/gx/texture/CTgaFile.cpp new file mode 100644 index 0000000..3680fcb --- /dev/null +++ b/src/gx/texture/CTgaFile.cpp @@ -0,0 +1,475 @@ +#include "gx/texture/CTgaFile.hpp" + +#include +#include + +#include + +int32_t CTgaFile::Open(const char* filename, int32_t a3) { + STORM_VALIDATE_STRING(filename, ERROR_INVALID_PARAMETER, 0); + + this->Close(); + + if (!SFile::OpenEx(0, filename, a3 != 0, &this->m_file)) { + return 0; + } + + // Read header + if (!SFile::Read(this->m_file, &this->m_header, sizeof(this->m_header), nullptr, nullptr, nullptr)) { + return 0; + } + + // Read additional header data + if (this->m_header.bIDLength == 0) { + this->m_addlHeaderData = nullptr; + } else { + this->m_addlHeaderData = static_cast(SMemAlloc(this->m_header.bIDLength, __FILE__, __LINE__, 0x0)); + if (!SFile::Read(this->m_file, static_cast(this->m_addlHeaderData), this->m_header.bIDLength, nullptr, nullptr, nullptr)) { + return 0; + } + } + + // Read color map + if (this->m_header.bColorMapType == 0) { + this->m_colorMap = nullptr; + } else { + this->m_colorMap = static_cast(SMemAlloc(this->ColorMapBytes(), __FILE__, __LINE__, 0x0)); + if (!SFile::Read(this->m_file, static_cast(this->m_colorMap), this->ColorMapBytes(), nullptr, nullptr, nullptr)) { + return 0; + } + } + + // 3 pixels with alpha channel makes no sense + STORM_VALIDATE(!(this->m_header.bPixelDepth == 24 && this->m_header.desc.bAlphaChannelBits == 8), 0x8720012E, 0); + // 4 pixels with no alpha channel makes no sense + STORM_VALIDATE(!(this->m_header.bPixelDepth == 32 && this->m_header.desc.bAlphaChannelBits == 0), 0x8720012F, 0); + + return 1; +} + +void CTgaFile::Close() { + if (this->m_image) { + SMemFree(this->m_image, __FILE__, __LINE__, 0x0); + } + this->m_image = nullptr; + + if (this->m_file) { + SFile::Close(this->m_file); + } + this->m_file = nullptr; + + if (this->m_addlHeaderData) { + SMemFree(this->m_addlHeaderData, __FILE__, __LINE__, 0x0); + } + this->m_addlHeaderData = nullptr; + + if (this->m_colorMap) { + SMemFree(this->m_colorMap, __FILE__, __LINE__, 0x0); + } + this->m_colorMap = nullptr; +} + +uint32_t CTgaFile::ColorMapEntryBytes() const { + auto v1 = static_cast(this->m_header.bColorMapEntrySize) / 3; + if (7 < v1) { + v1 = 8; + } + + return (v1 * 3) >> 3; +} + +uint32_t CTgaFile::ColorMapEntries() const { + return this->m_header.wColorMapEntries; +} + +uint32_t CTgaFile::ColorMapBytes() const { + return this->ColorMapEntryBytes() * this->ColorMapEntries(); +} + +uint32_t CTgaFile::PreImageBytes() const { + return sizeof(TGAHeader) + this->m_header.bIDLength + this->ColorMapBytes(); +} + +uint32_t CTgaFile::Width() const { + return this->m_header.wWidth; +} + +uint32_t CTgaFile::Height() const { + return this->m_header.wHeight; +} + +uint32_t CTgaFile::Size() const { + return this->Width() * this->Height(); +} + +uint32_t CTgaFile::BytesPerPixel() const { + return (static_cast(this->m_header.bPixelDepth) + 7) >> 3; +} + +uint32_t CTgaFile::Bytes() const { + return this->BytesPerPixel() * this->Size(); +} + +uint8_t CTgaFile::AlphaBits() const { + return this->m_header.desc.bAlphaChannelBits; +} + +void CTgaFile::SetTopDown(int32_t set) { + if (set) { + if (this->m_header.desc.bTopBottomOrder) { + return; + } + } else if (!this->m_header.desc.bTopBottomOrder) { + return; + } + + STORM_VALIDATE(this->m_header.bImageType >= TGA_RLE_COLOR_MAPPED && this->m_header.bImageType <= TGA_RLE_BLACK_N_WHITE, 0xF7200083); + + auto newImage = static_cast(SMemAlloc(this->Bytes(), __FILE__, __LINE__, 0x0)); + + if (this->Height()) { + auto source = this->m_image; + auto dest = newImage + (this->BytesPerPixel() * this->Width() * (this->Height() - 1)); + + auto bytesPerRow = this->Width() * this->BytesPerPixel(); + + uint32_t row = this->Height(); + while (row) { + row--; + + memcpy(dest, source, bytesPerRow); + + source += bytesPerRow; + dest -= bytesPerRow; + } + } + + SMemFree(this->m_image, __FILE__, __LINE__, 0x0); + + this->m_header.desc.bTopBottomOrder = static_cast(set); + + this->m_image = newImage; +} + +int32_t CTgaFile::ValidateColorDepth() { + if (this->m_header.bPixelDepth - this->m_header.desc.bAlphaChannelBits == 24) { + return 1; + } + + if (this->m_header.bPixelDepth == 16) { + SErrSetLastError(0xF720007C); + } else { + SErrSetLastError(0xF720007D); + } + + return 0; +} + +void CTgaFile::AddAlphaChannel(uint8_t* pAlphaData, uint8_t* pNoAlphaData, const uint8_t* alpha) { + if (alpha) { + for (auto size = this->Size(); size != 0; size--) { + memmove(pAlphaData, pNoAlphaData, this->BytesPerPixel()); + pNoAlphaData += this->BytesPerPixel(); + pAlphaData[this->BytesPerPixel()] = *alpha; + alpha++; + pAlphaData += this->BytesPerPixel() + 1; + } + } else { + for (auto size = this->Size(); size != 0; size--) { + memmove(pAlphaData, pNoAlphaData, this->BytesPerPixel()); + pNoAlphaData += this->BytesPerPixel(); + pAlphaData[this->BytesPerPixel()] = 0xFF; + pAlphaData += this->BytesPerPixel() + 1; + } + } +} + +int32_t CTgaFile::RemoveAlphaChannels() { + if (!this->Image()) { + return 0; + } + + if (this->m_header.desc.bAlphaChannelBits == 0) { + if (this->m_header.bPixelDepth == 24) { + return 1; + } + + SErrSetLastError(0x8720012D); + + if (this->m_header.bPixelDepth == 32) { + this->m_header.desc.bAlphaChannelBits = 8; + } + } + + if (this->m_header.desc.bAlphaChannelBits != 8) { + SErrSetLastError(0xF7200082); + return 0; + } + + this->m_header.bPixelDepth -= 8; + + this->m_header.desc.bAlphaChannelBits = 0; + + auto dest = this->m_image; + auto source = this->m_image; + auto size = this->Size(); + auto stride = this->BytesPerPixel(); + + if (size) { + while (1) { + --size; + memcpy(dest, source, stride); + dest += stride; + source += stride + 1; + if (!size) { + break; + } + } + } + + return 1; +} + +int32_t CTgaFile::AddAlphaChannel(const void* pImg) { + if (!this->Image()) { + return 0; + } + + if (this->m_header.bImageType > 8 && this->m_header.bImageType < 12) { + SErrSetLastError(0xF7200083); + return 0; + } + + if (this->m_header.desc.bAlphaChannelBits) { + this->RemoveAlphaChannels(); + } + + auto newImage = static_cast(SMemAlloc((this->BytesPerPixel() + 1) * this->Size(), __FILE__, __LINE__, 0x0)); + if (!newImage) { + return 0; + } + + this->AddAlphaChannel(newImage, this->m_image, reinterpret_cast(pImg)); + + SMemFree(this->m_image, __FILE__, __LINE__, 0x0); + this->m_image = newImage; + + return 1; +} + +int32_t CTgaFile::ReadRawImage(uint32_t flags) { + int32_t alpha; + + if (!(flags & 0x1) || this->m_header.desc.bAlphaChannelBits == 0) { + alpha = 0; + } else { + alpha = 1; + } + + if (SFile::SetFilePointer(this->m_file, this->PreImageBytes(), nullptr, 0) == 0xFFFFFFFF) { + return 0; + } + + STORM_ASSERT(this->m_image == nullptr); + + this->m_image = static_cast(SMemAlloc(this->Bytes(), __FILE__, __LINE__, 0x0)); + + if (this->m_image == nullptr) { + return 0; + } + + if (!SFile::Read(this->m_file, this->m_image + (alpha * this->Size()), this->Bytes(), nullptr, nullptr, nullptr)) { + return 0; + } + + if (alpha) { + this->AddAlphaChannel(this->m_image, this->m_image + this->Size(), nullptr); + } + + return 1; +} + +int32_t CTgaFile::RLEDecompressImage(uint8_t* pRLEData, uint8_t* pData) { + int32_t pixels = this->Size(); + + while (pixels) { + // fetch control byte from + // input stream + auto byte = *pRLEData++; + // data to read after control byte + auto source = pRLEData; + if (byte & 0x80) { + // run length packet + auto count = (byte & 0x7F) + 1; + auto bytes = this->BytesPerPixel(); + for (auto i = 0; i < count; i++) { + memcpy(pData, source, bytes); + pData += bytes; + } + pixels -= count; + pRLEData += bytes; + } else { + // raw packet + auto count = static_cast(byte) + 1; + auto bytes = this->BytesPerPixel() * count; + memcpy(pData, source, bytes); + pixels -= count; + pData += bytes; + pRLEData += bytes; + } + } + + if (pixels <= -1) { + SErrSetLastError(0xF7200077); + return 0; + } + + this->m_header.bImageType -= 8; + return 1; +} + +int32_t CTgaFile::ReadRleImage(uint32_t flags) { + int32_t alpha; + + if (!(flags & 0x1) || this->m_header.desc.bAlphaChannelBits == 0) { + alpha = 0; + } else { + alpha = 1; + } + + this->m_image = static_cast(SMemAlloc(this->Bytes() + (alpha * this->Size()), __FILE__, __LINE__, 0x0)); + + if (!this->m_image) { + return 0; + } + + auto filesize = SFile::GetFileSize(this->m_file, nullptr); + auto imagelength = filesize - this->PreImageBytes(); + if (imagelength > filesize) { + return 0; + } + auto image = static_cast(SMemAlloc(imagelength, __FILE__, __LINE__, 0x0)); + if (!image) { + return 0; + } + + if (SFile::SetFilePointer(this->m_file, this->PreImageBytes(), nullptr, 0) == -1) { + return 0; + } + if (!SFile::Read(this->m_file, image, imagelength, nullptr, nullptr, nullptr)) { + return 0; + } + + auto decompressed = this->RLEDecompressImage(image, this->m_image + (alpha * this->Size())); + SMemFree(image, __FILE__, __LINE__, 0x0); + if (!decompressed) { + return 0; + } + if (alpha) { + this->AddAlphaChannel(this->m_image, this->m_image + this->Size(), nullptr); + } + + return 1; +} + +void CTgaFile::ConvertColorMapped(uint32_t flags) { + auto pixelDepth = static_cast(this->m_header.desc.bAlphaChannelBits) + 24; + uint32_t newImageSize = ((pixelDepth / 8) * this->Size()) + ((flags & 0x1) * this->Size()); + auto oldImage = this->m_image; + this->m_image = static_cast(SMemAlloc(newImageSize, __FILE__, __LINE__, 0x0)); + + auto dest = this->m_image + (this->Size() * (flags & 0x1)); + auto source = oldImage; + + for (auto i = this->Size(); i; i--) { + memcpy(dest, this->m_colorMap + (this->ColorMapEntryBytes() * (static_cast(*source) - this->m_header.wColorMapStartIndex)), this->ColorMapEntryBytes()); + source++; + dest += this->ColorMapEntryBytes(); + } + + SMemFree(oldImage, __FILE__, __LINE__, 0x0); + + SMemFree(this->m_colorMap, __FILE__, __LINE__, 0x0); + this->m_colorMap = nullptr; + + this->m_header.wColorMapEntries = 0; + + this->m_header.bColorMapType = 0; + this->m_header.bImageType = TGA_TRUE_COLOR; + this->m_header.bPixelDepth = pixelDepth; + this->m_imageBytes = this->Bytes(); + + if (flags & 0x1) { + this->AddAlphaChannel(this->m_image, this->m_image + this->Size(), 0); + } +} + +int32_t CTgaFile::ReadColorMappedImage(uint32_t flags) { + STORM_VALIDATE(this->m_header.bColorMapType, 0xF7200084, 0); + + int32_t status; + + if (this->m_header.bImageType < TGA_RLE_COLOR_MAPPED) { + status = this->ReadRawImage(0); + } else { + status = this->ReadRleImage(0); + } + + if (this->m_header.bColorMapType != TGA_NO_IMAGE_DATA && (flags & 2)) { + this->ConvertColorMapped(flags); + } +} + +int32_t CTgaFile::LoadImageData(uint32_t flags) { + STORM_VALIDATE(this->m_image == nullptr, ERROR_INVALID_PARAMETER, 1); + STORM_VALIDATE(this->m_file, 0xF720007E, 1); + + this->m_imageBytes = this->Bytes(); + + switch (this->m_header.bImageType) { + case TGA_NO_IMAGE_DATA: + SErrSetLastError(0xF7200078); + return 0; + case TGA_COLOR_MAPPED: + case TGA_RLE_COLOR_MAPPED: + return this->ReadColorMappedImage(flags); + case TGA_TRUE_COLOR: + if (!this->ValidateColorDepth()) { + return 0; + } + return this->ReadRawImage(flags); + case TGA_BLACK_N_WHITE: + case TGA_RLE_BLACK_N_WHITE: + SErrSetLastError(0xF720007A); + return 0; + case TGA_RLE_TRUE_COLOR: + if (!this->ValidateColorDepth()) { + return 0; + } + return this->ReadRleImage(flags); + default: + return 0; + } +} + +uint8_t* CTgaFile::Image() { + if (!this->m_image) { + SErrSetLastError(0xF720007F); + return nullptr; + } + return this->m_image; +} + +CTgaFile::TGA32Pixel* CTgaFile::ImageTGA32Pixel() { + auto image = this->Image(); + if (!image) { + return nullptr; + } + + if (this->m_header.bPixelDepth != 32) { + SErrSetLastError(0xF720007D); + return nullptr; + } + + return reinterpret_cast(image); +} diff --git a/src/gx/texture/CTgaFile.hpp b/src/gx/texture/CTgaFile.hpp new file mode 100644 index 0000000..eba46a1 --- /dev/null +++ b/src/gx/texture/CTgaFile.hpp @@ -0,0 +1,100 @@ +#ifndef GX_TEXTURE_C_TGA_FILE_HPP +#define GX_TEXTURE_C_TGA_FILE_HPP + +#include + +#include "util/SFile.hpp" + +class CTgaFile { + private: + enum { + TGA_NO_IMAGE_DATA = 0x0, + TGA_COLOR_MAPPED = 0x1, + TGA_TRUE_COLOR = 0x2, + TGA_BLACK_N_WHITE = 0x3, + TGA_RLE_COLOR_MAPPED = 0x9, + TGA_RLE_TRUE_COLOR = 0xA, + TGA_RLE_BLACK_N_WHITE = 0xB + }; + +// This class casts raw memory into these structures, so pack them tightly +#pragma pack(push, 1) + struct TGAHeader { + uint8_t bIDLength; + uint8_t bColorMapType; + uint8_t bImageType; + // byte packed + uint16_t wColorMapStartIndex; + uint16_t wColorMapEntries; + uint8_t bColorMapEntrySize; + uint16_t wXOrigin; + uint16_t wYOrigin; + uint16_t wWidth; + uint16_t wHeight; + uint8_t bPixelDepth; + union { + uint8_t bImageDescriptor; + struct { + uint8_t bAlphaChannelBits : 4; + uint8_t bLeftRightOrder : 1; + uint8_t bTopBottomOrder : 1; + uint8_t bReserved : 2; + } desc; + }; + }; + + struct TGAFooter { + uint32_t dwExtensionOffset; + uint32_t dwDeveloperOffset; + uint8_t szSigniture[18]; + }; +#pragma pack(pop) + + SFile* m_file = nullptr; + uint8_t* m_image = nullptr; + TGAHeader m_header; + uint8_t* m_addlHeaderData = nullptr; + TGAFooter m_footer; + uint32_t m_imageBytes = 0; + uint8_t* m_colorMap = nullptr; + + private: + void AddAlphaChannel(uint8_t* pAlphaData, uint8_t* pNoAlphaData, const uint8_t* alpha); + int32_t RemoveAlphaChannels(); + int32_t ReadRawImage(uint32_t flags); + int32_t RLEDecompressImage(uint8_t* pRLEData, uint8_t* pData); + int32_t ReadRleImage(uint32_t flags); + int32_t ReadColorMappedImage(uint32_t flags); + int32_t ValidateColorDepth(); + void ConvertColorMapped(uint32_t flags); + uint32_t PreImageBytes() const; + uint32_t ColorMapEntryBytes() const; + uint32_t ColorMapEntries() const; + uint32_t ColorMapBytes() const; + uint32_t BytesPerPixel() const; + // int32_t CountRun(uint8_t* pImage ,int32_t nMax); + // int32_t RleCompressLine(uint8_t** uncompressed, uint8_t** compressed); + + public: + struct TGA32Pixel { + uint8_t b; + uint8_t g; + uint8_t r; + uint8_t a; + }; + + int32_t Open(const char* filename, int32_t a3); + void Close(); + uint32_t Width() const; + uint32_t Height() const; + uint32_t Size() const; + uint32_t Bytes() const; + uint8_t AlphaBits() const; + uint8_t* Image(); + TGA32Pixel* ImageTGA32Pixel(); + int32_t LoadImageData(uint32_t flags); + int32_t AddAlphaChannel(const void* pImg); + void SetTopDown(int32_t set); +}; + +#endif