| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392 |
- #include <cmath>
- #include <cstring>
- #include <lz4.h>
- #include <nlohmann/json.hpp>
- #include "TextureParser.h"
- #include "WallpaperEngine/Data/Assets/Texture.h"
- #include "WallpaperEngine/Logging/Log.h"
- using namespace WallpaperEngine::Data::Assets;
- using namespace WallpaperEngine::Data::Parsers;
- TextureUniquePtr TextureParser::parse (const BinaryReader& file) {
- auto result = std::make_unique<Texture> ();
- parseTextureHeader (*result, file);
- parseContainer (*result, file);
- for (uint32_t image = 0; image < result->imageCount; image++) {
- const uint32_t mipmapCount = file.nextUInt32 ();
- MipmapList mipmaps;
- for (uint32_t mipmap = 0; mipmap < mipmapCount; mipmap++) {
- mipmaps.emplace_back (parseMipmap (file, *result));
- }
- result->images.emplace (image, mipmaps);
- }
- if (!result->isAnimated ()) {
- return result;
- }
- parseAnimations (*result, file);
- return result;
- }
- MipmapSharedPtr TextureParser::parseMipmap (const BinaryReader& file, const Texture& header) {
- auto result = std::make_shared<Mipmap> ();
- // TEXB0004 has extra header data
- if (header.containerVersion == ContainerVersion_TEXB0004) {
- // two integers that only seem to affect the editor
- std::ignore = file.nextUInt32 ();
- std::ignore = file.nextUInt32 ();
- // json blob, not parsed yet
- result->json = file.nextNullTerminatedString ();
- std::ignore = file.nextUInt32 ();
- }
- result->width = file.nextUInt32 ();
- result->height = file.nextUInt32 ();
- if (header.containerVersion == ContainerVersion_TEXB0004 || header.containerVersion == ContainerVersion_TEXB0003
- || header.containerVersion == ContainerVersion_TEXB0002) {
- result->compression = file.nextUInt32 ();
- result->uncompressedSize = file.nextInt ();
- }
- result->compressedSize = file.nextInt ();
- if (result->compression == 0) {
- // misnamed: in uncompressed files compressedSize actually holds the file length
- result->uncompressedSize = result->compressedSize;
- }
- result->uncompressedData = std::unique_ptr<char[]> (new char[result->uncompressedSize]);
- if (result->compression == 1) {
- result->compressedData = std::unique_ptr<char[]> (new char[result->compressedSize]);
- file.next (result->compressedData.get (), result->compressedSize);
- int bytes = LZ4_decompress_safe (
- result->compressedData.get (), result->uncompressedData.get (), result->compressedSize,
- result->uncompressedSize
- );
- if (bytes < 0) {
- sLog.exception ("Cannot decompress texture data, LZ4_decompress_safe returned an error");
- }
- } else {
- file.next (result->uncompressedData.get (), result->uncompressedSize);
- }
- return result;
- }
- FrameSharedPtr TextureParser::parseFrameV1 (const BinaryReader& file) {
- auto result = std::make_shared<Frame> ();
- result->frameNumber = file.nextUInt32 ();
- result->frametime = file.nextFloat ();
- result->x = static_cast<float> (file.nextUInt32 ());
- result->y = static_cast<float> (file.nextUInt32 ());
- result->width1 = static_cast<float> (file.nextUInt32 ());
- std::ignore = file.nextUInt32 (); // unknown
- std::ignore = file.nextUInt32 (); // unknown
- result->height1 = static_cast<float> (file.nextUInt32 ());
- return result;
- }
- FrameSharedPtr TextureParser::parseFrame (const BinaryReader& file) {
- auto result = std::make_shared<Frame> ();
- result->frameNumber = file.nextUInt32 ();
- result->frametime = file.nextFloat ();
- result->x = file.nextFloat ();
- result->y = file.nextFloat ();
- result->width1 = file.nextFloat ();
- result->width2 = file.nextFloat ();
- result->height2 = file.nextFloat ();
- result->height1 = file.nextFloat ();
- return result;
- }
- TextureMap TextureParser::parseTextureMap (const JSON& it) {
- if (!it.is_array ()) {
- return {};
- }
- TextureMap result = {};
- int textureIndex = -1;
- for (const auto& cur : it) {
- textureIndex++;
- if (cur.is_null ()) {
- continue;
- }
- if (cur.is_object ()) {
- const auto nameIt = cur.find ("name");
- if (nameIt != cur.end () && nameIt->is_string ()) {
- result.emplace (textureIndex, nameIt->get<std::string> ());
- }
- } else if (cur.is_string ()) {
- std::string texName = cur;
- if (!texName.empty ()) {
- result.emplace (textureIndex, texName);
- }
- }
- }
- return result;
- }
- TextureFormat TextureParser::parseTextureFormat (uint32_t value) {
- switch (value) {
- case TextureFormat_UNKNOWN:
- case TextureFormat_ARGB8888:
- case TextureFormat_RGB888:
- case TextureFormat_RGB565:
- case TextureFormat_DXT5:
- case TextureFormat_DXT3:
- case TextureFormat_DXT1:
- case TextureFormat_RG88:
- case TextureFormat_R8:
- case TextureFormat_RG1616f:
- case TextureFormat_R16f:
- case TextureFormat_BC7:
- case TextureFormat_RGBa1010102:
- case TextureFormat_RGBA16161616f:
- case TextureFormat_RGB161616f:
- return static_cast<TextureFormat> (value);
- default:
- sLog.exception ("unknown texture format: ", value);
- }
- }
- void TextureParser::parseTextureHeader (Texture& header, const BinaryReader& file) {
- char magic[9] = { 0 };
- file.next (magic, 9);
- if (strncmp (magic, "TEXV0005", 9) != 0) {
- sLog.exception ("unexpected texture container type: ", std::string_view (magic, 9));
- }
- file.next (magic, 9);
- if (strncmp (magic, "TEXI0001", 9) != 0) {
- sLog.exception ("unexpected texture sub-container type: ", std::string_view (magic, 9));
- }
- header.format = parseTextureFormat (file.nextUInt32 ());
- header.flags = parseTextureFlags (file.nextUInt32 ());
- header.textureWidth = file.nextUInt32 ();
- header.textureHeight = file.nextUInt32 ();
- header.width = file.nextUInt32 ();
- header.height = file.nextUInt32 ();
- // ignore some more bytes
- std::ignore = file.nextUInt32 ();
- }
- void TextureParser::parseContainer (Texture& header, const BinaryReader& file) {
- char magic[9] = { 0 };
- file.next (magic, 9);
- header.imageCount = file.nextUInt32 ();
- if (strncmp (magic, "TEXB0004", 9) == 0) {
- header.containerVersion = ContainerVersion_TEXB0004;
- header.freeImageFormat = parseFIF (file.nextUInt32 ());
- header.isVideoMp4 = file.nextUInt32 () == 1;
- if (header.freeImageFormat == FIF_UNKNOWN && header.isVideoMp4) {
- header.freeImageFormat = FIF_MP4;
- }
- // TEXB0004 behaves like TEXB0003 unless it's actually an MP4
- if (header.freeImageFormat != FIF_MP4) {
- header.containerVersion = ContainerVersion_TEXB0003;
- }
- } else if (strncmp (magic, "TEXB0003", 9) == 0) {
- header.containerVersion = ContainerVersion_TEXB0003;
- header.freeImageFormat = parseFIF (file.nextUInt32 ());
- } else if (strncmp (magic, "TEXB0002", 9) == 0) {
- header.containerVersion = ContainerVersion_TEXB0002;
- } else if (strncmp (magic, "TEXB0001", 9) == 0) {
- header.containerVersion = ContainerVersion_TEXB0001;
- } else {
- sLog.exception ("unknown texture format type: ", std::string_view (magic, 9));
- }
- }
- void TextureParser::parseAnimations (Texture& header, const BinaryReader& file) {
- char magic[9] = { 0 };
- file.next (magic, 9);
- if (strncmp (magic, "TEXS0001", 9) == 0) {
- header.animatedVersion = AnimatedVersion_TEXS0001;
- } else if (strncmp (magic, "TEXS0002", 9) == 0) {
- header.animatedVersion = AnimatedVersion_TEXS0002;
- } else if (strncmp (magic, "TEXS0003", 9) == 0) {
- header.animatedVersion = AnimatedVersion_TEXS0003;
- } else {
- sLog.exception ("found animation information of unknown type: ", std::string_view (magic, 9));
- }
- uint32_t frameCount = file.nextUInt32 ();
- if (header.animatedVersion == AnimatedVersion_TEXS0003) {
- header.gifWidth = file.nextUInt32 ();
- header.gifHeight = file.nextUInt32 ();
- }
- while (frameCount-- > 0) {
- if (header.animatedVersion == AnimatedVersion_TEXS0001) {
- header.frames.push_back (parseFrameV1 (file));
- } else {
- header.frames.push_back (parseFrame (file));
- }
- }
- // TEXS0001/TEXS0002 don't carry gif dimensions in the header, derive them from the first frame
- if (header.animatedVersion == AnimatedVersion_TEXS0001 || header.animatedVersion == AnimatedVersion_TEXS0002) {
- header.gifWidth = (*header.frames.begin ())->width1;
- header.gifHeight = (*header.frames.begin ())->height1;
- }
- // spritesheets are grid-based; infer the grid from texture size vs. frame size
- if (!header.frames.empty () && header.width > 0 && header.height > 0) {
- auto& firstFrame = *header.frames.front ();
- float frameWidth = firstFrame.width1;
- float frameHeight = firstFrame.height1;
- if (frameWidth > 0.0f && frameHeight > 0.0f) {
- const uint32_t cols = static_cast<uint32_t> (std::round (static_cast<double> (header.width) / frameWidth));
- const uint32_t rows
- = static_cast<uint32_t> (std::round (static_cast<double> (header.height) / frameHeight));
- const uint32_t frameCount = static_cast<uint32_t> (header.frames.size ());
- // only accept the grid if it can hold all frames - otherwise plain GIFs (frameWidth == textureWidth)
- // would get treated as 1x1 spritesheets
- if (cols > 0 && rows > 0 && cols * rows >= frameCount) {
- header.spritesheetCols = cols;
- header.spritesheetRows = rows;
- header.spritesheetFrames = frameCount;
- float totalDuration = 0.0f;
- for (const auto& frame : header.frames) {
- totalDuration += frame->frametime;
- }
- header.spritesheetDuration = totalDuration;
- }
- }
- }
- }
- uint32_t TextureParser::parseTextureFlags (uint32_t value) {
- if (value < TextureFlags_All) {
- return value;
- }
- sLog.exception ("unknown texture flags: ", value);
- }
- FIF TextureParser::parseFIF (uint32_t value) {
- switch (value) {
- case FIF_UNKNOWN:
- case FIF_BMP:
- case FIF_ICO:
- case FIF_JPEG:
- case FIF_JNG:
- case FIF_KOALA:
- case FIF_LBM:
- case FIF_MNG:
- case FIF_PBM:
- case FIF_PBMRAW:
- case FIF_PCD:
- case FIF_PCX:
- case FIF_PGM:
- case FIF_PGMRAW:
- case FIF_PNG:
- case FIF_PPM:
- case FIF_PPMRAW:
- case FIF_RAS:
- case FIF_TARGA:
- case FIF_TIFF:
- case FIF_WBMP:
- case FIF_PSD:
- case FIF_CUT:
- case FIF_XBM:
- case FIF_XPM:
- case FIF_DDS:
- case FIF_GIF:
- case FIF_HDR:
- case FIF_FAXG3:
- case FIF_SGI:
- case FIF_EXR:
- case FIF_J2K:
- case FIF_JP2:
- case FIF_PFM:
- case FIF_PICT:
- case FIF_RAW:
- case FIF_WEBP:
- case FIF_JXR:
- return static_cast<FIF> (value);
- default:
- sLog.exception ("unknown free image format: ", value);
- }
- }
- TextureUniquePtr TextureParser::parse (
- const BinaryReader& file, const std::string& filename,
- std::function<std::string (const std::string&)> metadataLoader
- ) {
- auto result = parse (file);
- if (metadataLoader) {
- parseSpritesheetMetadata (*result, filename, metadataLoader);
- }
- return result;
- }
- void TextureParser::parseSpritesheetMetadata (
- Texture& header, const std::string& filename, std::function<std::string (const std::string&)> metadataLoader
- ) {
- try {
- std::string texJsonContent = metadataLoader (filename + ".tex-json");
- nlohmann::json texJson = nlohmann::json::parse (texJsonContent);
- if (texJson.contains ("spritesheetsequences") && texJson["spritesheetsequences"].is_array ()) {
- auto& sequences = texJson["spritesheetsequences"];
- if (!sequences.empty ()) {
- auto& firstSeq = sequences[0];
- int frames = firstSeq.value ("frames", 0);
- float frameWidth = firstSeq.value ("width", 0.0f);
- float frameHeight = firstSeq.value ("height", 0.0f);
- float duration = firstSeq.value ("duration", 1.0f);
- if (frames > 0 && frameWidth > 0.0f && frameHeight > 0.0f && header.width > 0 && header.height > 0) {
- header.spritesheetCols = static_cast<uint32_t> (std::round (header.width / frameWidth));
- header.spritesheetRows = static_cast<uint32_t> (std::round (header.height / frameHeight));
- header.spritesheetFrames = static_cast<uint32_t> (frames);
- header.spritesheetDuration = duration;
- }
- }
- }
- } catch (const std::exception&) {
- // .tex-json file is optional, only used for spritesheet data
- }
- }
|