From 62446c7ba4ca9b89198d92ccc86420de078ee5c1 Mon Sep 17 00:00:00 2001 From: inspectredc Date: Tue, 26 Nov 2024 15:59:38 +0000 Subject: [PATCH] Compressed Texture Factory --- src/Companion.cpp | 2 + src/factories/CompressedTextureFactory.cpp | 536 +++++++++++++++++++++ src/factories/CompressedTextureFactory.h | 47 ++ src/factories/TextureFactory.cpp | 74 +-- src/factories/TextureFactory.h | 21 +- src/utils/TextureUtils.cpp | 57 +++ src/utils/TextureUtils.h | 31 ++ 7 files changed, 684 insertions(+), 84 deletions(-) create mode 100644 src/factories/CompressedTextureFactory.cpp create mode 100644 src/factories/CompressedTextureFactory.h create mode 100644 src/utils/TextureUtils.cpp create mode 100644 src/utils/TextureUtils.h diff --git a/src/Companion.cpp b/src/Companion.cpp index e16c670..a86e64a 100644 --- a/src/Companion.cpp +++ b/src/Companion.cpp @@ -25,6 +25,7 @@ #include "factories/Vec3sFactory.h" #include "factories/AssetArrayFactory.h" #include "factories/ViewportFactory.h" +#include "factories/CompressedTextureFactory.h" #include "factories/sm64/AnimationFactory.h" #include "factories/sm64/BehaviorScriptFactory.h" @@ -105,6 +106,7 @@ void Companion::Init(const ExportType type) { this->RegisterFactory("ARRAY", std::make_shared()); this->RegisterFactory("ASSET_ARRAY", std::make_shared()); this->RegisterFactory("VP", std::make_shared()); + this->RegisterFactory("COMPRESSED_TEXTURE", std::make_shared()); // SM64 specific this->RegisterFactory("SM64:DIALOG", std::make_shared()); diff --git a/src/factories/CompressedTextureFactory.cpp b/src/factories/CompressedTextureFactory.cpp new file mode 100644 index 0000000..95e2520 --- /dev/null +++ b/src/factories/CompressedTextureFactory.cpp @@ -0,0 +1,536 @@ +#include "CompressedTextureFactory.h" +#include "utils/Decompressor.h" +#include "spdlog/spdlog.h" +#include "Companion.h" +#include +#include + +extern "C" { +#include "n64graphics/n64graphics.h" +#include "BaseFactory.h" +#include +} + +static bool isTable = false; +static std::vector tableEntries; + +static const std::unordered_map sTextureFormats = { + { "RGBA16", { TextureType::RGBA16bpp, 16 } }, + { "RGBA32", { TextureType::RGBA32bpp, 32 } }, + { "CI4", { TextureType::Palette4bpp, 4 } }, + { "CI8", { TextureType::Palette8bpp, 8 } }, + { "I4", { TextureType::Grayscale4bpp, 4 } }, + { "I8", { TextureType::Grayscale8bpp, 8 } }, + { "IA1", { TextureType::GrayscaleAlpha1bpp, 1 } }, + { "IA4", { TextureType::GrayscaleAlpha4bpp, 4 } }, + { "IA8", { TextureType::GrayscaleAlpha8bpp, 8 } }, + { "IA16", { TextureType::GrayscaleAlpha16bpp, 16 } }, + { "TLUT", { TextureType::TLUT, 16 } }, +}; + +static const std::unordered_map sCompressionTypes = { + { "MIO0", CompressionType::MIO0 }, + { "YAY0", CompressionType::YAY0 }, + { "YAZ0", CompressionType::YAZ0 }, +}; + +ExportResult CompressedTextureHeaderExporter::Export(std::ostream &write, std::shared_ptr raw, std::string& entryName, YAML::Node &node, std::string* replacement) { + const auto symbol = GetSafeNode(node, "symbol", entryName); + const auto offset = GetSafeNode(node, "offset"); + auto format = GetSafeNode(node, "format"); + auto texture = std::static_pointer_cast(raw); + auto data = texture->mBuffer; + auto isOTR = Companion::Instance->IsOTRMode(); + size_t byteSize = std::max(1, (int) (texture->mFormat.depth / 8)); + + const auto searchTable = Companion::Instance->SearchTable(offset); + + if(searchTable.has_value()){ + const auto [name, start, end, mode, index_size] = searchTable.value(); + unsigned int isize = index_size > -1 ? index_size : data.size() / byteSize; + + if(isOTR){ + write << "static const ALIGN_ASSET(2) char " << symbol << "[] = \"__OTR__" << (*replacement) << "\";\n\n"; + + tableEntries.push_back(symbol); + + if(end == offset){ + write << "static const char* " << name << "[] = {\n"; + for(auto& entry : tableEntries){ + write << tab_t << entry << ",\n"; + } + write << "};\n\n"; + tableEntries.clear(); + } + } else { + write << "extern " << "u8 " << name << "[][" << isize << "];\n"; + } + } else { + if(isOTR){ + write << "static const ALIGN_ASSET(2) char " << symbol << "[] = \"__OTR__" << (*replacement) << "\";\n\n"; + } else { + write << "extern " << "u8 " << symbol << "[];\n"; + } + } + + return std::nullopt; +} + +ExportResult CompressedTextureCodeExporter::Export(std::ostream &write, std::shared_ptr raw, std::string& entryName, YAML::Node &node, std::string* replacement) { + auto texture = std::static_pointer_cast(raw); + auto data = texture->mBuffer; + auto offset = GetSafeNode(node, "offset"); + auto symbol = GetSafeNode(node, "symbol", entryName); + auto format = GetSafeNode(node, "format"); + + std::transform(format.begin(), format.end(), format.begin(), tolower); + (*replacement) += "." + format; + + std::string dpath = Companion::Instance->GetOutputPath() + "/" + (*replacement); + if(!exists(fs::path(dpath).parent_path())){ + create_directories(fs::path(dpath).parent_path()); + } + + std::ostringstream imgstream; + + size_t byteSize = std::max(1, (int) (texture->mFormat.depth / 8)); + size_t isize = texture->mBuffer.size() / byteSize; + + for (int i = 0; i < data.size(); i+=byteSize) { + if (i % 16 == 0 && i != 0) { + imgstream << std::endl; + } + + imgstream << "0x"; + + for (int j = 0; j < byteSize; j++) { + imgstream << std::hex << std::setw(2) << std::setfill('0') << static_cast(data[i + j]); + } + + imgstream << ", "; + } + imgstream << std::endl; + + std::ofstream file(dpath + ".inc.c", std::ios::binary); + file << imgstream.str(); + file.close(); + + // Allocate worse case size + uint8_t* compressedData; + size_t compressedSize; + + switch (texture->mCompressionType) { + case CompressionType::MIO0: + compressedData = static_cast(malloc(MIO0_HEADER_LENGTH + ((data.size()+7)/8) + data.size())); + compressedSize = mio0_encode(data.data(), data.size(), compressedData); + break; + default: + // UNIMPLEMENTED + throw std::runtime_error("Unsupported Compressed Texture Type"); + break; + } + if (compressedData) { + std::ostringstream compressedStream; + + for (size_t i = 0; i < compressedSize; i++) { + if (i % 16 == 0 && i != 0) { + compressedStream << std::endl; + } + + compressedStream << "0x"; + compressedStream << std::hex << std::setw(2) << std::setfill('0') << static_cast(compressedData[i]); + compressedStream << ", "; + } + compressedStream << std::endl; + + std::ofstream file(dpath + ".incbin.c", std::ios::binary); + file << compressedStream.str(); + file.close(); + free(compressedData); + } + + const auto searchTable = Companion::Instance->SearchTable(offset); + + if(searchTable.has_value()){ + const auto [name, start, end, mode, index_size] = searchTable.value(); + + if(mode != TableMode::Append){ + throw std::runtime_error("Reference mode is not supported for now"); + } + + if (index_size > -1) { + isize = index_size; + } + + if(start == offset){ + write << "u8 " << name << "[][" << isize << "] = {\n"; + } + + write << tab_t << "{\n"; + + write << tab_t << tab_t << "#include \"" << Companion::Instance->GetOutputPath() + "/" << *replacement << ".incbin.c\"\n"; + + write << tab_t << "},\n"; + + if(end == offset){ + write << "};\n"; + if (Companion::Instance->IsDebug()) { + write << "// size: 0x" << std::hex << std::uppercase << ASSET_PTR((end - start) + isize * byteSize) << "\n"; + } + } + } else { + write << "u8 " << symbol << "[] = {\n"; + + write << tab_t << "#include \"" << Companion::Instance->GetOutputPath() + "/" << *replacement << ".incbin.c\"\n"; + + write << "};\n"; + + const auto sz = data.size(); + if (Companion::Instance->IsDebug()) { + write << "// size: 0x" << std::hex << std::uppercase << sz; + } + + write << "\n"; + } + return offset + isize * byteSize; +} + +ExportResult CompressedTextureBinaryExporter::Export(std::ostream &write, std::shared_ptr raw, std::string& entryName, YAML::Node &node, std::string* replacement) { + auto writer = LUS::BinaryWriter(); + auto texture = std::static_pointer_cast(raw); + auto data = texture->mBuffer; + + // TODO: Recompress? + + WriteHeader(writer, Torch::ResourceType::Texture, 0); + + if(texture->mFormat.type == TextureType::TLUT) { + texture->mFormat.type = TextureType::RGBA16bpp; + } + + writer.Write((uint32_t) texture->mFormat.type); + writer.Write(texture->mWidth); + writer.Write(texture->mHeight); + + writer.Write((uint32_t) data.size()); + writer.Write((char*) data.data(), data.size()); + writer.Finish(write); + return std::nullopt; +} + +ExportResult CompressedTextureModdingExporter::Export(std::ostream&write, std::shared_ptr data, std::string&entryName, YAML::Node&node, std::string* replacement) { + auto texture = std::static_pointer_cast(data); + auto format = texture->mFormat; + uint8_t* raw = new uint8_t[TextureUtils::CalculateTextureSize(format.type, texture->mWidth, texture->mHeight) * 2]; + int size = 0; + + auto ext = GetSafeNode(node, "format"); + + std::transform(ext.begin(), ext.end(), ext.begin(), tolower); + *replacement += "." + ext + ".png"; + + switch (format.type) { + case TextureType::TLUT: + case TextureType::RGBA16bpp: + case TextureType::RGBA32bpp: { + rgba* imgr = raw2rgba(texture->mBuffer.data(), texture->mWidth, texture->mHeight, format.depth); + if(rgba2png(&raw, &size, imgr, texture->mWidth, texture->mHeight)) { + throw std::runtime_error("Failed to convert texture to PNG"); + } + break; + } + case TextureType::GrayscaleAlpha16bpp: + case TextureType::GrayscaleAlpha8bpp: + case TextureType::GrayscaleAlpha4bpp: + case TextureType::GrayscaleAlpha1bpp: { + ia* imgia = raw2ia(texture->mBuffer.data(), texture->mWidth, texture->mHeight, format.depth); + if(ia2png(&raw, &size, imgia, texture->mWidth, texture->mHeight)) { + throw std::runtime_error("Failed to convert texture to PNG"); + } + break; + } + case TextureType::Palette8bpp: + case TextureType::Palette4bpp: { + if (node["tlut_symbol"]) { + auto tlut = GetSafeNode(node,"tlut_symbol"); + auto palette = Companion::Instance->GetParseDataBySymbol(tlut); + + if (palette.has_value()) { + auto palTexture = std::static_pointer_cast(palette.value().data.value()); + convert_raw_to_ci8(&raw, &size, texture->mBuffer.data(), (uint8_t *)palTexture->mBuffer.data(), 0, texture->mWidth, texture->mHeight, texture->mFormat.depth, palTexture->mFormat.depth); + } else { + auto symbol = GetSafeNode(node, "symbol"); + throw std::runtime_error("Could not convert ci8 '"+symbol+"' the tlut symbol name is probably wrong for tlut_symbol node"); + } + break; + } + + if (node["tlut"]) { + auto tlut = GetSafeNode(node,"tlut"); + auto palette = Companion::Instance->GetParseDataByAddr(tlut); + + if (palette.has_value()) { + auto palTexture = std::static_pointer_cast(palette.value().data.value()); + convert_raw_to_ci8(&raw, &size, texture->mBuffer.data(), (uint8_t *)palTexture->mBuffer.data(), 0, texture->mWidth, texture->mHeight, texture->mFormat.depth, palTexture->mFormat.depth); + } else { + auto symbol = GetSafeNode(node, "symbol"); + throw std::runtime_error("Could not convert ci8 '"+symbol+"' the address is probably wrong for tlut address node"); + } + break; + } + } + case TextureType::Grayscale8bpp: + case TextureType::Grayscale4bpp: { + ia* imgi = raw2i(texture->mBuffer.data(), texture->mWidth, texture->mHeight, format.depth); + if(ia2png(&raw, &size, imgi, texture->mWidth, texture->mHeight)) { + throw std::runtime_error("Failed to convert texture to PNG"); + } + break; + } + default: { + SPDLOG_ERROR("Unsupported texture format for modding: {}", ext); + } + } + + write.write(reinterpret_cast(raw), size); + return std::nullopt; +} + +std::string getcomptype(CompressionType type) { + switch (type) { + case CompressionType::MIO0: + return "MIO0"; + case CompressionType::YAY0: + return "YAY0"; + case CompressionType::YAZ0: + return "YAZ0"; + default: + break; + } + + return "None"; +} + +std::optional> CompressedTextureFactory::parse(std::vector& buffer, YAML::Node& node) { + auto offset = GetSafeNode(node, "offset"); + auto format = GetSafeNode(node, "format"); + auto symbol = GetSafeNode(node, "symbol"); + uint32_t width; + uint32_t height; + uint32_t size; + auto compression = GetSafeNode(node, "compression"); + CompressionType compressionType; + if (!sCompressionTypes.contains(compression)) { + SPDLOG_ERROR("Compresed Texture entry at {:X} in yaml missing compression type\n\ + Please add one of the following compression types\n\ + MIO0, YAY0 (Unsupported), YAZ0 (Unsupported)", offset); + return std::nullopt; + } + compressionType = sCompressionTypes.at(compression); + + CompressionType realCompressionType = Decompressor::GetCompressionType(buffer, Decompressor::TranslateAddr(offset, false)); + + if (realCompressionType != compressionType) { + SPDLOG_ERROR("Compressed Texture entry at {:X} in yaml uses mismatching compression type\n\ + Passed In {}, expected {}", offset, getcomptype(compressionType), getcomptype(realCompressionType)); + return std::nullopt; + } + + DataChunk* uncompressedData = Decompressor::Decode(buffer, Decompressor::TranslateAddr(offset, false), compressionType); + + std::transform(format.begin(), format.end(), format.begin(), ::toupper); + + if (format.empty()) { + SPDLOG_ERROR("Texture entry at {:X} in yaml missing format node\n\ + Please add one of the following formats\n\ + rgba16, rgba32, ia16, ia8, ia4, i8, i4, ci8, ci4, 1bpp, tlut", offset); + return std::nullopt; + } + + if(!sTextureFormats.contains(format)) { + return std::nullopt; + } + + TextureFormat fmt = sTextureFormats.at(format); + + if(fmt.type == TextureType::TLUT){ + width = GetSafeNode(node, "colors"); + height = 1; + } else { + width = GetSafeNode(node, "width"); + height = GetSafeNode(node, "height"); + } + + if((format == "CI4" || format == "CI8") && node["tlut"] && node["colors"]) { + YAML::Node tlutNode; + const auto tlutOffset = GetSafeNode(node, "tlut"); + const auto tlutSymbol = GetSafeNode(node, "tlut_symbol", symbol + "_tlut"); + std::ostringstream offsetSeg; + offsetSeg << std::uppercase << std::hex << tlutOffset; + tlutNode["symbol"] = std::regex_replace(tlutSymbol, std::regex(R"(OFFSET)"), offsetSeg.str()); + tlutNode["type"] = "TEXTURE"; + tlutNode["format"] = "TLUT"; + tlutNode["offset"] = tlutOffset; + tlutNode["colors"] = GetSafeNode(node, "colors"); + node["tlut"] = tlutOffset; + if(node["tlut_ctype"]) { + tlutNode["ctype"] = GetSafeNode(node, "tlut_ctype"); + } + Companion::Instance->AddAsset(tlutNode); + } + size = GetSafeNode(node, "size", TextureUtils::CalculateTextureSize(sTextureFormats.at(format).type, width, height)); + + std::vector result; + + if(fmt.type == TextureType::GrayscaleAlpha1bpp){ + result = TextureUtils::alloc_ia8_text_from_i1(reinterpret_cast(uncompressedData->data), 8, 16); + } else { + result = std::vector(uncompressedData->data, uncompressedData->data + uncompressedData->size); + } + + SPDLOG_INFO("Texture: {}", format); + if(fmt.type == TextureType::TLUT){ + SPDLOG_INFO("Colors: {}", width); + } else { + SPDLOG_INFO("Width: {}", width); + SPDLOG_INFO("Height: {}", height); + } + SPDLOG_INFO("Size: {}", size); + SPDLOG_INFO("Offset: 0x{:X}", offset); + + if(result.size() == 0){ + return std::nullopt; + } + + if(result.size() == 0){ + return std::nullopt; + } + + return std::make_shared(fmt, width, height, result, compressionType); +} + +std::optional> CompressedTextureFactory::parse_modding(std::vector& buffer, YAML::Node& node) { + auto format = GetSafeNode(node, "format"); + int width; + int height; + uint32_t size; + auto offset = GetSafeNode(node, "offset"); + auto compression = GetSafeNode(node, "compression"); + CompressionType compressionType; + if (!sCompressionTypes.contains(compression)) { + SPDLOG_ERROR("Compresed Texture entry at {:X} in yaml missing compression type\n\ + Please add one of the following compression types\n\ + MIO0, YAY0 (Unsupported), YAZ0 (Unsupported)", offset); + return std::nullopt; + } + compressionType = sCompressionTypes.at(compression); + + if (format.empty()) { + SPDLOG_ERROR("Texture entry at {:X} in yaml missing format node\n\ + Please add one of the following formats\n\ + rgba16, rgba32, ia16, ia8, ia4, i8, i4, ci8, ci4, 1bpp, tlut", offset); + return std::nullopt; + } + + if(!sTextureFormats.contains(format)) { + return std::nullopt; + } + + TextureFormat fmt = sTextureFormats.at(format); + if(fmt.type == TextureType::TLUT){ + width = GetSafeNode(node, "colors"); + height = 1; + } else { + width = GetSafeNode(node, "width"); + height = GetSafeNode(node, "height"); + } + + uint8_t* raw; + switch (fmt.type) { + case TextureType::TLUT: + case TextureType::RGBA16bpp: + case TextureType::RGBA32bpp: { + const auto imgr = png2rgba(buffer.data(), buffer.size(), &width, &height); + size = width * height * fmt.depth / 8; + raw = new uint8_t[size]; + if(rgba2raw(raw, imgr, width, height, fmt.depth) <= 0){ + throw std::runtime_error("Failed to convert PNG to texture"); + } + break; + } + case TextureType::GrayscaleAlpha16bpp: + case TextureType::GrayscaleAlpha8bpp: + case TextureType::GrayscaleAlpha4bpp: + case TextureType::GrayscaleAlpha1bpp: { + const auto imgia = png2ia(buffer.data(), buffer.size(), &width, &height); + size = width * height * fmt.depth / 8; + raw = new uint8_t[size]; + if(ia2raw(raw, imgia, width, height, fmt.depth) <= 0){ + throw std::runtime_error("Failed to convert PNG to texture"); + } + break; + } + case TextureType::Palette8bpp: + case TextureType::Palette4bpp: { + // This implementation is not correct. + // The process should be: + // png2rgba --> imgpal2rawci + + // todo: Add wheel palette input + // Implement so that it works. + + // auto tlut = GetSafeNode(node,"tlut_symbol"); + // auto tlutTextureMap = Companion::Instance->GetTlutTextureMap(); + // auto palettePtr = tlutTextureMap[tlut]; + + // if (palettePtr) { + + // auto imgi = png2rgba(buffer.data(), buffer.size(), &width, &height); + // auto pal = png2rgba(palettePtr->mBuffer.data(), (palettePtr->mWidth * palettePtr->mWidth * palettePtr->mFormat.depth * 2), &width, &height); + + // size = width * height * fmt.depth / 8; + // raw = new uint8_t[size]; + + // if(imgpal2rawci(raw, imgi, pal, 0, 0, width, height, fmt.depth) <= 0){ + // throw std::runtime_error("Failed to convert PNG to texture"); + // } + // } else { + + // } + SPDLOG_ERROR("Unsupported texture format for modding: {}", format); + return std::nullopt; + } + case TextureType::Grayscale8bpp: + case TextureType::Grayscale4bpp: { + const auto imgi = png2ia(buffer.data(), buffer.size(), &width, &height); + size = width * height * fmt.depth / 8; + raw = new uint8_t[size]; + if(i2raw(raw, imgi, width, height, fmt.depth) <= 0){ + throw std::runtime_error("Failed to convert PNG to texture"); + } + break; + } + default: { + SPDLOG_ERROR("Unsupported texture format for modding: {}", format); + return std::nullopt; + } + } + + auto result = std::vector(raw, raw + size); + + SPDLOG_INFO("Texture: {}", format); + if(fmt.type == TextureType::TLUT){ + SPDLOG_INFO("Colors: {}", width); + } else { + SPDLOG_INFO("Width: {}", width); + SPDLOG_INFO("Height: {}", height); + } + SPDLOG_INFO("Size: {}", size); + SPDLOG_INFO("Offset: 0x{:X}", offset); + + if(result.size() == 0){ + return std::nullopt; + } + + return std::make_shared(fmt, width, height, result, compressionType); +} diff --git a/src/factories/CompressedTextureFactory.h b/src/factories/CompressedTextureFactory.h new file mode 100644 index 0000000..521f499 --- /dev/null +++ b/src/factories/CompressedTextureFactory.h @@ -0,0 +1,47 @@ +#pragma once + +#include "BaseFactory.h" +#include "utils/Decompressor.h" +#include "utils/TextureUtils.h" + +class CompressedTextureData : public IParsedData { +public: + TextureFormat mFormat; + uint32_t mWidth; + uint32_t mHeight; + std::vector mBuffer; + CompressionType mCompressionType; + + CompressedTextureData(TextureFormat format, uint32_t width, uint32_t height, std::vector& buffer, CompressionType compressionType) : mFormat(format), mWidth(width), mHeight(height), mBuffer(std::move(buffer)), mCompressionType(compressionType) {} +}; + +class CompressedTextureHeaderExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + +class CompressedTextureCodeExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + +class CompressedTextureBinaryExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + +class CompressedTextureModdingExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + +class CompressedTextureFactory : public BaseFactory { +public: + std::optional> parse(std::vector& buffer, YAML::Node& data) override; + std::optional> parse_modding(std::vector& buffer, YAML::Node& data) override; + inline std::unordered_map> GetExporters() override { + return { + REGISTER(Header, CompressedTextureHeaderExporter) + REGISTER(Binary, CompressedTextureBinaryExporter) + REGISTER(Code, CompressedTextureCodeExporter) + REGISTER(Modding, CompressedTextureModdingExporter) + }; + } + bool SupportModdedAssets() override { return true; } +}; diff --git a/src/factories/TextureFactory.cpp b/src/factories/TextureFactory.cpp index 34a6362..1165b62 100644 --- a/src/factories/TextureFactory.cpp +++ b/src/factories/TextureFactory.cpp @@ -10,10 +10,10 @@ extern "C" { #include "BaseFactory.h" } -bool isTable = false; -std::vector tableEntries; +static bool isTable = false; +static std::vector tableEntries; -static const std::unordered_map gTextureFormats = { +static const std::unordered_map sTextureFormats = { { "RGBA16", { TextureType::RGBA16bpp, 16 } }, { "RGBA32", { TextureType::RGBA32bpp, 32 } }, { "CI4", { TextureType::Palette4bpp, 4 } }, @@ -27,60 +27,6 @@ static const std::unordered_map gTextureFormats = { { "TLUT", { TextureType::TLUT, 16 } }, }; -size_t CalculateTextureSize(TextureType type, uint32_t width, uint32_t height) { - switch (type) { - // 4 bytes per pixel - case TextureType::RGBA32bpp: - return width * height * 4; - // 2 bytes per pixel - case TextureType::TLUT: - case TextureType::RGBA16bpp: - case TextureType::GrayscaleAlpha16bpp: - return width * height * 2; - // 1 byte per pixel - case TextureType::Grayscale8bpp: - case TextureType::Palette8bpp: - case TextureType::GrayscaleAlpha8bpp: - // TODO: We need to validate this MegaMech - case TextureType::GrayscaleAlpha1bpp: - return width * height; - // 1/2 byte per pixel - case TextureType::Palette4bpp: - case TextureType::Grayscale4bpp: - case TextureType::GrayscaleAlpha4bpp: - return (width * height) / 2; - default: - return 0; - } -} - -std::vector alloc_ia8_text_from_i1(uint16_t *in, int16_t width, int16_t height) { - int32_t inPos; - uint16_t bitMask; - int16_t outPos = 0; - const auto out = new uint8_t[width * height]; - - for (int32_t inPos = 0; inPos < (width * height) / 16; inPos++) { - uint16_t bitMask = 0x8000; - - while (bitMask != 0) { - if (BSWAP16(in[inPos]) & bitMask) { - out[outPos] = 0xFF; - } else { - out[outPos] = 0x00; - } - - bitMask /= 2; - outPos++; - } - } - - auto result = std::vector(out, out + width * height); - delete[] out; - - return result; -} - ExportResult TextureHeaderExporter::Export(std::ostream &write, std::shared_ptr raw, std::string& entryName, YAML::Node &node, std::string* replacement) { const auto symbol = GetSafeNode(node, "symbol", entryName); const auto offset = GetSafeNode(node, "offset"); @@ -239,7 +185,7 @@ ExportResult TextureBinaryExporter::Export(std::ostream &write, std::shared_ptr< ExportResult TextureModdingExporter::Export(std::ostream&write, std::shared_ptr data, std::string&entryName, YAML::Node&node, std::string* replacement) { auto texture = std::static_pointer_cast(data); auto format = texture->mFormat; - uint8_t* raw = new uint8_t[CalculateTextureSize(format.type, texture->mWidth, texture->mHeight) * 2]; + uint8_t* raw = new uint8_t[TextureUtils::CalculateTextureSize(format.type, texture->mWidth, texture->mHeight) * 2]; int size = 0; auto ext = GetSafeNode(node, "format"); @@ -332,11 +278,11 @@ std::optional> TextureFactory::parse(std::vector(node, "colors"); @@ -363,12 +309,12 @@ std::optional> TextureFactory::parse(std::vectorAddAsset(tlutNode); } - size = GetSafeNode(node, "size", CalculateTextureSize(gTextureFormats.at(format).type, width, height)); + size = GetSafeNode(node, "size", TextureUtils::CalculateTextureSize(sTextureFormats.at(format).type, width, height)); auto [_, segment] = Decompressor::AutoDecode(node, buffer, size); std::vector result; if(fmt.type == TextureType::GrayscaleAlpha1bpp){ - result = alloc_ia8_text_from_i1(reinterpret_cast(segment.data), 8, 16); + result = TextureUtils::alloc_ia8_text_from_i1(reinterpret_cast(segment.data), 8, 16); } else { result = std::vector(segment.data, segment.data + segment.size); } @@ -408,11 +354,11 @@ std::optional> TextureFactory::parse_modding(std::v return std::nullopt; } - if(!gTextureFormats.contains(format)) { + if(!sTextureFormats.contains(format)) { return std::nullopt; } - TextureFormat fmt = gTextureFormats.at(format); + TextureFormat fmt = sTextureFormats.at(format); if(fmt.type == TextureType::TLUT){ width = GetSafeNode(node, "colors"); height = 1; diff --git a/src/factories/TextureFactory.h b/src/factories/TextureFactory.h index 102ee54..9fa0006 100644 --- a/src/factories/TextureFactory.h +++ b/src/factories/TextureFactory.h @@ -1,26 +1,7 @@ #pragma once #include "BaseFactory.h" - -enum class TextureType { - Error, - RGBA32bpp, - RGBA16bpp, - Palette4bpp, - Palette8bpp, - Grayscale4bpp, - Grayscale8bpp, - GrayscaleAlpha4bpp, - GrayscaleAlpha8bpp, - GrayscaleAlpha16bpp, - GrayscaleAlpha1bpp, - TLUT -}; - -struct TextureFormat { - TextureType type; - uint32_t depth; -}; +#include "utils/TextureUtils.h" class TextureData : public IParsedData { public: diff --git a/src/utils/TextureUtils.cpp b/src/utils/TextureUtils.cpp new file mode 100644 index 0000000..7b70f19 --- /dev/null +++ b/src/utils/TextureUtils.cpp @@ -0,0 +1,57 @@ +#include "TextureUtils.h" +#include +#include + +size_t TextureUtils::CalculateTextureSize(TextureType type, uint32_t width, uint32_t height) { + switch (type) { + // 4 bytes per pixel + case TextureType::RGBA32bpp: + return width * height * 4; + // 2 bytes per pixel + case TextureType::TLUT: + case TextureType::RGBA16bpp: + case TextureType::GrayscaleAlpha16bpp: + return width * height * 2; + // 1 byte per pixel + case TextureType::Grayscale8bpp: + case TextureType::Palette8bpp: + case TextureType::GrayscaleAlpha8bpp: + // TODO: We need to validate this MegaMech + case TextureType::GrayscaleAlpha1bpp: + return width * height; + // 1/2 byte per pixel + case TextureType::Palette4bpp: + case TextureType::Grayscale4bpp: + case TextureType::GrayscaleAlpha4bpp: + return (width * height) / 2; + default: + return 0; + } +} + +std::vector TextureUtils::alloc_ia8_text_from_i1(uint16_t *in, int16_t width, int16_t height) { + int32_t inPos; + uint16_t bitMask; + int16_t outPos = 0; + const auto out = new uint8_t[width * height]; + + for (int32_t inPos = 0; inPos < (width * height) / 16; inPos++) { + uint16_t bitMask = 0x8000; + + while (bitMask != 0) { + if (BSWAP16(in[inPos]) & bitMask) { + out[outPos] = 0xFF; + } else { + out[outPos] = 0x00; + } + + bitMask /= 2; + outPos++; + } + } + + auto result = std::vector(out, out + width * height); + delete[] out; + + return result; +} \ No newline at end of file diff --git a/src/utils/TextureUtils.h b/src/utils/TextureUtils.h new file mode 100644 index 0000000..90501c6 --- /dev/null +++ b/src/utils/TextureUtils.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +enum class TextureType { + Error, + RGBA32bpp, + RGBA16bpp, + Palette4bpp, + Palette8bpp, + Grayscale4bpp, + Grayscale8bpp, + GrayscaleAlpha4bpp, + GrayscaleAlpha8bpp, + GrayscaleAlpha16bpp, + GrayscaleAlpha1bpp, + TLUT +}; + +struct TextureFormat { + TextureType type; + uint32_t depth; +}; + +class TextureUtils { + public: + static size_t CalculateTextureSize(TextureType type, uint32_t width, uint32_t height); + static std::vector alloc_ia8_text_from_i1(uint16_t *in, int16_t width, int16_t height); +};