#include #include #include "TXTR.hpp" #include "PAK.hpp" #include "athena/FileWriter.hpp" 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) { 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 0 && inHeight > 0) { ret += inWidth * inHeight; inWidth /= 2; inHeight /= 2; } return ret; } /* GX uses this upsampling technique to extract full 8-bit range */ 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&1)?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 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_structrp png, png_infop info) { 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[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=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(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); 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; } bool TXTR::Cook(const hecl::ProjectPath& inPath, const hecl::ProjectPath& outPath) { return false; } bool TXTR::CookPC(const hecl::ProjectPath& inPath, const hecl::ProjectPath& outPath) { FILE* inf = hecl::Fopen(inPath.getAbsolutePath().c_str(), _S("rb")); if (!inf) { Log.report(logvisor::Error, _S("Unable to open '%s' for reading"), inPath.getAbsolutePath().c_str()); 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, _S("invalid PNG signature in '%s'"), inPath.getAbsolutePath().c_str()); 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, "unable to initialize libpng"); fclose(inf); return false; } png_infop info = png_create_info_struct(pngRead); if (!info) { Log.report(logvisor::Error, "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, _S("unable to initialize libpng I/O for '%s'"), inPath.getAbsolutePath().c_str()); 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>= 1) ++numMips; } if (bitDepth != 8) { Log.report(logvisor::Error, _S("'%s' is not 8 bits-per-channel"), inPath.getAbsolutePath().c_str()); fclose(inf); png_destroy_read_struct(&pngRead, &info, nullptr); return false; } size_t rowSize = 0; 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; default: Log.report(logvisor::Error, _S("unsupported color type in '%s'"), inPath.getAbsolutePath().c_str()); fclose(inf); png_destroy_read_struct(&pngRead, &info, nullptr); return false; } /* Intermediate row-read buf (file components) */ std::unique_ptr rowBuf(new uint8_t[rowSize]); /* Final mipmapped buf (RGBA components) */ std::unique_ptr bufOut; size_t bufLen = 0; if (numMips > 1) bufLen = ComputeMippedTexelCount(width, height) * 4; else bufLen = width * height * 4; bufOut.reset(new uint8_t[bufLen]); if (setjmp(png_jmpbuf(pngRead))) { Log.report(logvisor::Error, _S("unable to read image in '%s'"), inPath.getAbsolutePath().c_str()); fclose(inf); png_destroy_read_struct(&pngRead, &info, nullptr); return 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 (int i=0 ; i 1) { const uint8_t* filterIn = bufOut.get(); uint8_t* filterOut = bufOut.get() + width * height * 4; unsigned filterWidth = width; unsigned filterHeight = height; for (size_t i=1 ; i