#include #include #include "TXTR.hpp" namespace Retro { static LogVisor::LogModule Log("libpng"); /* GX uses this upsampling technique to prevent banding on downsampled texture formats */ static inline uint8_t Convert3To8(uint8_t v) { /* Swizzle bits: 00000123 -> 12312312 */ return (v << 5) | (v << 2) | (v >> 1); } static inline uint8_t Convert4To8(uint8_t v) { /* Swizzle bits: 00001234 -> 12341234 */ return (v << 4) | v; } static inline uint8_t Convert5To8(uint8_t v) { /* Swizzle bits: 00012345 -> 12345123 */ return (v << 3) | (v >> 2); } static inline uint8_t Convert6To8(uint8_t v) { /* Swizzle bits: 00123456 -> 12345612 */ return (v << 2) | (v >> 4); } static inline 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%2)?0:4) & 0xf; } static inline 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 inline 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 inline 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 DecodeI4(png_structrp 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 buf(new uint8_t[width]); //memset(buf.get(), 0, width); for (int y=height-1 ; y>=0 ; --y) { for (int x=0 ; x buf(new uint8_t[width]); for (int y=height-1 ; y>=0 ; --y) { for (int x=0 ; x buf(new uint8_t[width*2]); for (int y=height-1 ; y>=0 ; --y) { for (int x=0 ; x> 4 & 0xf; buf[x*2+1] = texel & 0xf; } png_write_row(png, buf.get()); } } static void DecodeIA8(png_structrp 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 buf(new uint16_t[width]); for (int y=height-1 ; y>=0 ; --y) { for (int x=0 ; x> 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> 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 const uint8_t* DecodePaletteSPLT(png_structrp 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> 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> 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 void C4Palette(png_structrp png, png_infop info) { 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} }; png_set_PLTE(png, info, C4Colors, 16); } static void DecodeC4(png_structrp 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 buf(new uint8_t[width]); for (int y=0 ; y buf(new uint8_t[width]); for (int y=0 ; y buf(new uint8_t[width*3]); for (int y=height-1 ; y>=0 ; --y) { for (int x=0 ; x> 11 & 0x1f); buf[x*3+1] = Convert6To8(texel >> 5 & 0x3f); buf[x*3+2] = Convert5To8(texel & 0x1f); } png_write_row(png, buf.get()); } } static void DecodeRGB5A3(png_structrp 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 buf(new uint8_t[width*4]); for (int y=height-1 ; y>=0 ; --y) { for (int x=0 ; x> 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()); } } static void DecodeRGBA8(png_structrp 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 buf(new uint8_t[width*4]); for (int y=height-1 ; y>=0 ; --y) { for (int x=0 ; x buf(new uint32_t[width*8]); uint32_t* bTargets[4] = { buf.get(), buf.get() + 4, buf.get() + 4 * width, buf.get() + 4 * width + 4 }; int bwidth = (width + 7) / 8; for (int y=height/8-1 ; y>=0 ; --y) { const DXTBlock* blks = (DXTBlock*)(texels + 32 * bwidth * y); for (int x=0 ; x=0 ; --r) png_write_row(png, (png_bytep)(bTargets[0] + width * r)); } } static void PNGErr(png_structp png, png_const_charp msg) { Log.report(LogVisor::Error, msg); } static void PNGWarn(png_structp png, png_const_charp msg) { Log.report(LogVisor::Warning, msg); } bool TXTR::Extract(const SpecBase& dataspec, 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().c_str(), _S("wb")); if (!fp) { Log.report(LogVisor::Error, _S("Unable to open '%s' for writing"), outPath.getAbsolutePath().c_str()); 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); 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; } bool TXTR::Cook(const HECL::ProjectPath& inPath, const HECL::ProjectPath& outPath) { return false; } }