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

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

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