mirror of
https://github.com/thunderbrewhq/thunderbrew
synced 2025-04-09 23:06:47 +03:00
feat(texture): implemented TextureLoadImage API, also support loading and mipping TGA files
This commit is contained in:
parent
3425aefc73
commit
c6e1751bbe
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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*);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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*);
|
||||
};
|
||||
|
||||
|
@ -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
475
src/gx/texture/CTgaFile.cpp
Normal 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
100
src/gx/texture/CTgaFile.hpp
Normal 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
|
Loading…
Reference in New Issue
Block a user