#include "DataSpec/DNACommon/TXTR.hpp" #include #include #include "DataSpec/DNACommon/PAK.hpp" #include #include #include #include #include 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; } for (unsigned y = 0; y < mipHeight; ++y) { const unsigned miplineBase = mipWidth * y; const unsigned in1LineBase = inWidth * (y * 2); const unsigned in2LineBase = inWidth * (y * 2 + 1); for (unsigned x = 0; x < mipWidth; ++x) { uint8_t* out = &output[(miplineBase + x) * chanCount]; for (unsigned 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 */ static constexpr uint8_t Convert3To8(uint8_t v) { /* Swizzle bits: 00000123 -> 12312312 */ return (v << 5) | (v << 2) | (v >> 1); } static constexpr uint8_t Convert8To3(uint8_t v) { return v >> 5; } static constexpr uint8_t Convert4To8(uint8_t v) { /* Swizzle bits: 00001234 -> 12341234 */ return (v << 4) | v; } static constexpr uint8_t Convert8To4(uint8_t v) { return v >> 4; } static constexpr uint8_t Convert5To8(uint8_t v) { /* Swizzle bits: 00012345 -> 12345123 */ return (v << 3) | (v >> 2); } static constexpr uint8_t Convert8To5(uint8_t v) { return v >> 3; } static constexpr uint8_t Convert6To8(uint8_t v) { /* Swizzle bits: 00123456 -> 12345612 */ return (v << 2) | (v >> 4); } static constexpr uint8_t Convert8To6(uint8_t v) { return v >> 2; } static uint8_t Lookup4BPP(const uint8_t* texels, int width, int x, int y) { const int bwidth = (width + 7) / 8; const int bx = x / 8; const int by = y / 8; const int rx = x % 8; const int ry = y % 8; const 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) { const int bwidth = (width + 7) / 8; const int bx = x / 8; const int by = y / 8; const int rx = x % 8; const int ry = y % 8; const 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) { const int bwidth = (width + 7) / 8; const int bx = x / 8; const int by = y / 4; const int rx = x % 8; const int ry = y % 4; const 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) { const int bwidth = (width + 3) / 4; const int bx = x / 4; const int by = y / 4; const int rx = x % 4; const int ry = y % 4; int bidx = by * bwidth + bx; const uint16_t* btexels = reinterpret_cast(&texels[32 * bidx]); return btexels[ry * 4 + rx]; } static void Set16BPP(uint8_t* texels, int width, int x, int y, uint16_t val) { const int bwidth = (width + 3) / 4; const int bx = x / 4; const int by = y / 4; const int rx = x % 4; const int ry = y % 4; const int bidx = by * bwidth + bx; auto* btexels = reinterpret_cast(&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) { const int bwidth = (width + 3) / 4; const int bx = x / 4; const int by = y / 4; const int rx = x % 4; const int ry = y % 4; const int bidx = (by * bwidth + bx) * 2; const auto* artexels = reinterpret_cast(&texels[32 * bidx]); const auto* gbtexels = reinterpret_cast(&texels[32 * (bidx + 1)]); const uint16_t ar = hecl::SBig(artexels[ry * 4 + rx]); *a = ar >> 8 & 0xff; *r = ar & 0xff; const 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) { const int bwidth = (width + 3) / 4; const int bx = x / 4; const int by = y / 4; const int rx = x % 4; const int ry = y % 4; const int bidx = (by * bwidth + bx) * 2; uint16_t* artexels = reinterpret_cast(&texels[32 * bidx]); uint16_t* gbtexels = reinterpret_cast(&texels[32 * (bidx + 1)]); const uint16_t ar = (a << 8) | r; artexels[ry * 4 + rx] = hecl::SBig(ar); const 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 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 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 buf(new uint8_t[width * 2]); for (int y = height - 1; y >= 0; --y) { for (int x = 0; x < width; ++x) { const 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 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, reinterpret_cast(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(reinterpret_cast(rgbaIn)[x])); } rgbaIn += width * 2; } } static const uint8_t* DecodePalette(png_structp png, png_infop info, int numEntries, const uint8_t* data) { const auto format = hecl::SBig(*reinterpret_cast(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 auto* data16 = reinterpret_cast(data); for (int e = 0; e < numEntries; ++e) { const 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 auto* data16 = reinterpret_cast(data); for (int e = 0; e < numEntries; ++e) { const 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) { const png_const_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; } } reinterpret_cast(data)[0] = hecl::SBig(format); data += 4; reinterpret_cast(data)[0] = hecl::SBig(uint16_t(numEntries)); reinterpret_cast(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 = reinterpret_cast(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 */ auto* data16 = reinterpret_cast(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) { const auto format = hecl::SBig(*reinterpret_cast(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 auto* data16 = reinterpret_cast(data); for (int e = 0; e < numEntries; ++e) { const 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 auto* data16 = reinterpret_cast(data); for (int e = 0; e < numEntries; ++e) { const 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; const int pngNumPalettes = png_get_sPLT(png, info, &palettes); int pngNumEntries = 0; png_sPLT_entryp cEntries = nullptr; for (int i = 0; i < pngNumPalettes; ++i) { const png_const_sPLT_tp palette = &palettes[i]; if (strncmp(palette->name, "GX_", 3) == 0) { pngNumEntries = palette->nentries; cEntries = palette->entries; break; } } uint32_t format = 2; /* Default RGB5A3 */ for (int e = 0; e < pngNumEntries; ++e) { const png_const_sPLT_entryp ent = &cEntries[e]; if (ent->red != ent->green || ent->red != ent->blue) { if (ent->alpha) { format = 2; break; } else { format = 1; } } } reinterpret_cast(data)[0] = hecl::SBig(format); data += 4; reinterpret_cast(data)[0] = hecl::SBig(uint16_t(1)); reinterpret_cast(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 */ auto* data16 = reinterpret_cast(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 */ auto* data16 = reinterpret_cast(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 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 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 buf(new uint8_t[width * 3]); for (int y = height - 1; y >= 0; --y) { for (int x = 0; x < width; ++x) { const 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 buf(new uint8_t[width * 4]); for (int y = height - 1; y >= 0; --y) { for (int x = 0; x < width; ++x) { const 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 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 */ const int bwidth = (width + 7) / 8; const int bpwidth = bwidth * 8; std::unique_ptr 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 auto* blks = reinterpret_cast(texels + 32 * bwidth * y); for (int x = 0; x < width; x += 8) { uint32_t blkOut[4][4][4]; squish::Decompress(reinterpret_cast(blkOut[0][0]), blks++, squish::kDxt1GCN); squish::Decompress(reinterpret_cast(blkOut[1][0]), blks++, squish::kDxt1GCN); squish::Decompress(reinterpret_cast(blkOut[2][0]), blks++, squish::kDxt1GCN); squish::Decompress(reinterpret_cast(blkOut[3][0]), blks++, squish::kDxt1GCN); for (int bt = 0; bt < 4; ++bt) { for (int by = 0; by < 4; ++by) { std::memcpy(bTargets[bt] + x + width * by, blkOut[bt][by], 16); } } } for (int r = 7; r >= 0; --r) { png_write_row(png, reinterpret_cast(bTargets[0] + width * r)); } } } static void EncodeCMPR(const uint8_t* rgbaIn, uint8_t* texels, int width, int height) { /* Encode 8 rows at a time */ const int bwidth = (width + 7) / 8; const int bpwidth = bwidth * 8; std::unique_ptr 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) { std::memcpy(bTargets[0] + width * r, rgbaIn, width * 4); rgbaIn += width * 4; } auto* blks = reinterpret_cast(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) { std::memcpy(blkIn[bt][by], bTargets[bt] + x + width * by, 16); } } squish::Compress(reinterpret_cast(blkIn[0][0]), blks++, squish::kDxt1GCN); squish::Compress(reinterpret_cast(blkIn[1][0]), blks++, squish::kDxt1GCN); squish::Compress(reinterpret_cast(blkIn[2][0]), blks++, squish::kDxt1GCN); squish::Compress(reinterpret_cast(blkIn[3][0]), blks++, squish::kDxt1GCN); } } } static void PNGErr(png_structp png, png_const_charp msg) { Log.report(logvisor::Error, FMT_STRING("{}"), msg); } static void PNGWarn(png_structp png, png_const_charp msg) { Log.report(logvisor::Warning, FMT_STRING("{}"), msg); } bool TXTR::Extract(PAKEntryReadStream& rs, const hecl::ProjectPath& outPath) { const uint32_t format = rs.readUint32Big(); const uint16_t width = rs.readUint16Big(); const uint16_t height = rs.readUint16Big(); const uint32_t numMips = rs.readUint32Big(); auto fp = hecl::FopenUnique(outPath.getAbsolutePath().data(), _SYS_STR("wb")); if (fp == nullptr) { Log.report(logvisor::Error, FMT_STRING(_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.get()); png_infop info = png_create_info_struct(png); png_text textStruct = {}; textStruct.key = png_charp("metaforce_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); return true; } static std::unique_ptr ReadPalette(png_structp png, png_infop info, size_t& szOut) { std::unique_ptr ret; png_sPLT_tp palettes; const int paletteCount = png_get_sPLT(png, info, &palettes); if (paletteCount != 0) { for (int i = 0; i < paletteCount; ++i) { const png_const_sPLT_tp palette = &palettes[i]; if (strncmp(palette->name, "GX_", 3) == 0) { if (palette->nentries > 16) { /* This is a C8 palette */ ret.reset(new uint8_t[4 * 257]); szOut = 4 * 257; *reinterpret_cast(ret.get()) = hecl::SBig(256); uint8_t* cur = ret.get() + 4; for (int j = 0; j < 256; ++j) { if (j < palette->nentries) { const png_const_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(ret.get()) = hecl::SBig(16); uint8_t* cur = ret.get() + 4; for (int j = 0; j < 16; ++j) { if (j < palette->nentries) { const png_const_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 palettes2; int colorCount; if (png_get_PLTE(png, info, &palettes2, &colorCount) == PNG_INFO_PLTE) { if (colorCount > 16) { /* This is a C8 palette */ ret.reset(new uint8_t[4 * 257]); szOut = 4 * 257; *reinterpret_cast(ret.get()) = hecl::SBig(256); uint8_t* cur = ret.get() + 4; for (int j = 0; j < 256; ++j) { if (j < colorCount) { const png_const_colorp entry = &palettes2[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(ret.get()) = hecl::SBig(16); uint8_t* cur = ret.get() + 4; for (int j = 0; j < 16; ++j) { if (j < colorCount) { const png_const_colorp entry = &palettes2[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; const int paletteCount = png_get_sPLT(png, info, &palettes); if (paletteCount != 0) { for (int i = 0; i < paletteCount; ++i) { const png_const_sPLT_tp palette = &palettes[i]; if (strncmp(palette->name, "GX_", 3) == 0) { if (palette->nentries > 16) { /* This is a C8 palette */ return 256; } else { /* This is a C4 palette */ return 16; } } } } else { png_colorp palletes2; int colorCount; if (png_get_PLTE(png, info, &palletes2, &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) { auto inf = hecl::FopenUnique(inPath.getAbsolutePath().data(), _SYS_STR("rb")); if (inf == nullptr) { Log.report(logvisor::Error, FMT_STRING(_SYS_STR("Unable to open '{}' for reading")), inPath.getAbsolutePath()); return false; } /* Validate PNG */ char header[8]; std::fread(header, 1, sizeof(header), inf.get()); if (png_sig_cmp((png_const_bytep)header, 0, 8)) { Log.report(logvisor::Error, FMT_STRING(_SYS_STR("invalid PNG signature in '{}'")), inPath.getAbsolutePath()); 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_STRING("unable to initialize libpng")); return false; } png_infop info = png_create_info_struct(pngRead); if (!info) { Log.report(logvisor::Error, FMT_STRING("unable to initialize libpng info")); png_destroy_read_struct(&pngRead, nullptr, nullptr); return false; } if (setjmp(png_jmpbuf(pngRead))) { Log.report(logvisor::Error, FMT_STRING(_SYS_STR("unable to initialize libpng I/O for '{}'")), inPath.getAbsolutePath()); png_destroy_read_struct(&pngRead, &info, nullptr); return false; } png_init_io(pngRead, inf.get()); png_set_sig_bytes(pngRead, 8); png_read_info(pngRead, info); const png_uint_32 width = png_get_image_width(pngRead, info); const png_uint_32 height = png_get_image_height(pngRead, info); const png_byte colorType = png_get_color_type(pngRead, info); const png_byte bitDepth = png_get_bit_depth(pngRead, info); if (width < 4 || height < 4) { Log.report(logvisor::Error, FMT_STRING("image must be 4x4 or larger")); png_destroy_read_struct(&pngRead, &info, nullptr); return false; } /* Disable mipmapping if metaforce_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 (std::strcmp(textStruct[i].key, "metaforce_nomip") == 0) { 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_STRING(_SYS_STR("'{}' is not 8 bits-per-channel")), inPath.getAbsolutePath()); 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_STRING(_SYS_STR("unsupported color type in '{}'")), inPath.getAbsolutePath()); png_destroy_read_struct(&pngRead, &info, nullptr); return false; } /* Intermediate row-read buf (file components) */ std::unique_ptr rowBuf; if (colorType == PNG_COLOR_TYPE_RGB) rowBuf.reset(new uint8_t[rowSize]); /* Final mipmapped buf (RGBA components) */ std::unique_ptr 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_STRING(_SYS_STR("unable to read image in '{}'")), inPath.getAbsolutePath()); 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) { const size_t inbase = i * 3; const 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) { const 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); inf.reset(); /* 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 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(); std::memset(blocksOut, 0, compLen); for (size_t i = 0; i < numMips; ++i) { const 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(); std::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_STRING(_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) { auto inf = hecl::FopenUnique(inPath.getAbsolutePath().data(), _SYS_STR("rb")); if (inf == nullptr) { Log.report(logvisor::Error, FMT_STRING(_SYS_STR("Unable to open '{}' for reading")), inPath.getAbsolutePath()); return false; } /* Validate PNG */ char header[8]; std::fread(header, 1, sizeof(header), inf.get()); if (png_sig_cmp((png_const_bytep)header, 0, 8)) { Log.report(logvisor::Error, FMT_STRING(_SYS_STR("invalid PNG signature in '{}'")), inPath.getAbsolutePath()); 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_STRING("unable to initialize libpng")); return false; } png_infop info = png_create_info_struct(pngRead); if (!info) { Log.report(logvisor::Error, FMT_STRING("unable to initialize libpng info")); png_destroy_read_struct(&pngRead, nullptr, nullptr); return false; } if (setjmp(png_jmpbuf(pngRead))) { Log.report(logvisor::Error, FMT_STRING(_SYS_STR("unable to initialize libpng I/O for '{}'")), inPath.getAbsolutePath()); png_destroy_read_struct(&pngRead, &info, nullptr); return false; } png_init_io(pngRead, inf.get()); png_set_sig_bytes(pngRead, 8); png_read_info(pngRead, info); const png_uint_32 width = png_get_image_width(pngRead, info); const png_uint_32 height = png_get_image_height(pngRead, info); const png_byte colorType = png_get_color_type(pngRead, info); const png_byte bitDepth = png_get_bit_depth(pngRead, info); /* Disable mipmapping if metaforce_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 (std::strcmp(textStruct[i].key, "metaforce_nomip") == 0) { 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_STRING(_SYS_STR("'{}' is not 8 bits-per-channel")), inPath.getAbsolutePath()); png_destroy_read_struct(&pngRead, &info, nullptr); return false; } std::unique_ptr 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_STRING(_SYS_STR("unsupported color type in '{}'")), inPath.getAbsolutePath()); 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) * nComps; else bufLen = width * height * nComps; bufOut.reset(new uint8_t[bufLen]); if (setjmp(png_jmpbuf(pngRead))) { Log.report(logvisor::Fatal, FMT_STRING(_SYS_STR("unable to read image in '{}'")), inPath.getAbsolutePath()); 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) { const 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) { const size_t inbase = i * 2; const 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) { const size_t inbase = i * 3; const 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) { const size_t inbase = i * 4; const 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); inf.reset(); /* 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 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) { const 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_STRING(_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 void DataSpec::TXTR::PaletteMeta::Enumerate(typename Op::StreamT& s) { Do(athena::io::PropId{"format"}, format, s); Do(athena::io::PropId{"elementCount"}, elementCount, s); Do(athena::io::PropId{"dolphinHash"}, dolphinHash, s); } AT_SPECIALIZE_DNA_YAML(DataSpec::TXTR::PaletteMeta) std::string_view DataSpec::TXTR::PaletteMeta::DNAType() { return "DataSpec::TXTR::PaletteMeta"sv; } template void DataSpec::TXTR::Meta::Enumerate(typename Op::StreamT& s) { Do(athena::io::PropId{"format"}, format, s); Do(athena::io::PropId{"mips"}, mips, s); Do(athena::io::PropId{"width"}, width, s); Do(athena::io::PropId{"height"}, height, s); Do(athena::io::PropId{"dolphinHash"}, dolphinHash, s); Do(athena::io::PropId{"hasPalette"}, hasPalette, s); if (hasPalette) Do(athena::io::PropId{"palette"}, palette, s); } AT_SPECIALIZE_DNA_YAML(DataSpec::TXTR::Meta) std::string_view DataSpec::TXTR::Meta::DNAType() { return "DataSpec::TXTR::Meta"sv; } static const atInt32 RetroToDol[11] { 0, 1, 2, 3, 8, 9, -1, 4, 5, 6, 14 }; TXTR::Meta TXTR::GetMetaData(DataSpec::PAKEntryReadStream& rs) { const atUint32 retroFormat = rs.readUint32Big(); const 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(); const atUint16 palWidth = rs.readUint16Big(); const atUint16 palHeight = rs.readUint16Big(); palMeta.elementCount = palWidth * palHeight; const atUint32 palSize = atUint32(palWidth * palHeight * 2); if (format == 8) textureSize /= 2; std::unique_ptr 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 textureData(new u8[textureSize]); rs.readUBytesToBuf(textureData.get(), textureSize); meta.dolphinHash = XXH64(textureData.get(), textureSize, 0); return meta; } } // namespace DataSpec