#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, 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 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 Convert8To3(uint8_t v) { return v >> 5; } static inline uint8_t Convert4To8(uint8_t v) { /* Swizzle bits: 00001234 -> 12341234 */ return (v << 4) | v; } static inline uint8_t Convert8To4(uint8_t v) { return v >> 4; } static inline uint8_t Convert5To8(uint8_t v) { /* Swizzle bits: 00012345 -> 12345123 */ return (v << 3) | (v >> 2); } static inline uint8_t Convert8To5(uint8_t v) { return v >> 3; } static inline uint8_t Convert6To8(uint8_t v) { /* Swizzle bits: 00123456 -> 12345612 */ return (v << 2) | (v >> 4); } static inline uint8_t Convert8To6(uint8_t v) { return v >> 2; } 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 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 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 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 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 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 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 inline 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 buf(new uint8_t[width]); //memset(buf.get(), 0, width); for (int y=height-1 ; y>=0 ; --y) { for (int x=0 ; x=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=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] = Convert4To8(texel & 0xf); } png_write_row(png, buf.get()); } } 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=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 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 ; ered != 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> 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 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 ; iname, "GX_", 3)) { pngNumEntries = palette->nentries; cEntries = palette->entries; break; } } uint32_t format = 2; /* Default RGB5A3 */ for (int e=0 ; ered != 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 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 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> 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 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=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 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 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 ReadPalette(png_structp png, png_infop info, size_t& szOut) { std::unique_ptr ret; png_sPLT_tp palettes; int paletteCount = png_get_sPLT(png, info, &palettes); if (paletteCount) { for (int i=0 ; iname, "GX_", 3)) { 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) { 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(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(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(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 ; iname, "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(), _S("rb")); if (!inf) { Log.report(logvisor::Error, _S("Unable to open '%s' for reading"), inPath.getAbsolutePath().data()); 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().data()); 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().data()); 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, "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>= 1) ++numMips; } if (bitDepth != 8) { Log.report(logvisor::Error, _S("'%s' is not 8 bits-per-channel"), inPath.getAbsolutePath().data()); 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, _S("unsupported color type in '%s'"), inPath.getAbsolutePath().data()); fclose(inf); 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::Error, _S("unable to read image in '%s'"), inPath.getAbsolutePath().data()); 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 (int r=0 ; r 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>= 1) ++numMips; } if (bitDepth != 8) { Log.report(logvisor::Error, _S("'%s' is not 8 bits-per-channel"), inPath.getAbsolutePath().data()); fclose(inf); 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, _S("unsupported color type in '%s'"), inPath.getAbsolutePath().data()); 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) * nComps; else bufLen = width * height * nComps; bufOut.reset(new uint8_t[bufLen]); if (setjmp(png_jmpbuf(pngRead))) { Log.report(logvisor::Error, _S("unable to read image in '%s'"), inPath.getAbsolutePath().data()); 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 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 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 compOut; size_t compLen = 0; if (doDXT1) { int filterWidth = width; int filterHeight = height; size_t i; for (i=0 ; i