mirror of
https://github.com/thunderbrewhq/thunderbrew
synced 2025-12-12 11:12:29 +00:00
feat(texture): implemented TextureLoadImage API, also support loading and mipping TGA files
This commit is contained in:
parent
3425aefc73
commit
c6e1751bbe
7 changed files with 1336 additions and 90 deletions
|
|
@ -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…
Add table
Add a link
Reference in a new issue