feat(texture): implemented TextureLoadImage API, also support loading and mipping TGA files

This commit is contained in:
superp00t 2024-09-06 12:31:08 -04:00
parent 3425aefc73
commit c6e1751bbe
7 changed files with 1336 additions and 90 deletions

View File

@ -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 <algorithm>
@ -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<C4Pixel*>(reinterpret_cast<uintptr_t>(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);
}
}

View File

@ -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*);

View File

@ -1,11 +1,72 @@
#include "gx/Texture.hpp"
#include "gx/texture/CBLPFile.hpp"
#include "util/SFile.hpp"
#include "util/Unimplemented.hpp"
#include <cstring>
#include <storm/Error.hpp>
#include <storm/Memory.hpp>
TSGrowableArray<unsigned char> CBLPFile::s_blpFileLoadBuffer;
TSGrowableArray<uint8_t> 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<MIPS_TYPE>(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<uint8_t*>(tempbuffer);
for (auto i = colorSize; i; i--) {
*reinterpret_cast<BlpPalPixel*>(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<uint8_t*>(tempbuffer);
for (auto i = colorSize; i; i--) {
*reinterpret_cast<BlpPalPixel*>(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<unsigned char*>(this->m_inMemoryImage) + this->m_header.mipOffsets[mipLevel];
uint8_t* mipData = static_cast<uint8_t*>(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<char*>(this->m_inMemoryImage) + *offset;
MipBits* image = static_cast<MipBits*>(address);
reinterpret_cast<MipBits**>(images)[i] = image;
void* address = static_cast<uint8_t*>(this->m_inMemoryImage) + *offset;
images->mip[i] = reinterpret_cast<C4Pixel*>(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<MipBits**>(images);
for (int32_t level = mipLevel, i = 0; level < this->m_numLevels; level++, i++) {
if (!this->Lock2(fileName, format, level, reinterpret_cast<unsigned char*>(ptr[i]), mipLevel)) {
if (!this->Lock2(fileName, format, level, reinterpret_cast<uint8_t*>(images->mip[i]), mipLevel)) {
return 0;
}
}

View File

@ -6,6 +6,12 @@
#include <cstdint>
#include <storm/Array.hpp>
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<unsigned char> s_blpFileLoadBuffer;
static TSGrowableArray<uint8_t> 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*);
};

View File

@ -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()

475
src/gx/texture/CTgaFile.cpp Normal file
View File

@ -0,0 +1,475 @@
#include "gx/texture/CTgaFile.hpp"
#include <storm/Memory.hpp>
#include <storm/Error.hpp>
#include <cstring>
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<uint8_t*>(SMemAlloc(this->m_header.bIDLength, __FILE__, __LINE__, 0x0));
if (!SFile::Read(this->m_file, static_cast<void*>(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<uint8_t*>(SMemAlloc(this->ColorMapBytes(), __FILE__, __LINE__, 0x0));
if (!SFile::Read(this->m_file, static_cast<void*>(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<uint32_t>(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<uint32_t>(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<uint8_t*>(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<uint8_t>(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<uint8_t*>(SMemAlloc((this->BytesPerPixel() + 1) * this->Size(), __FILE__, __LINE__, 0x0));
if (!newImage) {
return 0;
}
this->AddAlphaChannel(newImage, this->m_image, reinterpret_cast<const uint8_t*>(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<uint8_t*>(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<uint32_t>(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<uint8_t*>(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<uint8_t*>(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<uint32_t>(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<uint8_t*>(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<uint32_t>(*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<TGA32Pixel*>(image);
}

100
src/gx/texture/CTgaFile.hpp Normal file
View File

@ -0,0 +1,100 @@
#ifndef GX_TEXTURE_C_TGA_FILE_HPP
#define GX_TEXTURE_C_TGA_FILE_HPP
#include <cstdint>
#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