mirror of
https://github.com/AxioDL/metaforce.git
synced 2025-05-14 23:11:22 +00:00
Avoids indirect inclusions where applicable and includes the necessary headers as used by the interface. This way, it prevents code from failing to compile due to changes in other header inclusions.
1690 lines
51 KiB
C++
1690 lines
51 KiB
C++
#include "DataSpec/DNACommon/TXTR.hpp"
|
|
|
|
#include <cstdint>
|
|
#include <memory>
|
|
|
|
#include "DataSpec/DNACommon/PAK.hpp"
|
|
|
|
#include <athena/FileWriter.hpp>
|
|
#include <hecl/hecl.hpp>
|
|
#include <logvisor/logvisor.hpp>
|
|
#include <png.h>
|
|
#include <squish.h>
|
|
|
|
namespace DataSpec {
|
|
|
|
static logvisor::Module Log("libpng");
|
|
|
|
static int CountBits(uint32_t n) {
|
|
int ret = 0;
|
|
for (int i = 0; i < 32; ++i)
|
|
if (((n >> i) & 1) != 0)
|
|
++ret;
|
|
return ret;
|
|
}
|
|
|
|
/* Box filter algorithm (for mipmapping) */
|
|
static void BoxFilter(const uint8_t* input, unsigned chanCount, unsigned inWidth, unsigned inHeight, uint8_t* output,
|
|
bool dxt1) {
|
|
unsigned mipWidth = 1;
|
|
unsigned mipHeight = 1;
|
|
if (inWidth > 1)
|
|
mipWidth = inWidth / 2;
|
|
if (inHeight > 1)
|
|
mipHeight = inHeight / 2;
|
|
|
|
unsigned y, x, c;
|
|
for (y = 0; y < mipHeight; ++y) {
|
|
unsigned miplineBase = mipWidth * y;
|
|
unsigned in1LineBase = inWidth * (y * 2);
|
|
unsigned in2LineBase = inWidth * (y * 2 + 1);
|
|
for (x = 0; x < mipWidth; ++x) {
|
|
uint8_t* out = &output[(miplineBase + x) * chanCount];
|
|
for (c = 0; c < chanCount; ++c) {
|
|
uint32_t tmp = 0;
|
|
tmp += input[(in1LineBase + (x * 2)) * chanCount + c];
|
|
tmp += input[(in1LineBase + (x * 2 + 1)) * chanCount + c];
|
|
tmp += input[(in2LineBase + (x * 2)) * chanCount + c];
|
|
tmp += input[(in2LineBase + (x * 2 + 1)) * chanCount + c];
|
|
out[c] = uint8_t(tmp / 4);
|
|
if (c == 3 && dxt1)
|
|
out[c] = uint8_t(out[c] ? 0xff : 0x0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static size_t ComputeMippedTexelCount(unsigned inWidth, unsigned inHeight) {
|
|
size_t ret = 0;
|
|
while (inWidth > 0 && inHeight > 0) {
|
|
ret += inWidth * inHeight;
|
|
inWidth /= 2;
|
|
inHeight /= 2;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* GX uses this upsampling technique to extract full 8-bit range */
|
|
constexpr uint8_t Convert3To8(uint8_t v) {
|
|
/* Swizzle bits: 00000123 -> 12312312 */
|
|
return (v << 5) | (v << 2) | (v >> 1);
|
|
}
|
|
|
|
constexpr uint8_t Convert8To3(uint8_t v) { return v >> 5; }
|
|
|
|
constexpr uint8_t Convert4To8(uint8_t v) {
|
|
/* Swizzle bits: 00001234 -> 12341234 */
|
|
return (v << 4) | v;
|
|
}
|
|
|
|
constexpr uint8_t Convert8To4(uint8_t v) { return v >> 4; }
|
|
|
|
constexpr uint8_t Convert5To8(uint8_t v) {
|
|
/* Swizzle bits: 00012345 -> 12345123 */
|
|
return (v << 3) | (v >> 2);
|
|
}
|
|
|
|
constexpr uint8_t Convert8To5(uint8_t v) { return v >> 3; }
|
|
|
|
constexpr uint8_t Convert6To8(uint8_t v) {
|
|
/* Swizzle bits: 00123456 -> 12345612 */
|
|
return (v << 2) | (v >> 4);
|
|
}
|
|
|
|
constexpr uint8_t Convert8To6(uint8_t v) { return v >> 2; }
|
|
|
|
static uint8_t Lookup4BPP(const uint8_t* texels, int width, int x, int y) {
|
|
int bwidth = (width + 7) / 8;
|
|
int bx = x / 8;
|
|
int by = y / 8;
|
|
int rx = x % 8;
|
|
int ry = y % 8;
|
|
int bidx = by * bwidth + bx;
|
|
const uint8_t* btexels = &texels[32 * bidx];
|
|
return btexels[ry * 4 + rx / 2] >> ((rx & 1) ? 0 : 4) & 0xf;
|
|
}
|
|
|
|
static void Set4BPP(uint8_t* texels, int width, int x, int y, uint8_t val) {
|
|
int bwidth = (width + 7) / 8;
|
|
int bx = x / 8;
|
|
int by = y / 8;
|
|
int rx = x % 8;
|
|
int ry = y % 8;
|
|
int bidx = by * bwidth + bx;
|
|
uint8_t* btexels = &texels[32 * bidx];
|
|
btexels[ry * 4 + rx / 2] |= (val & 0xf) << ((rx & 1) ? 0 : 4);
|
|
}
|
|
|
|
static uint8_t Lookup8BPP(const uint8_t* texels, int width, int x, int y) {
|
|
int bwidth = (width + 7) / 8;
|
|
int bx = x / 8;
|
|
int by = y / 4;
|
|
int rx = x % 8;
|
|
int ry = y % 4;
|
|
int bidx = by * bwidth + bx;
|
|
const uint8_t* btexels = &texels[32 * bidx];
|
|
return btexels[ry * 8 + rx];
|
|
}
|
|
|
|
static void Set8BPP(uint8_t* texels, int width, int x, int y, uint8_t val) {
|
|
int bwidth = (width + 7) / 8;
|
|
int bx = x / 8;
|
|
int by = y / 4;
|
|
int rx = x % 8;
|
|
int ry = y % 4;
|
|
int bidx = by * bwidth + bx;
|
|
uint8_t* btexels = &texels[32 * bidx];
|
|
btexels[ry * 8 + rx] = val;
|
|
}
|
|
|
|
static uint16_t Lookup16BPP(const uint8_t* texels, int width, int x, int y) {
|
|
int bwidth = (width + 3) / 4;
|
|
int bx = x / 4;
|
|
int by = y / 4;
|
|
int rx = x % 4;
|
|
int ry = y % 4;
|
|
int bidx = by * bwidth + bx;
|
|
const uint16_t* btexels = (uint16_t*)&texels[32 * bidx];
|
|
return btexels[ry * 4 + rx];
|
|
}
|
|
|
|
static void Set16BPP(uint8_t* texels, int width, int x, int y, uint16_t val) {
|
|
int bwidth = (width + 3) / 4;
|
|
int bx = x / 4;
|
|
int by = y / 4;
|
|
int rx = x % 4;
|
|
int ry = y % 4;
|
|
int bidx = by * bwidth + bx;
|
|
uint16_t* btexels = (uint16_t*)&texels[32 * bidx];
|
|
btexels[ry * 4 + rx] = val;
|
|
}
|
|
|
|
static void LookupRGBA8(const uint8_t* texels, int width, int x, int y, uint8_t* r, uint8_t* g, uint8_t* b,
|
|
uint8_t* a) {
|
|
int bwidth = (width + 3) / 4;
|
|
int bx = x / 4;
|
|
int by = y / 4;
|
|
int rx = x % 4;
|
|
int ry = y % 4;
|
|
int bidx = (by * bwidth + bx) * 2;
|
|
const uint16_t* artexels = (uint16_t*)&texels[32 * bidx];
|
|
const uint16_t* gbtexels = (uint16_t*)&texels[32 * (bidx + 1)];
|
|
uint16_t ar = hecl::SBig(artexels[ry * 4 + rx]);
|
|
*a = ar >> 8 & 0xff;
|
|
*r = ar & 0xff;
|
|
uint16_t gb = hecl::SBig(gbtexels[ry * 4 + rx]);
|
|
*g = gb >> 8 & 0xff;
|
|
*b = gb & 0xff;
|
|
}
|
|
|
|
static void SetRGBA8(uint8_t* texels, int width, int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
|
|
int bwidth = (width + 3) / 4;
|
|
int bx = x / 4;
|
|
int by = y / 4;
|
|
int rx = x % 4;
|
|
int ry = y % 4;
|
|
int bidx = (by * bwidth + bx) * 2;
|
|
uint16_t* artexels = (uint16_t*)&texels[32 * bidx];
|
|
uint16_t* gbtexels = (uint16_t*)&texels[32 * (bidx + 1)];
|
|
uint16_t ar = (a << 8) | r;
|
|
artexels[ry * 4 + rx] = hecl::SBig(ar);
|
|
uint16_t gb = (g << 8) | b;
|
|
gbtexels[ry * 4 + rx] = hecl::SBig(gb);
|
|
}
|
|
|
|
static void DecodeI4(png_structp png, png_infop info, const uint8_t* texels, int width, int height) {
|
|
png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
|
|
PNG_FILTER_TYPE_DEFAULT);
|
|
png_write_info(png, info);
|
|
std::unique_ptr<uint8_t[]> buf(new uint8_t[width]);
|
|
// memset(buf.get(), 0, width);
|
|
for (int y = height - 1; y >= 0; --y) {
|
|
for (int x = 0; x < width; ++x)
|
|
buf[x] = Convert4To8(Lookup4BPP(texels, width, x, y));
|
|
png_write_row(png, buf.get());
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
static void EncodeI4(const uint8_t* rgbaIn, uint8_t* texels, int width, int height)
|
|
{
|
|
for (int y=height-1 ; y>=0 ; --y)
|
|
{
|
|
for (int x=0 ; x<width ; ++x)
|
|
Set4BPP(texels, width, x, y, Convert8To4(rgbaIn[x]));
|
|
rgbaIn += width;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void DecodeI8(png_structp png, png_infop info, const uint8_t* texels, int width, int height) {
|
|
png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
|
|
PNG_FILTER_TYPE_DEFAULT);
|
|
png_write_info(png, info);
|
|
std::unique_ptr<uint8_t[]> buf(new uint8_t[width]);
|
|
for (int y = height - 1; y >= 0; --y) {
|
|
for (int x = 0; x < width; ++x)
|
|
buf[x] = Lookup8BPP(texels, width, x, y);
|
|
png_write_row(png, buf.get());
|
|
}
|
|
}
|
|
|
|
static void EncodeI8(const uint8_t* rgbaIn, uint8_t* texels, int width, int height) {
|
|
for (int y = height - 1; y >= 0; --y) {
|
|
for (int x = 0; x < width; ++x)
|
|
Set8BPP(texels, width, x, y, rgbaIn[x]);
|
|
rgbaIn += width;
|
|
}
|
|
}
|
|
|
|
static void DecodeIA4(png_structp png, png_infop info, const uint8_t* texels, int width, int height) {
|
|
png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_GRAY_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
|
|
PNG_FILTER_TYPE_DEFAULT);
|
|
png_write_info(png, info);
|
|
std::unique_ptr<uint8_t[]> buf(new uint8_t[width * 2]);
|
|
for (int y = height - 1; y >= 0; --y) {
|
|
for (int x = 0; x < width; ++x) {
|
|
uint8_t texel = Lookup8BPP(texels, width, x, y);
|
|
buf[x * 2 ] = Convert4To8(texel & 0xf);
|
|
buf[x * 2 + 1] = Convert4To8(texel >> 4 & 0xf);
|
|
}
|
|
png_write_row(png, buf.get());
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
static void EncodeIA4(const uint8_t* rgbaIn, uint8_t* texels, int width, int height)
|
|
{
|
|
for (int y=height-1 ; y>=0 ; --y)
|
|
{
|
|
for (int x=0 ; x<width ; ++x)
|
|
{
|
|
uint8_t texel = Convert8To4(rgbaIn[x*2+1]) << 4;
|
|
texel |= Convert8To4(rgbaIn[x*2]);
|
|
Set8BPP(texels, width, x, y, texel);
|
|
rgbaIn += width * 2;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void DecodeIA8(png_structp png, png_infop info, const uint8_t* texels, int width, int height) {
|
|
png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_GRAY_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
|
|
PNG_FILTER_TYPE_DEFAULT);
|
|
png_write_info(png, info);
|
|
std::unique_ptr<uint16_t[]> buf(new uint16_t[width]);
|
|
for (int y = height - 1; y >= 0; --y) {
|
|
for (int x = 0; x < width; ++x)
|
|
buf[x] = hecl::SBig(Lookup16BPP(texels, width, x, y));
|
|
png_write_row(png, (png_bytep)buf.get());
|
|
}
|
|
}
|
|
|
|
static void EncodeIA8(const uint8_t* rgbaIn, uint8_t* texels, int width, int height) {
|
|
for (int y = height - 1; y >= 0; --y) {
|
|
for (int x = 0; x < width; ++x)
|
|
Set16BPP(texels, width, x, y, hecl::SBig(((uint16_t*)rgbaIn)[x]));
|
|
rgbaIn += width * 2;
|
|
}
|
|
}
|
|
|
|
static const uint8_t* DecodePalette(png_structp png, png_infop info, int numEntries, const uint8_t* data) {
|
|
uint32_t format = hecl::SBig(*(uint32_t*)data);
|
|
data += 8;
|
|
png_color cEntries[256];
|
|
png_byte aEntries[256];
|
|
switch (format) {
|
|
case 0: {
|
|
/* IA8 */
|
|
for (int e = 0; e < numEntries; ++e) {
|
|
cEntries[e].red = data[e * 2];
|
|
cEntries[e].green = data[e * 2];
|
|
cEntries[e].blue = data[e * 2];
|
|
aEntries[e] = data[e * 2 + 1];
|
|
}
|
|
break;
|
|
}
|
|
case 1: {
|
|
/* RGB565 */
|
|
const uint16_t* data16 = (uint16_t*)data;
|
|
for (int e = 0; e < numEntries; ++e) {
|
|
uint16_t texel = hecl::SBig(data16[e]);
|
|
cEntries[e].red = Convert5To8(texel >> 11 & 0x1f);
|
|
cEntries[e].green = Convert6To8(texel >> 5 & 0x3f);
|
|
cEntries[e].blue = Convert5To8(texel & 0x1f);
|
|
}
|
|
break;
|
|
}
|
|
case 2: {
|
|
/* RGB5A3 */
|
|
const uint16_t* data16 = (uint16_t*)data;
|
|
for (int e = 0; e < numEntries; ++e) {
|
|
uint16_t texel = hecl::SBig(data16[e]);
|
|
if (texel & 0x8000) {
|
|
cEntries[e].red = Convert5To8(texel >> 10 & 0x1f);
|
|
cEntries[e].green = Convert5To8(texel >> 5 & 0x1f);
|
|
cEntries[e].blue = Convert5To8(texel & 0x1f);
|
|
aEntries[e] = 0xff;
|
|
} else {
|
|
cEntries[e].red = Convert4To8(texel >> 8 & 0xf);
|
|
cEntries[e].green = Convert4To8(texel >> 4 & 0xf);
|
|
cEntries[e].blue = Convert4To8(texel & 0xf);
|
|
aEntries[e] = Convert3To8(texel >> 12 & 0x7);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
png_set_PLTE(png, info, cEntries, numEntries);
|
|
if (format == 0 || format == 2)
|
|
png_set_tRNS(png, info, aEntries, numEntries, nullptr);
|
|
data += numEntries * 2;
|
|
return data;
|
|
}
|
|
|
|
static uint8_t* EncodePalette(png_structp png, png_infop info, int numEntries, uint8_t* data) {
|
|
png_colorp cEntries;
|
|
int pngNumEntries;
|
|
if (png_get_PLTE(png, info, &cEntries, &pngNumEntries) != PNG_INFO_PLTE) {
|
|
cEntries = nullptr;
|
|
pngNumEntries = 0;
|
|
}
|
|
|
|
png_bytep aEntries;
|
|
int pngNumAEntries;
|
|
png_color_16p trans_color = nullptr;
|
|
if (png_get_tRNS(png, info, &aEntries, &pngNumAEntries, &trans_color) != PNG_INFO_tRNS) {
|
|
aEntries = nullptr;
|
|
pngNumAEntries = 0;
|
|
}
|
|
|
|
uint32_t format = 0; /* Default IA8 */
|
|
for (int e = 0; e < pngNumEntries; ++e) {
|
|
png_colorp ent = &cEntries[e];
|
|
if (ent->red != ent->green || ent->red != ent->blue) {
|
|
if (pngNumAEntries)
|
|
format = 2; /* RGB565 if not greyscale and has alpha */
|
|
else
|
|
format = 1; /* RGB565 if not greyscale */
|
|
break;
|
|
}
|
|
}
|
|
|
|
((uint32_t*)data)[0] = hecl::SBig(format);
|
|
data += 4;
|
|
((uint16_t*)data)[0] = hecl::SBig(uint16_t(numEntries));
|
|
((uint16_t*)data)[1] = hecl::SBig(uint16_t(1));
|
|
data += 4;
|
|
|
|
switch (format) {
|
|
case 0: {
|
|
/* IA8 */
|
|
for (int e = 0; e < numEntries; ++e) {
|
|
if (e < pngNumEntries)
|
|
data[e * 2] = cEntries[e].green;
|
|
else
|
|
data[e * 2] = 0;
|
|
if (e < pngNumAEntries)
|
|
data[e * 2 + 1] = aEntries[e];
|
|
else
|
|
data[e * 2 + 1] = 0;
|
|
}
|
|
break;
|
|
}
|
|
case 1: {
|
|
/* RGB565 */
|
|
uint16_t* data16 = (uint16_t*)data;
|
|
for (int e = 0; e < numEntries; ++e) {
|
|
if (e < pngNumEntries) {
|
|
uint16_t texel = Convert8To5(cEntries[e].red) << 11;
|
|
texel |= Convert8To6(cEntries[e].green) << 5;
|
|
texel |= Convert8To5(cEntries[e].blue);
|
|
data16[e] = hecl::SBig(texel);
|
|
} else {
|
|
data16[e] = 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 2: {
|
|
/* RGB5A3 */
|
|
uint16_t* data16 = (uint16_t*)data;
|
|
for (int e = 0; e < numEntries; ++e) {
|
|
uint8_t alpha = 0;
|
|
if (e < pngNumAEntries)
|
|
alpha = aEntries[e];
|
|
|
|
uint16_t texel = 0;
|
|
if (alpha == 0xff) {
|
|
texel |= 0x8000;
|
|
if (e < pngNumEntries) {
|
|
texel |= Convert8To5(cEntries[e].red) << 10;
|
|
texel |= Convert8To5(cEntries[e].green) << 5;
|
|
texel |= Convert8To5(cEntries[e].blue);
|
|
}
|
|
} else {
|
|
if (e < pngNumEntries) {
|
|
texel |= Convert8To4(cEntries[e].red) << 8;
|
|
texel |= Convert8To4(cEntries[e].green) << 4;
|
|
texel |= Convert8To4(cEntries[e].blue);
|
|
texel |= Convert8To3(alpha << 12);
|
|
}
|
|
}
|
|
data16[e] = hecl::SBig(texel);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
data += numEntries * 2;
|
|
return data;
|
|
}
|
|
|
|
static const uint8_t* DecodePaletteSPLT(png_structp png, png_infop info, int numEntries, const uint8_t* data) {
|
|
uint32_t format = hecl::SBig(*(uint32_t*)data);
|
|
data += 8;
|
|
png_sPLT_entry entries[256] = {};
|
|
png_sPLT_t GXEntry = {(char*)"GXPalette", 8, entries, numEntries};
|
|
switch (format) {
|
|
case 0: {
|
|
/* IA8 */
|
|
GXEntry.name = (char*)"GX_IA8";
|
|
for (int e = 0; e < numEntries; ++e) {
|
|
entries[e].red = data[e * 2];
|
|
entries[e].green = data[e * 2];
|
|
entries[e].blue = data[e * 2];
|
|
entries[e].alpha = data[e * 2 + 1];
|
|
}
|
|
break;
|
|
}
|
|
case 1: {
|
|
/* RGB565 */
|
|
GXEntry.name = (char*)"GX_RGB565";
|
|
const uint16_t* data16 = (uint16_t*)data;
|
|
for (int e = 0; e < numEntries; ++e) {
|
|
uint16_t texel = hecl::SBig(data16[e]);
|
|
entries[e].red = Convert5To8(texel >> 11 & 0x1f);
|
|
entries[e].green = Convert6To8(texel >> 5 & 0x3f);
|
|
entries[e].blue = Convert5To8(texel & 0x1f);
|
|
entries[e].alpha = 0xff;
|
|
}
|
|
break;
|
|
}
|
|
case 2: {
|
|
/* RGB5A3 */
|
|
GXEntry.name = (char*)"GX_RGB5A3";
|
|
const uint16_t* data16 = (uint16_t*)data;
|
|
for (int e = 0; e < numEntries; ++e) {
|
|
uint16_t texel = hecl::SBig(data16[e]);
|
|
if (texel & 0x8000) {
|
|
entries[e].red = Convert5To8(texel >> 10 & 0x1f);
|
|
entries[e].green = Convert5To8(texel >> 5 & 0x1f);
|
|
entries[e].blue = Convert5To8(texel & 0x1f);
|
|
entries[e].alpha = 0xff;
|
|
} else {
|
|
entries[e].red = Convert4To8(texel >> 8 & 0xf);
|
|
entries[e].green = Convert4To8(texel >> 4 & 0xf);
|
|
entries[e].blue = Convert4To8(texel & 0xf);
|
|
entries[e].alpha = Convert3To8(texel >> 12 & 0x7);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
png_set_sPLT(png, info, &GXEntry, 1);
|
|
data += numEntries * 2;
|
|
return data;
|
|
}
|
|
|
|
static uint8_t* EncodePaletteSPLT(png_structp png, png_infop info, int numEntries, uint8_t* data) {
|
|
png_sPLT_tp palettes;
|
|
int pngNumPalettes = png_get_sPLT(png, info, &palettes);
|
|
int pngNumEntries = 0;
|
|
png_sPLT_entryp cEntries = nullptr;
|
|
for (int i = 0; i < pngNumPalettes; ++i) {
|
|
png_sPLT_tp palette = &palettes[i];
|
|
if (!strncmp(palette->name, "GX_", 3)) {
|
|
pngNumEntries = palette->nentries;
|
|
cEntries = palette->entries;
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint32_t format = 2; /* Default RGB5A3 */
|
|
for (int e = 0; e < pngNumEntries; ++e) {
|
|
png_sPLT_entryp ent = &cEntries[e];
|
|
if (ent->red != ent->green || ent->red != ent->blue) {
|
|
if (ent->alpha) {
|
|
format = 2;
|
|
break;
|
|
} else
|
|
format = 1;
|
|
}
|
|
}
|
|
|
|
((uint32_t*)data)[0] = hecl::SBig(format);
|
|
data += 4;
|
|
((uint16_t*)data)[0] = hecl::SBig(uint16_t(1));
|
|
((uint16_t*)data)[1] = hecl::SBig(uint16_t(numEntries));
|
|
data += 4;
|
|
|
|
switch (format) {
|
|
case 0: {
|
|
/* IA8 */
|
|
for (int e = 0; e < numEntries; ++e) {
|
|
if (e < pngNumEntries) {
|
|
data[e * 2] = cEntries[e].green;
|
|
data[e * 2 + 1] = cEntries[e].alpha;
|
|
} else {
|
|
data[e * 2] = 0;
|
|
data[e * 2 + 1] = 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 1: {
|
|
/* RGB565 */
|
|
uint16_t* data16 = (uint16_t*)data;
|
|
for (int e = 0; e < numEntries; ++e) {
|
|
if (e < pngNumEntries) {
|
|
uint16_t texel = Convert8To5(cEntries[e].red) << 11;
|
|
texel |= Convert8To6(cEntries[e].green) << 5;
|
|
texel |= Convert8To5(cEntries[e].blue);
|
|
data16[e] = hecl::SBig(texel);
|
|
} else {
|
|
data16[e] = 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 2: {
|
|
/* RGB5A3 */
|
|
uint16_t* data16 = (uint16_t*)data;
|
|
for (int e = 0; e < numEntries; ++e) {
|
|
uint16_t texel = 0;
|
|
if (cEntries && cEntries[e].alpha == 0xff) {
|
|
texel |= 0x8000;
|
|
if (e < pngNumEntries) {
|
|
texel |= Convert8To5(cEntries[e].red) << 10;
|
|
texel |= Convert8To5(cEntries[e].green) << 5;
|
|
texel |= Convert8To5(cEntries[e].blue);
|
|
}
|
|
} else {
|
|
if (e < pngNumEntries) {
|
|
texel |= Convert8To4(cEntries[e].red) << 8;
|
|
texel |= Convert8To4(cEntries[e].green) << 4;
|
|
texel |= Convert8To4(cEntries[e].blue);
|
|
texel |= Convert8To3(cEntries[e].alpha << 12);
|
|
}
|
|
}
|
|
data16[e] = hecl::SBig(texel);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
data += numEntries * 2;
|
|
return data;
|
|
}
|
|
|
|
static const png_color C4Colors[] = {
|
|
{0, 0, 0}, {155, 0, 0}, {0, 155, 0}, {0, 0, 155}, {155, 155, 0}, {155, 0, 155}, {0, 155, 155}, {155, 155, 155},
|
|
{55, 55, 55}, {255, 0, 0}, {0, 255, 0}, {0, 0, 255}, {255, 255, 0}, {255, 0, 255}, {0, 255, 255}, {255, 255, 255}};
|
|
|
|
static void C4Palette(png_structp png, png_infop info) { png_set_PLTE(png, info, C4Colors, 16); }
|
|
|
|
static void DecodeC4(png_structp png, png_infop info, const uint8_t* data, int width, int height) {
|
|
png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
|
|
PNG_FILTER_TYPE_DEFAULT);
|
|
C4Palette(png, info);
|
|
const uint8_t* texels = DecodePaletteSPLT(png, info, 16, data);
|
|
png_write_info(png, info);
|
|
std::unique_ptr<uint8_t[]> buf(new uint8_t[width]);
|
|
for (int y = 0; y < height; ++y) {
|
|
for (int x = 0; x < width; ++x)
|
|
buf[x] = Lookup4BPP(texels, width, x, y);
|
|
png_write_row(png, buf.get());
|
|
}
|
|
}
|
|
|
|
static void EncodeC4(png_structp png, png_infop info, const uint8_t* rgbaIn, uint8_t* data, int width, int height) {
|
|
uint8_t* texels = EncodePaletteSPLT(png, info, 16, data);
|
|
for (int y = 0; y < height; ++y) {
|
|
for (int x = 0; x < width; ++x)
|
|
Set4BPP(texels, width, x, y, rgbaIn[x]);
|
|
rgbaIn += width;
|
|
}
|
|
}
|
|
|
|
static void DecodeC8(png_structp png, png_infop info, const uint8_t* data, int width, int height) {
|
|
png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
|
|
PNG_FILTER_TYPE_DEFAULT);
|
|
const uint8_t* texels = DecodePalette(png, info, 256, data);
|
|
png_write_info(png, info);
|
|
std::unique_ptr<uint8_t[]> buf(new uint8_t[width]);
|
|
for (int y = 0; y < height; ++y) {
|
|
for (int x = 0; x < width; ++x)
|
|
buf[x] = Lookup8BPP(texels, width, x, y);
|
|
png_write_row(png, buf.get());
|
|
}
|
|
}
|
|
|
|
static void EncodeC8(png_structp png, png_infop info, const uint8_t* rgbaIn, uint8_t* data, int width, int height) {
|
|
uint8_t* texels = EncodePalette(png, info, 256, data);
|
|
for (int y = 0; y < height; ++y) {
|
|
for (int x = 0; x < width; ++x)
|
|
Set8BPP(texels, width, x, y, rgbaIn[x]);
|
|
rgbaIn += width;
|
|
}
|
|
}
|
|
|
|
static void DecodeRGB565(png_structp png, png_infop info, const uint8_t* texels, int width, int height) {
|
|
png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
|
|
PNG_FILTER_TYPE_DEFAULT);
|
|
png_write_info(png, info);
|
|
std::unique_ptr<uint8_t[]> buf(new uint8_t[width * 3]);
|
|
for (int y = height - 1; y >= 0; --y) {
|
|
for (int x = 0; x < width; ++x) {
|
|
uint16_t texel = hecl::SBig(Lookup16BPP(texels, width, x, y));
|
|
buf[x * 3] = Convert5To8(texel >> 11 & 0x1f);
|
|
buf[x * 3 + 1] = Convert6To8(texel >> 5 & 0x3f);
|
|
buf[x * 3 + 2] = Convert5To8(texel & 0x1f);
|
|
}
|
|
png_write_row(png, buf.get());
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
static void EncodeRGB565(const uint8_t* rgbaIn, uint8_t* texels, int width, int height)
|
|
{
|
|
for (int y=height-1 ; y>=0 ; --y)
|
|
{
|
|
for (int x=0 ; x<width ; ++x)
|
|
{
|
|
uint16_t texel = Convert8To5(rgbaIn[x*3]) << 11;
|
|
texel |= Convert8To6(rgbaIn[x*3+1]) << 5;
|
|
texel |= Convert8To5(rgbaIn[x*3+2]);
|
|
Set16BPP(texels, width, x, y, hecl::SBig(texel));
|
|
}
|
|
rgbaIn += width * 3;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void DecodeRGB5A3(png_structp png, png_infop info, const uint8_t* texels, int width, int height) {
|
|
png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
|
|
PNG_FILTER_TYPE_DEFAULT);
|
|
png_write_info(png, info);
|
|
std::unique_ptr<uint8_t[]> buf(new uint8_t[width * 4]);
|
|
for (int y = height - 1; y >= 0; --y) {
|
|
for (int x = 0; x < width; ++x) {
|
|
uint16_t texel = hecl::SBig(Lookup16BPP(texels, width, x, y));
|
|
if (texel & 0x8000) {
|
|
buf[x * 4] = Convert5To8(texel >> 10 & 0x1f);
|
|
buf[x * 4 + 1] = Convert5To8(texel >> 5 & 0x1f);
|
|
buf[x * 4 + 2] = Convert5To8(texel & 0x1f);
|
|
buf[x * 4 + 3] = 0xff;
|
|
} else {
|
|
buf[x * 4] = Convert4To8(texel >> 8 & 0xf);
|
|
buf[x * 4 + 1] = Convert4To8(texel >> 4 & 0xf);
|
|
buf[x * 4 + 2] = Convert4To8(texel & 0xf);
|
|
buf[x * 4 + 3] = Convert3To8(texel >> 12 & 0x7);
|
|
}
|
|
}
|
|
png_write_row(png, buf.get());
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
static void EncodeRGB5A3(const uint8_t* rgbaIn, uint8_t* texels, int width, int height)
|
|
{
|
|
for (int y=height-1 ; y>=0 ; --y)
|
|
{
|
|
for (int x=0 ; x<width ; ++x)
|
|
{
|
|
uint16_t texel = 0;
|
|
if (rgbaIn[x*4+3] == 0xff)
|
|
{
|
|
texel |= Convert8To5(rgbaIn[x*4]) << 10;
|
|
texel |= Convert8To5(rgbaIn[x*4+1]) << 5;
|
|
texel |= Convert8To5(rgbaIn[x*4+2]);
|
|
texel |= 0x8000;
|
|
}
|
|
else
|
|
{
|
|
texel |= Convert8To4(rgbaIn[x*4]) << 8;
|
|
texel |= Convert8To4(rgbaIn[x*4+1]) << 4;
|
|
texel |= Convert8To4(rgbaIn[x*4+2]);
|
|
texel |= Convert8To3(rgbaIn[x*4+3]) << 12;
|
|
}
|
|
Set16BPP(texels, width, x, y, hecl::SBig(texel));
|
|
}
|
|
rgbaIn += width * 4;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void DecodeRGBA8(png_structp png, png_infop info, const uint8_t* texels, int width, int height) {
|
|
png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
|
|
PNG_FILTER_TYPE_DEFAULT);
|
|
png_write_info(png, info);
|
|
std::unique_ptr<uint8_t[]> buf(new uint8_t[width * 4]);
|
|
for (int y = height - 1; y >= 0; --y) {
|
|
for (int x = 0; x < width; ++x)
|
|
LookupRGBA8(texels, width, x, y, &buf[x * 4], &buf[x * 4 + 1], &buf[x * 4 + 2], &buf[x * 4 + 3]);
|
|
png_write_row(png, buf.get());
|
|
}
|
|
}
|
|
|
|
static void EncodeRGBA8(const uint8_t* rgbaIn, uint8_t* texels, int width, int height) {
|
|
for (int y = height - 1; y >= 0; --y) {
|
|
for (int x = 0; x < width; ++x)
|
|
SetRGBA8(texels, width, x, y, rgbaIn[x * 4], rgbaIn[x * 4 + 1], rgbaIn[x * 4 + 2], rgbaIn[x * 4 + 3]);
|
|
rgbaIn += width * 4;
|
|
}
|
|
}
|
|
|
|
struct DXTBlock {
|
|
uint16_t color1;
|
|
uint16_t color2;
|
|
uint8_t lines[4];
|
|
};
|
|
|
|
static void DecodeCMPR(png_structp png, png_infop info, const uint8_t* texels, int width, int height) {
|
|
png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
|
|
PNG_FILTER_TYPE_DEFAULT);
|
|
png_write_info(png, info);
|
|
|
|
/* Decode 8 rows at a time */
|
|
int bwidth = (width + 7) / 8;
|
|
int bpwidth = bwidth * 8;
|
|
std::unique_ptr<uint32_t[]> buf(new uint32_t[bpwidth * 8]);
|
|
uint32_t* bTargets[4] = {buf.get(), buf.get() + 4, buf.get() + 4 * width, buf.get() + 4 * width + 4};
|
|
for (int y = height / 8 - 1; y >= 0; --y) {
|
|
const DXTBlock* blks = (DXTBlock*)(texels + 32 * bwidth * y);
|
|
for (int x = 0; x < width; x += 8) {
|
|
uint32_t blkOut[4][4][4];
|
|
squish::Decompress((uint8_t*)blkOut[0][0], blks++, squish::kDxt1GCN);
|
|
squish::Decompress((uint8_t*)blkOut[1][0], blks++, squish::kDxt1GCN);
|
|
squish::Decompress((uint8_t*)blkOut[2][0], blks++, squish::kDxt1GCN);
|
|
squish::Decompress((uint8_t*)blkOut[3][0], blks++, squish::kDxt1GCN);
|
|
|
|
for (int bt = 0; bt < 4; ++bt)
|
|
for (int by = 0; by < 4; ++by)
|
|
memcpy(bTargets[bt] + x + width * by, blkOut[bt][by], 16);
|
|
}
|
|
for (int r = 7; r >= 0; --r)
|
|
png_write_row(png, (png_bytep)(bTargets[0] + width * r));
|
|
}
|
|
}
|
|
|
|
static void EncodeCMPR(const uint8_t* rgbaIn, uint8_t* texels, int width, int height) {
|
|
/* Encode 8 rows at a time */
|
|
int bwidth = (width + 7) / 8;
|
|
int bpwidth = bwidth * 8;
|
|
std::unique_ptr<uint32_t[]> buf(new uint32_t[bpwidth * 8]);
|
|
uint32_t* bTargets[4] = {buf.get(), buf.get() + 4, buf.get() + 4 * width, buf.get() + 4 * width + 4};
|
|
for (int y = height / 8 - 1; y >= 0; --y) {
|
|
for (int r = 7; r >= 0; --r) {
|
|
memcpy(bTargets[0] + width * r, rgbaIn, width * 4);
|
|
rgbaIn += width * 4;
|
|
}
|
|
DXTBlock* blks = (DXTBlock*)(texels + 32 * bwidth * y);
|
|
for (int x = 0; x < width; x += 8) {
|
|
uint32_t blkIn[4][4][4];
|
|
for (int bt = 0; bt < 4; ++bt)
|
|
for (int by = 0; by < 4; ++by)
|
|
memcpy(blkIn[bt][by], bTargets[bt] + x + width * by, 16);
|
|
|
|
squish::Compress((uint8_t*)blkIn[0][0], blks++, squish::kDxt1GCN);
|
|
squish::Compress((uint8_t*)blkIn[1][0], blks++, squish::kDxt1GCN);
|
|
squish::Compress((uint8_t*)blkIn[2][0], blks++, squish::kDxt1GCN);
|
|
squish::Compress((uint8_t*)blkIn[3][0], blks++, squish::kDxt1GCN);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void PNGErr(png_structp png, png_const_charp msg) { Log.report(logvisor::Error, fmt("{}"), msg); }
|
|
|
|
static void PNGWarn(png_structp png, png_const_charp msg) { Log.report(logvisor::Warning, fmt("{}"), msg); }
|
|
|
|
bool TXTR::Extract(PAKEntryReadStream& rs, const hecl::ProjectPath& outPath) {
|
|
uint32_t format = rs.readUint32Big();
|
|
uint16_t width = rs.readUint16Big();
|
|
uint16_t height = rs.readUint16Big();
|
|
uint32_t numMips = rs.readUint32Big();
|
|
|
|
FILE* fp = hecl::Fopen(outPath.getAbsolutePath().data(), _SYS_STR("wb"));
|
|
if (!fp) {
|
|
Log.report(logvisor::Error, fmt(_SYS_STR("Unable to open '{}' for writing")), outPath.getAbsolutePath());
|
|
return false;
|
|
}
|
|
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, PNGErr, PNGWarn);
|
|
png_init_io(png, fp);
|
|
png_infop info = png_create_info_struct(png);
|
|
|
|
png_text textStruct = {};
|
|
textStruct.key = png_charp("urde_nomip");
|
|
if (numMips == 1)
|
|
png_set_text(png, info, &textStruct, 1);
|
|
|
|
switch (format) {
|
|
case 0:
|
|
DecodeI4(png, info, rs.data() + 12, width, height);
|
|
break;
|
|
case 1:
|
|
DecodeI8(png, info, rs.data() + 12, width, height);
|
|
break;
|
|
case 2:
|
|
DecodeIA4(png, info, rs.data() + 12, width, height);
|
|
break;
|
|
case 3:
|
|
DecodeIA8(png, info, rs.data() + 12, width, height);
|
|
break;
|
|
case 4:
|
|
DecodeC4(png, info, rs.data() + 12, width, height);
|
|
break;
|
|
case 5:
|
|
DecodeC8(png, info, rs.data() + 12, width, height);
|
|
break;
|
|
case 7:
|
|
DecodeRGB565(png, info, rs.data() + 12, width, height);
|
|
break;
|
|
case 8:
|
|
DecodeRGB5A3(png, info, rs.data() + 12, width, height);
|
|
break;
|
|
case 9:
|
|
DecodeRGBA8(png, info, rs.data() + 12, width, height);
|
|
break;
|
|
case 10:
|
|
DecodeCMPR(png, info, rs.data() + 12, width, height);
|
|
break;
|
|
}
|
|
|
|
png_write_end(png, info);
|
|
png_write_flush(png);
|
|
png_destroy_write_struct(&png, &info);
|
|
fclose(fp);
|
|
|
|
return true;
|
|
}
|
|
|
|
static std::unique_ptr<uint8_t[]> ReadPalette(png_structp png, png_infop info, size_t& szOut) {
|
|
std::unique_ptr<uint8_t[]> ret;
|
|
png_sPLT_tp palettes;
|
|
int paletteCount = png_get_sPLT(png, info, &palettes);
|
|
if (paletteCount) {
|
|
for (int i = 0; i < paletteCount; ++i) {
|
|
png_sPLT_tp palette = &palettes[i];
|
|
if (!strncmp(palette->name, "GX_", 3)) {
|
|
if (palette->nentries > 16) {
|
|
/* This is a C8 palette */
|
|
ret.reset(new uint8_t[4 * 257]);
|
|
szOut = 4 * 257;
|
|
*reinterpret_cast<uint32_t*>(ret.get()) = hecl::SBig(256);
|
|
uint8_t* cur = ret.get() + 4;
|
|
for (int j = 0; j < 256; ++j) {
|
|
if (j < palette->nentries) {
|
|
png_sPLT_entryp entry = &palette->entries[j];
|
|
if (palette->depth == 16) {
|
|
*cur++ = entry->red >> 8;
|
|
*cur++ = entry->green >> 8;
|
|
*cur++ = entry->blue >> 8;
|
|
*cur++ = entry->alpha >> 8;
|
|
} else {
|
|
*cur++ = entry->red;
|
|
*cur++ = entry->green;
|
|
*cur++ = entry->blue;
|
|
*cur++ = entry->alpha;
|
|
}
|
|
} else {
|
|
*cur++ = 0;
|
|
*cur++ = 0;
|
|
*cur++ = 0;
|
|
*cur++ = 0;
|
|
}
|
|
}
|
|
} else {
|
|
/* This is a C4 palette */
|
|
ret.reset(new uint8_t[4 * 17]);
|
|
szOut = 4 * 17;
|
|
*reinterpret_cast<uint32_t*>(ret.get()) = hecl::SBig(16);
|
|
uint8_t* cur = ret.get() + 4;
|
|
for (int j = 0; j < 16; ++j) {
|
|
if (j < palette->nentries) {
|
|
png_sPLT_entryp entry = &palette->entries[j];
|
|
if (palette->depth == 16) {
|
|
*cur++ = entry->red >> 8;
|
|
*cur++ = entry->green >> 8;
|
|
*cur++ = entry->blue >> 8;
|
|
*cur++ = entry->alpha >> 8;
|
|
} else {
|
|
*cur++ = entry->red;
|
|
*cur++ = entry->green;
|
|
*cur++ = entry->blue;
|
|
*cur++ = entry->alpha;
|
|
}
|
|
} else {
|
|
*cur++ = 0;
|
|
*cur++ = 0;
|
|
*cur++ = 0;
|
|
*cur++ = 0;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
png_colorp palettes;
|
|
int colorCount;
|
|
if (png_get_PLTE(png, info, &palettes, &colorCount) == PNG_INFO_PLTE) {
|
|
if (colorCount > 16) {
|
|
/* This is a C8 palette */
|
|
ret.reset(new uint8_t[4 * 257]);
|
|
szOut = 4 * 257;
|
|
*reinterpret_cast<uint32_t*>(ret.get()) = hecl::SBig(256);
|
|
uint8_t* cur = ret.get() + 4;
|
|
for (int j = 0; j < 256; ++j) {
|
|
if (j < colorCount) {
|
|
png_colorp entry = &palettes[j];
|
|
*cur++ = entry->red;
|
|
*cur++ = entry->green;
|
|
*cur++ = entry->blue;
|
|
*cur++ = 0xff;
|
|
} else {
|
|
*cur++ = 0;
|
|
*cur++ = 0;
|
|
*cur++ = 0;
|
|
*cur++ = 0;
|
|
}
|
|
}
|
|
} else {
|
|
/* This is a C4 palette */
|
|
ret.reset(new uint8_t[4 * 17]);
|
|
szOut = 4 * 17;
|
|
*reinterpret_cast<uint32_t*>(ret.get()) = hecl::SBig(16);
|
|
uint8_t* cur = ret.get() + 4;
|
|
for (int j = 0; j < 16; ++j) {
|
|
if (j < colorCount) {
|
|
png_colorp entry = &palettes[j];
|
|
*cur++ = entry->red;
|
|
*cur++ = entry->green;
|
|
*cur++ = entry->blue;
|
|
*cur++ = 0xff;
|
|
} else {
|
|
*cur++ = 0;
|
|
*cur++ = 0;
|
|
*cur++ = 0;
|
|
*cur++ = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int GetNumPaletteEntriesForGCN(png_structp png, png_infop info) {
|
|
png_sPLT_tp palettes;
|
|
int paletteCount = png_get_sPLT(png, info, &palettes);
|
|
if (paletteCount) {
|
|
for (int i = 0; i < paletteCount; ++i) {
|
|
png_sPLT_tp palette = &palettes[i];
|
|
if (!strncmp(palette->name, "GX_", 3)) {
|
|
if (palette->nentries > 16) {
|
|
/* This is a C8 palette */
|
|
return 256;
|
|
} else {
|
|
/* This is a C4 palette */
|
|
return 16;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
png_colorp palettes;
|
|
int colorCount;
|
|
if (png_get_PLTE(png, info, &palettes, &colorCount) == PNG_INFO_PLTE) {
|
|
if (colorCount > 16) {
|
|
/* This is a C8 palette */
|
|
return 256;
|
|
} else {
|
|
/* This is a C4 palette */
|
|
return 16;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool TXTR::Cook(const hecl::ProjectPath& inPath, const hecl::ProjectPath& outPath) {
|
|
FILE* inf = hecl::Fopen(inPath.getAbsolutePath().data(), _SYS_STR("rb"));
|
|
if (!inf) {
|
|
Log.report(logvisor::Error, fmt(_SYS_STR("Unable to open '{}' for reading")), inPath.getAbsolutePath());
|
|
return false;
|
|
}
|
|
|
|
/* Validate PNG */
|
|
char header[8];
|
|
fread(header, 1, 8, inf);
|
|
if (png_sig_cmp((png_const_bytep)header, 0, 8)) {
|
|
Log.report(logvisor::Error, fmt(_SYS_STR("invalid PNG signature in '{}'")), inPath.getAbsolutePath());
|
|
fclose(inf);
|
|
return false;
|
|
}
|
|
|
|
/* Setup PNG reader */
|
|
png_structp pngRead = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
|
if (!pngRead) {
|
|
Log.report(logvisor::Error, fmt("unable to initialize libpng"));
|
|
fclose(inf);
|
|
return false;
|
|
}
|
|
png_infop info = png_create_info_struct(pngRead);
|
|
if (!info) {
|
|
Log.report(logvisor::Error, fmt("unable to initialize libpng info"));
|
|
fclose(inf);
|
|
png_destroy_read_struct(&pngRead, nullptr, nullptr);
|
|
return false;
|
|
}
|
|
|
|
if (setjmp(png_jmpbuf(pngRead))) {
|
|
Log.report(logvisor::Error, fmt(_SYS_STR("unable to initialize libpng I/O for '{}'")), inPath.getAbsolutePath());
|
|
fclose(inf);
|
|
png_destroy_read_struct(&pngRead, &info, nullptr);
|
|
return false;
|
|
}
|
|
|
|
png_init_io(pngRead, inf);
|
|
png_set_sig_bytes(pngRead, 8);
|
|
|
|
png_read_info(pngRead, info);
|
|
|
|
png_uint_32 width = png_get_image_width(pngRead, info);
|
|
png_uint_32 height = png_get_image_height(pngRead, info);
|
|
png_byte colorType = png_get_color_type(pngRead, info);
|
|
png_byte bitDepth = png_get_bit_depth(pngRead, info);
|
|
|
|
if (width < 4 || height < 4) {
|
|
Log.report(logvisor::Error, fmt("image must be 4x4 or larger"));
|
|
fclose(inf);
|
|
png_destroy_read_struct(&pngRead, &info, nullptr);
|
|
return false;
|
|
}
|
|
|
|
/* Disable mipmapping if urde_nomip embedded */
|
|
bool mipmap = true;
|
|
png_text* textStruct;
|
|
int numText;
|
|
png_get_text(pngRead, info, &textStruct, &numText);
|
|
for (int i = 0; i < numText; ++i)
|
|
if (!strcmp(textStruct[i].key, "urde_nomip"))
|
|
mipmap = false;
|
|
if (colorType == PNG_COLOR_TYPE_PALETTE)
|
|
mipmap = false;
|
|
|
|
/* Compute mipmap levels */
|
|
size_t numMips = 1;
|
|
if (mipmap && CountBits(width) == 1 && CountBits(height) == 1) {
|
|
size_t index = std::min(width, height);
|
|
while (index >>= 1)
|
|
++numMips;
|
|
}
|
|
|
|
if (bitDepth != 8) {
|
|
Log.report(logvisor::Error, fmt(_SYS_STR("'{}' is not 8 bits-per-channel")), inPath.getAbsolutePath());
|
|
fclose(inf);
|
|
png_destroy_read_struct(&pngRead, &info, nullptr);
|
|
return false;
|
|
}
|
|
|
|
size_t rowSize = 0;
|
|
size_t nComps = 4;
|
|
int nPaletteEntries = 0;
|
|
switch (colorType) {
|
|
case PNG_COLOR_TYPE_GRAY:
|
|
rowSize = width;
|
|
nComps = 1;
|
|
break;
|
|
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
|
rowSize = width * 2;
|
|
nComps = 2;
|
|
break;
|
|
case PNG_COLOR_TYPE_RGB:
|
|
rowSize = width * 3;
|
|
nComps = 4;
|
|
break;
|
|
case PNG_COLOR_TYPE_RGB_ALPHA:
|
|
rowSize = width * 4;
|
|
nComps = 4;
|
|
break;
|
|
case PNG_COLOR_TYPE_PALETTE:
|
|
nPaletteEntries = GetNumPaletteEntriesForGCN(pngRead, info);
|
|
rowSize = width;
|
|
nComps = 1;
|
|
break;
|
|
default:
|
|
Log.report(logvisor::Error, fmt(_SYS_STR("unsupported color type in '{}'")), inPath.getAbsolutePath());
|
|
fclose(inf);
|
|
png_destroy_read_struct(&pngRead, &info, nullptr);
|
|
return false;
|
|
}
|
|
|
|
/* Intermediate row-read buf (file components) */
|
|
std::unique_ptr<uint8_t[]> rowBuf;
|
|
if (colorType == PNG_COLOR_TYPE_RGB)
|
|
rowBuf.reset(new uint8_t[rowSize]);
|
|
|
|
/* Final mipmapped buf (RGBA components) */
|
|
std::unique_ptr<uint8_t[]> bufOut;
|
|
size_t bufLen = 0;
|
|
if (numMips > 1)
|
|
bufLen = ComputeMippedTexelCount(width, height) * nComps;
|
|
else
|
|
bufLen = width * height * nComps;
|
|
bufOut.reset(new uint8_t[bufLen]);
|
|
|
|
if (setjmp(png_jmpbuf(pngRead))) {
|
|
Log.report(logvisor::Fatal, fmt(_SYS_STR("unable to read image in '{}'")), inPath.getAbsolutePath());
|
|
fclose(inf);
|
|
png_destroy_read_struct(&pngRead, &info, nullptr);
|
|
return false;
|
|
}
|
|
|
|
/* Track alpha values for DXT1 eligibility */
|
|
bool doDXT1 = (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_RGB_ALPHA) && width >= 4 && height >= 4;
|
|
|
|
/* Read into mip0 image buffer */
|
|
for (png_uint_32 r = 0; r < height; ++r) {
|
|
if (colorType == PNG_COLOR_TYPE_RGB) {
|
|
png_read_row(pngRead, rowBuf.get(), nullptr);
|
|
for (unsigned i = 0; i < width; ++i) {
|
|
size_t inbase = i * 3;
|
|
size_t outbase = (r * width + i) * 4;
|
|
bufOut[outbase] = rowBuf[inbase];
|
|
bufOut[outbase + 1] = rowBuf[inbase + 1];
|
|
bufOut[outbase + 2] = rowBuf[inbase + 2];
|
|
bufOut[outbase + 3] = 0xff;
|
|
}
|
|
} else {
|
|
png_read_row(pngRead, &bufOut[(r * width) * nComps], nullptr);
|
|
if (colorType == PNG_COLOR_TYPE_RGB_ALPHA) {
|
|
for (unsigned i = 0; i < width; ++i) {
|
|
size_t outbase = (r * width + i) * nComps;
|
|
if (bufOut[outbase + 3] != 0 && bufOut[outbase + 3] != 255)
|
|
doDXT1 = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
png_destroy_read_struct(&pngRead, &info, nullptr);
|
|
fclose(inf);
|
|
|
|
/* Reduce mipmaps to minimum allowed dimensions */
|
|
unsigned minDimX, minDimY;
|
|
if (doDXT1) {
|
|
minDimX = minDimY = 4;
|
|
} else {
|
|
switch (colorType) {
|
|
case PNG_COLOR_TYPE_GRAY:
|
|
minDimX = 8;
|
|
minDimY = 4;
|
|
break;
|
|
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
|
case PNG_COLOR_TYPE_RGB:
|
|
case PNG_COLOR_TYPE_RGB_ALPHA:
|
|
default:
|
|
minDimX = 4;
|
|
minDimY = 4;
|
|
break;
|
|
case PNG_COLOR_TYPE_PALETTE: {
|
|
if (nPaletteEntries == 256) {
|
|
minDimX = 8;
|
|
minDimY = 4;
|
|
} else {
|
|
minDimX = minDimY = 8;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
{
|
|
unsigned totalPixels = 0;
|
|
unsigned filterWidth = width;
|
|
unsigned filterHeight = height;
|
|
for (size_t i = 0; i < numMips; ++i) {
|
|
totalPixels += filterWidth * filterHeight;
|
|
if (filterWidth == minDimX || filterHeight == minDimY) {
|
|
numMips = i + 1;
|
|
break;
|
|
}
|
|
filterWidth /= 2;
|
|
filterHeight /= 2;
|
|
}
|
|
bufLen = totalPixels * nComps;
|
|
}
|
|
|
|
/* Perform box-filter mipmap */
|
|
std::unique_ptr<uint8_t[]> compOut;
|
|
size_t compLen = 0;
|
|
if (numMips > 1) {
|
|
const uint8_t* filterIn = bufOut.get();
|
|
uint8_t* filterOut = bufOut.get() + width * height * nComps;
|
|
unsigned filterWidth = width;
|
|
unsigned filterHeight = height;
|
|
for (size_t i = 1; i < numMips; ++i) {
|
|
BoxFilter(filterIn, nComps, filterWidth, filterHeight, filterOut, doDXT1);
|
|
filterIn += filterWidth * filterHeight * nComps;
|
|
filterWidth /= 2;
|
|
filterHeight /= 2;
|
|
filterOut += filterWidth * filterHeight * nComps;
|
|
}
|
|
}
|
|
|
|
/* Do DXT1 compression */
|
|
int format;
|
|
if (doDXT1) {
|
|
int filterWidth = width;
|
|
int filterHeight = height;
|
|
for (size_t i = 0; i < numMips; ++i) {
|
|
compLen += squish::GetStorageRequirements(filterWidth, filterHeight, squish::kDxt1);
|
|
if (filterWidth == 8 || filterHeight == 8) {
|
|
numMips = i + 1;
|
|
break;
|
|
}
|
|
filterWidth /= 2;
|
|
filterHeight /= 2;
|
|
}
|
|
|
|
compOut.reset(new uint8_t[compLen]);
|
|
|
|
filterWidth = width;
|
|
filterHeight = height;
|
|
const uint8_t* rgbaIn = bufOut.get();
|
|
uint8_t* blocksOut = compOut.get();
|
|
memset(blocksOut, 0, compLen);
|
|
for (size_t i = 0; i < numMips; ++i) {
|
|
int thisLen = squish::GetStorageRequirements(filterWidth, filterHeight, squish::kDxt1);
|
|
EncodeCMPR(rgbaIn, blocksOut, filterWidth, filterHeight);
|
|
rgbaIn += filterWidth * filterHeight * nComps;
|
|
blocksOut += thisLen;
|
|
filterWidth /= 2;
|
|
filterHeight /= 2;
|
|
}
|
|
|
|
format = 10;
|
|
} else {
|
|
int filterWidth = width;
|
|
int filterHeight = height;
|
|
compLen = bufLen;
|
|
if (colorType == PNG_COLOR_TYPE_PALETTE) {
|
|
if (nPaletteEntries == 16)
|
|
compLen /= 2;
|
|
compLen += 8 + nPaletteEntries * 2;
|
|
}
|
|
compOut.reset(new uint8_t[compLen]);
|
|
const uint8_t* rgbaIn = bufOut.get();
|
|
uint8_t* dataOut = compOut.get();
|
|
memset(dataOut, 0, compLen);
|
|
for (size_t i = 0; i < numMips; ++i) {
|
|
switch (colorType) {
|
|
case PNG_COLOR_TYPE_GRAY:
|
|
EncodeI8(rgbaIn, dataOut, filterWidth, filterHeight);
|
|
format = 1;
|
|
break;
|
|
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
|
EncodeIA8(rgbaIn, dataOut, filterWidth, filterHeight);
|
|
format = 3;
|
|
break;
|
|
case PNG_COLOR_TYPE_RGB:
|
|
case PNG_COLOR_TYPE_RGB_ALPHA:
|
|
EncodeRGBA8(rgbaIn, dataOut, filterWidth, filterHeight);
|
|
format = 9;
|
|
break;
|
|
case PNG_COLOR_TYPE_PALETTE: {
|
|
if (nPaletteEntries == 256) {
|
|
EncodeC8(pngRead, info, rgbaIn, dataOut, filterWidth, filterHeight);
|
|
format = 5;
|
|
} else {
|
|
EncodeC4(pngRead, info, rgbaIn, dataOut, filterWidth, filterHeight);
|
|
format = 4;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
rgbaIn += filterWidth * filterHeight * nComps;
|
|
dataOut += filterWidth * filterHeight * nComps;
|
|
filterWidth /= 2;
|
|
filterHeight /= 2;
|
|
}
|
|
}
|
|
|
|
/* Do write out */
|
|
athena::io::FileWriter outf(outPath.getAbsolutePath(), true, false);
|
|
if (outf.hasError()) {
|
|
Log.report(logvisor::Error, fmt(_SYS_STR("Unable to open '{}' for writing")), outPath.getAbsolutePath());
|
|
return false;
|
|
}
|
|
|
|
outf.writeInt32Big(format);
|
|
outf.writeInt16Big(width);
|
|
outf.writeInt16Big(height);
|
|
outf.writeInt32Big(numMips);
|
|
outf.writeUBytes(compOut.get(), compLen);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TXTR::CookPC(const hecl::ProjectPath& inPath, const hecl::ProjectPath& outPath) {
|
|
FILE* inf = hecl::Fopen(inPath.getAbsolutePath().data(), _SYS_STR("rb"));
|
|
if (!inf) {
|
|
Log.report(logvisor::Error, fmt(_SYS_STR("Unable to open '{}' for reading")), inPath.getAbsolutePath());
|
|
return false;
|
|
}
|
|
|
|
/* Validate PNG */
|
|
char header[8];
|
|
fread(header, 1, 8, inf);
|
|
if (png_sig_cmp((png_const_bytep)header, 0, 8)) {
|
|
Log.report(logvisor::Error, fmt(_SYS_STR("invalid PNG signature in '{}'")), inPath.getAbsolutePath());
|
|
fclose(inf);
|
|
return false;
|
|
}
|
|
|
|
/* Setup PNG reader */
|
|
png_structp pngRead = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
|
if (!pngRead) {
|
|
Log.report(logvisor::Error, fmt("unable to initialize libpng"));
|
|
fclose(inf);
|
|
return false;
|
|
}
|
|
png_infop info = png_create_info_struct(pngRead);
|
|
if (!info) {
|
|
Log.report(logvisor::Error, fmt("unable to initialize libpng info"));
|
|
fclose(inf);
|
|
png_destroy_read_struct(&pngRead, nullptr, nullptr);
|
|
return false;
|
|
}
|
|
|
|
if (setjmp(png_jmpbuf(pngRead))) {
|
|
Log.report(logvisor::Error, fmt(_SYS_STR("unable to initialize libpng I/O for '{}'")), inPath.getAbsolutePath());
|
|
fclose(inf);
|
|
png_destroy_read_struct(&pngRead, &info, nullptr);
|
|
return false;
|
|
}
|
|
|
|
png_init_io(pngRead, inf);
|
|
png_set_sig_bytes(pngRead, 8);
|
|
|
|
png_read_info(pngRead, info);
|
|
|
|
png_uint_32 width = png_get_image_width(pngRead, info);
|
|
png_uint_32 height = png_get_image_height(pngRead, info);
|
|
png_byte colorType = png_get_color_type(pngRead, info);
|
|
png_byte bitDepth = png_get_bit_depth(pngRead, info);
|
|
|
|
/* Disable mipmapping if urde_nomip embedded */
|
|
bool mipmap = true;
|
|
png_text* textStruct;
|
|
int numText;
|
|
png_get_text(pngRead, info, &textStruct, &numText);
|
|
for (int i = 0; i < numText; ++i)
|
|
if (!strcmp(textStruct[i].key, "urde_nomip"))
|
|
mipmap = false;
|
|
if (colorType == PNG_COLOR_TYPE_PALETTE)
|
|
mipmap = false;
|
|
|
|
/* Compute mipmap levels */
|
|
size_t numMips = 1;
|
|
if (mipmap && CountBits(width) == 1 && CountBits(height) == 1) {
|
|
size_t index = std::min(width, height);
|
|
while (index >>= 1)
|
|
++numMips;
|
|
}
|
|
|
|
if (bitDepth != 8) {
|
|
Log.report(logvisor::Error, fmt(_SYS_STR("'{}' is not 8 bits-per-channel")), inPath.getAbsolutePath());
|
|
fclose(inf);
|
|
png_destroy_read_struct(&pngRead, &info, nullptr);
|
|
return false;
|
|
}
|
|
|
|
std::unique_ptr<uint8_t[]> paletteBuf;
|
|
size_t paletteSize = 0;
|
|
|
|
size_t rowSize = 0;
|
|
size_t nComps = 4;
|
|
switch (colorType) {
|
|
case PNG_COLOR_TYPE_GRAY:
|
|
rowSize = width;
|
|
break;
|
|
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
|
rowSize = width * 2;
|
|
break;
|
|
case PNG_COLOR_TYPE_RGB:
|
|
rowSize = width * 3;
|
|
break;
|
|
case PNG_COLOR_TYPE_RGB_ALPHA:
|
|
rowSize = width * 4;
|
|
break;
|
|
case PNG_COLOR_TYPE_PALETTE:
|
|
rowSize = width;
|
|
nComps = 1;
|
|
paletteBuf = ReadPalette(pngRead, info, paletteSize);
|
|
break;
|
|
default:
|
|
Log.report(logvisor::Error, fmt(_SYS_STR("unsupported color type in '{}'")), inPath.getAbsolutePath());
|
|
fclose(inf);
|
|
png_destroy_read_struct(&pngRead, &info, nullptr);
|
|
return false;
|
|
}
|
|
|
|
/* Intermediate row-read buf (file components) */
|
|
std::unique_ptr<uint8_t[]> rowBuf(new uint8_t[rowSize]);
|
|
|
|
/* Final mipmapped buf (RGBA components) */
|
|
std::unique_ptr<uint8_t[]> bufOut;
|
|
size_t bufLen = 0;
|
|
if (numMips > 1)
|
|
bufLen = ComputeMippedTexelCount(width, height) * nComps;
|
|
else
|
|
bufLen = width * height * nComps;
|
|
bufOut.reset(new uint8_t[bufLen]);
|
|
|
|
if (setjmp(png_jmpbuf(pngRead))) {
|
|
Log.report(logvisor::Fatal, fmt(_SYS_STR("unable to read image in '{}'")), inPath.getAbsolutePath());
|
|
fclose(inf);
|
|
png_destroy_read_struct(&pngRead, &info, nullptr);
|
|
return false;
|
|
}
|
|
|
|
/* Track alpha values for DXT1 eligibility */
|
|
bool doDXT = (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_RGB_ALPHA) && width >= 4 && height >= 4;
|
|
bool doDXT3 = false;
|
|
|
|
/* Read and make RGBA */
|
|
for (int r = height - 1; r >= 0; --r) {
|
|
png_read_row(pngRead, rowBuf.get(), nullptr);
|
|
switch (colorType) {
|
|
case PNG_COLOR_TYPE_GRAY:
|
|
for (unsigned i = 0; i < width; ++i) {
|
|
size_t outbase = (r * width + i) * 4;
|
|
bufOut[outbase] = rowBuf[i];
|
|
bufOut[outbase + 1] = rowBuf[i];
|
|
bufOut[outbase + 2] = rowBuf[i];
|
|
bufOut[outbase + 3] = rowBuf[i];
|
|
}
|
|
break;
|
|
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
|
for (unsigned i = 0; i < width; ++i) {
|
|
size_t inbase = i * 2;
|
|
size_t outbase = (r * width + i) * 4;
|
|
bufOut[outbase] = rowBuf[inbase];
|
|
bufOut[outbase + 1] = rowBuf[inbase];
|
|
bufOut[outbase + 2] = rowBuf[inbase];
|
|
bufOut[outbase + 3] = rowBuf[inbase + 1];
|
|
}
|
|
break;
|
|
case PNG_COLOR_TYPE_RGB:
|
|
for (unsigned i = 0; i < width; ++i) {
|
|
size_t inbase = i * 3;
|
|
size_t outbase = (r * width + i) * 4;
|
|
bufOut[outbase] = rowBuf[inbase];
|
|
bufOut[outbase + 1] = rowBuf[inbase + 1];
|
|
bufOut[outbase + 2] = rowBuf[inbase + 2];
|
|
bufOut[outbase + 3] = 0xff;
|
|
}
|
|
break;
|
|
case PNG_COLOR_TYPE_RGB_ALPHA:
|
|
for (unsigned i = 0; i < width; ++i) {
|
|
size_t inbase = i * 4;
|
|
size_t outbase = (r * width + i) * 4;
|
|
bufOut[outbase] = rowBuf[inbase];
|
|
bufOut[outbase + 1] = rowBuf[inbase + 1];
|
|
bufOut[outbase + 2] = rowBuf[inbase + 2];
|
|
bufOut[outbase + 3] = rowBuf[inbase + 3];
|
|
if (rowBuf[inbase + 3] != 0 && rowBuf[inbase + 3] != 255)
|
|
doDXT = false;
|
|
else if (rowBuf[inbase + 3] == 0)
|
|
doDXT3 = true;
|
|
}
|
|
break;
|
|
case PNG_COLOR_TYPE_PALETTE:
|
|
for (unsigned i = 0; i < width; ++i)
|
|
bufOut[r * width + i] = rowBuf[i];
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
png_destroy_read_struct(&pngRead, &info, nullptr);
|
|
fclose(inf);
|
|
|
|
/* Perform box-filter mipmap */
|
|
if (numMips > 1) {
|
|
const uint8_t* filterIn = bufOut.get();
|
|
uint8_t* filterOut = bufOut.get() + width * height * nComps;
|
|
unsigned filterWidth = width;
|
|
unsigned filterHeight = height;
|
|
for (size_t i = 1; i < numMips; ++i) {
|
|
BoxFilter(filterIn, nComps, filterWidth, filterHeight, filterOut, doDXT);
|
|
filterIn += filterWidth * filterHeight * nComps;
|
|
filterWidth /= 2;
|
|
filterHeight /= 2;
|
|
filterOut += filterWidth * filterHeight * nComps;
|
|
}
|
|
}
|
|
|
|
/* Do DXT compression */
|
|
std::unique_ptr<uint8_t[]> compOut;
|
|
size_t compLen = 0;
|
|
if (doDXT) {
|
|
int compFlags = doDXT3 ? squish::kDxt3 : squish::kDxt1;
|
|
int filterWidth = width;
|
|
int filterHeight = height;
|
|
size_t i;
|
|
for (i = 0; i < numMips; ++i) {
|
|
compLen += squish::GetStorageRequirements(filterWidth, filterHeight, compFlags);
|
|
if (filterWidth == 4 || filterHeight == 4) {
|
|
++i;
|
|
break;
|
|
}
|
|
filterWidth /= 2;
|
|
filterHeight /= 2;
|
|
}
|
|
numMips = i;
|
|
|
|
compOut.reset(new uint8_t[compLen]);
|
|
|
|
filterWidth = width;
|
|
filterHeight = height;
|
|
const uint8_t* rgbaIn = bufOut.get();
|
|
uint8_t* blocksOut = compOut.get();
|
|
for (i = 0; i < numMips; ++i) {
|
|
int thisLen = squish::GetStorageRequirements(filterWidth, filterHeight, compFlags);
|
|
squish::CompressImage(rgbaIn, filterWidth, filterHeight, blocksOut, compFlags);
|
|
rgbaIn += filterWidth * filterHeight * nComps;
|
|
blocksOut += thisLen;
|
|
filterWidth /= 2;
|
|
filterHeight /= 2;
|
|
}
|
|
}
|
|
|
|
/* Do write out */
|
|
athena::io::FileWriter outf(outPath.getAbsolutePath(), true, false);
|
|
if (outf.hasError()) {
|
|
Log.report(logvisor::Error, fmt(_SYS_STR("Unable to open '{}' for writing")), outPath.getAbsolutePath());
|
|
return false;
|
|
}
|
|
|
|
int format;
|
|
if (paletteBuf && paletteSize)
|
|
format = 17;
|
|
else if (compOut)
|
|
format = doDXT3 ? 19 : 18;
|
|
else
|
|
format = 16;
|
|
outf.writeInt32Big(format);
|
|
outf.writeInt16Big(width);
|
|
outf.writeInt16Big(height);
|
|
outf.writeInt32Big(numMips);
|
|
if (paletteBuf && paletteSize)
|
|
outf.writeUBytes(paletteBuf.get(), paletteSize);
|
|
if (compOut)
|
|
outf.writeUBytes(compOut.get(), compLen);
|
|
else
|
|
outf.writeUBytes(bufOut.get(), bufLen);
|
|
|
|
return true;
|
|
}
|
|
|
|
template <class Op>
|
|
void DataSpec::TXTR::PaletteMeta::Enumerate(typename Op::StreamT& s) {
|
|
Do<Op>({"format"}, format, s);
|
|
Do<Op>({"elementCount"}, elementCount, s);
|
|
Do<Op>({"dolphinHash"}, dolphinHash, s);
|
|
}
|
|
|
|
AT_SPECIALIZE_DNA_YAML(DataSpec::TXTR::PaletteMeta)
|
|
|
|
const char* DataSpec::TXTR::PaletteMeta::DNAType() {
|
|
return "DataSpec::TXTR::PaletteMeta";
|
|
}
|
|
|
|
template <class Op>
|
|
void DataSpec::TXTR::Meta::Enumerate(typename Op::StreamT& s) {
|
|
Do<Op>({"format"}, format, s);
|
|
Do<Op>({"mips"}, mips, s);
|
|
Do<Op>({"width"}, width, s);
|
|
Do<Op>({"height"}, height, s);
|
|
Do<Op>({"dolphinHash"}, dolphinHash, s);
|
|
Do<Op>({"hasPalette"}, hasPalette, s);
|
|
if (hasPalette)
|
|
Do<Op>({"palette"}, palette, s);
|
|
}
|
|
|
|
AT_SPECIALIZE_DNA_YAML(DataSpec::TXTR::Meta)
|
|
|
|
const char* DataSpec::TXTR::Meta::DNAType() {
|
|
return "DataSpec::TXTR::Meta";
|
|
}
|
|
|
|
static const atInt32 RetroToDol[11] {
|
|
0, 1, 2, 3, 8, 9, -1, 4, 5, 6, 14
|
|
};
|
|
|
|
TXTR::Meta TXTR::GetMetaData(DataSpec::PAKEntryReadStream& rs) {
|
|
atUint32 retroFormat = rs.readUint32Big();
|
|
atUint32 format = RetroToDol[retroFormat];
|
|
if (format == UINT32_MAX)
|
|
return {};
|
|
|
|
Meta meta;
|
|
meta.format = retroFormat;
|
|
meta.width = rs.readUint16Big();
|
|
meta.height = rs.readUint16Big();
|
|
meta.mips = rs.readUint32Big();
|
|
atUint32 textureSize = meta.width * meta.height;
|
|
if (format == 8 || format == 9) {
|
|
meta.hasPalette = true;
|
|
PaletteMeta& palMeta = meta.palette;
|
|
palMeta.format = rs.readUint32Big();
|
|
atUint16 palWidth = rs.readUint16Big();
|
|
atUint16 palHeight = rs.readUint16Big();
|
|
palMeta.elementCount = palWidth * palHeight;
|
|
atUint32 palSize = atUint32(palWidth * palHeight * 2);
|
|
if (format == 8)
|
|
textureSize /= 2;
|
|
std::unique_ptr<u8[]> palData(new u8[palSize]);
|
|
rs.readUBytesToBuf(palData.get(), palSize);
|
|
palMeta.dolphinHash = XXH64(palData.get(), palSize, 0);
|
|
} else {
|
|
switch(format) {
|
|
case 0: // I4
|
|
case 14: // DXT1
|
|
textureSize /= 2;
|
|
break;
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
textureSize *= 2;
|
|
break;
|
|
case 6:
|
|
textureSize *= 4;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
std::unique_ptr<u8[]> textureData(new u8[textureSize]);
|
|
rs.readUBytesToBuf(textureData.get(), textureSize);
|
|
meta.dolphinHash = XXH64(textureData.get(), textureSize, 0);
|
|
|
|
return meta;
|
|
}
|
|
|
|
} // namespace DataSpec
|