TextureParser.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. #include <cmath>
  2. #include <cstring>
  3. #include <lz4.h>
  4. #include <nlohmann/json.hpp>
  5. #include "TextureParser.h"
  6. #include "WallpaperEngine/Data/Assets/Texture.h"
  7. #include "WallpaperEngine/Logging/Log.h"
  8. using namespace WallpaperEngine::Data::Assets;
  9. using namespace WallpaperEngine::Data::Parsers;
  10. TextureUniquePtr TextureParser::parse (const BinaryReader& file) {
  11. auto result = std::make_unique<Texture> ();
  12. parseTextureHeader (*result, file);
  13. parseContainer (*result, file);
  14. for (uint32_t image = 0; image < result->imageCount; image++) {
  15. const uint32_t mipmapCount = file.nextUInt32 ();
  16. MipmapList mipmaps;
  17. for (uint32_t mipmap = 0; mipmap < mipmapCount; mipmap++) {
  18. mipmaps.emplace_back (parseMipmap (file, *result));
  19. }
  20. result->images.emplace (image, mipmaps);
  21. }
  22. if (!result->isAnimated ()) {
  23. return result;
  24. }
  25. parseAnimations (*result, file);
  26. return result;
  27. }
  28. MipmapSharedPtr TextureParser::parseMipmap (const BinaryReader& file, const Texture& header) {
  29. auto result = std::make_shared<Mipmap> ();
  30. // TEXB0004 has extra header data
  31. if (header.containerVersion == ContainerVersion_TEXB0004) {
  32. // two integers that only seem to affect the editor
  33. std::ignore = file.nextUInt32 ();
  34. std::ignore = file.nextUInt32 ();
  35. // json blob, not parsed yet
  36. result->json = file.nextNullTerminatedString ();
  37. std::ignore = file.nextUInt32 ();
  38. }
  39. result->width = file.nextUInt32 ();
  40. result->height = file.nextUInt32 ();
  41. if (header.containerVersion == ContainerVersion_TEXB0004 || header.containerVersion == ContainerVersion_TEXB0003
  42. || header.containerVersion == ContainerVersion_TEXB0002) {
  43. result->compression = file.nextUInt32 ();
  44. result->uncompressedSize = file.nextInt ();
  45. }
  46. result->compressedSize = file.nextInt ();
  47. if (result->compression == 0) {
  48. // misnamed: in uncompressed files compressedSize actually holds the file length
  49. result->uncompressedSize = result->compressedSize;
  50. }
  51. result->uncompressedData = std::unique_ptr<char[]> (new char[result->uncompressedSize]);
  52. if (result->compression == 1) {
  53. result->compressedData = std::unique_ptr<char[]> (new char[result->compressedSize]);
  54. file.next (result->compressedData.get (), result->compressedSize);
  55. int bytes = LZ4_decompress_safe (
  56. result->compressedData.get (), result->uncompressedData.get (), result->compressedSize,
  57. result->uncompressedSize
  58. );
  59. if (bytes < 0) {
  60. sLog.exception ("Cannot decompress texture data, LZ4_decompress_safe returned an error");
  61. }
  62. } else {
  63. file.next (result->uncompressedData.get (), result->uncompressedSize);
  64. }
  65. return result;
  66. }
  67. FrameSharedPtr TextureParser::parseFrameV1 (const BinaryReader& file) {
  68. auto result = std::make_shared<Frame> ();
  69. result->frameNumber = file.nextUInt32 ();
  70. result->frametime = file.nextFloat ();
  71. result->x = static_cast<float> (file.nextUInt32 ());
  72. result->y = static_cast<float> (file.nextUInt32 ());
  73. result->width1 = static_cast<float> (file.nextUInt32 ());
  74. std::ignore = file.nextUInt32 (); // unknown
  75. std::ignore = file.nextUInt32 (); // unknown
  76. result->height1 = static_cast<float> (file.nextUInt32 ());
  77. return result;
  78. }
  79. FrameSharedPtr TextureParser::parseFrame (const BinaryReader& file) {
  80. auto result = std::make_shared<Frame> ();
  81. result->frameNumber = file.nextUInt32 ();
  82. result->frametime = file.nextFloat ();
  83. result->x = file.nextFloat ();
  84. result->y = file.nextFloat ();
  85. result->width1 = file.nextFloat ();
  86. result->width2 = file.nextFloat ();
  87. result->height2 = file.nextFloat ();
  88. result->height1 = file.nextFloat ();
  89. return result;
  90. }
  91. TextureMap TextureParser::parseTextureMap (const JSON& it) {
  92. if (!it.is_array ()) {
  93. return {};
  94. }
  95. TextureMap result = {};
  96. int textureIndex = -1;
  97. for (const auto& cur : it) {
  98. textureIndex++;
  99. if (cur.is_null ()) {
  100. continue;
  101. }
  102. if (cur.is_object ()) {
  103. const auto nameIt = cur.find ("name");
  104. if (nameIt != cur.end () && nameIt->is_string ()) {
  105. result.emplace (textureIndex, nameIt->get<std::string> ());
  106. }
  107. } else if (cur.is_string ()) {
  108. std::string texName = cur;
  109. if (!texName.empty ()) {
  110. result.emplace (textureIndex, texName);
  111. }
  112. }
  113. }
  114. return result;
  115. }
  116. TextureFormat TextureParser::parseTextureFormat (uint32_t value) {
  117. switch (value) {
  118. case TextureFormat_UNKNOWN:
  119. case TextureFormat_ARGB8888:
  120. case TextureFormat_RGB888:
  121. case TextureFormat_RGB565:
  122. case TextureFormat_DXT5:
  123. case TextureFormat_DXT3:
  124. case TextureFormat_DXT1:
  125. case TextureFormat_RG88:
  126. case TextureFormat_R8:
  127. case TextureFormat_RG1616f:
  128. case TextureFormat_R16f:
  129. case TextureFormat_BC7:
  130. case TextureFormat_RGBa1010102:
  131. case TextureFormat_RGBA16161616f:
  132. case TextureFormat_RGB161616f:
  133. return static_cast<TextureFormat> (value);
  134. default:
  135. sLog.exception ("unknown texture format: ", value);
  136. }
  137. }
  138. void TextureParser::parseTextureHeader (Texture& header, const BinaryReader& file) {
  139. char magic[9] = { 0 };
  140. file.next (magic, 9);
  141. if (strncmp (magic, "TEXV0005", 9) != 0) {
  142. sLog.exception ("unexpected texture container type: ", std::string_view (magic, 9));
  143. }
  144. file.next (magic, 9);
  145. if (strncmp (magic, "TEXI0001", 9) != 0) {
  146. sLog.exception ("unexpected texture sub-container type: ", std::string_view (magic, 9));
  147. }
  148. header.format = parseTextureFormat (file.nextUInt32 ());
  149. header.flags = parseTextureFlags (file.nextUInt32 ());
  150. header.textureWidth = file.nextUInt32 ();
  151. header.textureHeight = file.nextUInt32 ();
  152. header.width = file.nextUInt32 ();
  153. header.height = file.nextUInt32 ();
  154. // ignore some more bytes
  155. std::ignore = file.nextUInt32 ();
  156. }
  157. void TextureParser::parseContainer (Texture& header, const BinaryReader& file) {
  158. char magic[9] = { 0 };
  159. file.next (magic, 9);
  160. header.imageCount = file.nextUInt32 ();
  161. if (strncmp (magic, "TEXB0004", 9) == 0) {
  162. header.containerVersion = ContainerVersion_TEXB0004;
  163. header.freeImageFormat = parseFIF (file.nextUInt32 ());
  164. header.isVideoMp4 = file.nextUInt32 () == 1;
  165. if (header.freeImageFormat == FIF_UNKNOWN && header.isVideoMp4) {
  166. header.freeImageFormat = FIF_MP4;
  167. }
  168. // TEXB0004 behaves like TEXB0003 unless it's actually an MP4
  169. if (header.freeImageFormat != FIF_MP4) {
  170. header.containerVersion = ContainerVersion_TEXB0003;
  171. }
  172. } else if (strncmp (magic, "TEXB0003", 9) == 0) {
  173. header.containerVersion = ContainerVersion_TEXB0003;
  174. header.freeImageFormat = parseFIF (file.nextUInt32 ());
  175. } else if (strncmp (magic, "TEXB0002", 9) == 0) {
  176. header.containerVersion = ContainerVersion_TEXB0002;
  177. } else if (strncmp (magic, "TEXB0001", 9) == 0) {
  178. header.containerVersion = ContainerVersion_TEXB0001;
  179. } else {
  180. sLog.exception ("unknown texture format type: ", std::string_view (magic, 9));
  181. }
  182. }
  183. void TextureParser::parseAnimations (Texture& header, const BinaryReader& file) {
  184. char magic[9] = { 0 };
  185. file.next (magic, 9);
  186. if (strncmp (magic, "TEXS0001", 9) == 0) {
  187. header.animatedVersion = AnimatedVersion_TEXS0001;
  188. } else if (strncmp (magic, "TEXS0002", 9) == 0) {
  189. header.animatedVersion = AnimatedVersion_TEXS0002;
  190. } else if (strncmp (magic, "TEXS0003", 9) == 0) {
  191. header.animatedVersion = AnimatedVersion_TEXS0003;
  192. } else {
  193. sLog.exception ("found animation information of unknown type: ", std::string_view (magic, 9));
  194. }
  195. uint32_t frameCount = file.nextUInt32 ();
  196. if (header.animatedVersion == AnimatedVersion_TEXS0003) {
  197. header.gifWidth = file.nextUInt32 ();
  198. header.gifHeight = file.nextUInt32 ();
  199. }
  200. while (frameCount-- > 0) {
  201. if (header.animatedVersion == AnimatedVersion_TEXS0001) {
  202. header.frames.push_back (parseFrameV1 (file));
  203. } else {
  204. header.frames.push_back (parseFrame (file));
  205. }
  206. }
  207. // TEXS0001/TEXS0002 don't carry gif dimensions in the header, derive them from the first frame
  208. if (header.animatedVersion == AnimatedVersion_TEXS0001 || header.animatedVersion == AnimatedVersion_TEXS0002) {
  209. header.gifWidth = (*header.frames.begin ())->width1;
  210. header.gifHeight = (*header.frames.begin ())->height1;
  211. }
  212. // spritesheets are grid-based; infer the grid from texture size vs. frame size
  213. if (!header.frames.empty () && header.width > 0 && header.height > 0) {
  214. auto& firstFrame = *header.frames.front ();
  215. float frameWidth = firstFrame.width1;
  216. float frameHeight = firstFrame.height1;
  217. if (frameWidth > 0.0f && frameHeight > 0.0f) {
  218. const uint32_t cols = static_cast<uint32_t> (std::round (static_cast<double> (header.width) / frameWidth));
  219. const uint32_t rows
  220. = static_cast<uint32_t> (std::round (static_cast<double> (header.height) / frameHeight));
  221. const uint32_t frameCount = static_cast<uint32_t> (header.frames.size ());
  222. // only accept the grid if it can hold all frames - otherwise plain GIFs (frameWidth == textureWidth)
  223. // would get treated as 1x1 spritesheets
  224. if (cols > 0 && rows > 0 && cols * rows >= frameCount) {
  225. header.spritesheetCols = cols;
  226. header.spritesheetRows = rows;
  227. header.spritesheetFrames = frameCount;
  228. float totalDuration = 0.0f;
  229. for (const auto& frame : header.frames) {
  230. totalDuration += frame->frametime;
  231. }
  232. header.spritesheetDuration = totalDuration;
  233. }
  234. }
  235. }
  236. }
  237. uint32_t TextureParser::parseTextureFlags (uint32_t value) {
  238. if (value < TextureFlags_All) {
  239. return value;
  240. }
  241. sLog.exception ("unknown texture flags: ", value);
  242. }
  243. FIF TextureParser::parseFIF (uint32_t value) {
  244. switch (value) {
  245. case FIF_UNKNOWN:
  246. case FIF_BMP:
  247. case FIF_ICO:
  248. case FIF_JPEG:
  249. case FIF_JNG:
  250. case FIF_KOALA:
  251. case FIF_LBM:
  252. case FIF_MNG:
  253. case FIF_PBM:
  254. case FIF_PBMRAW:
  255. case FIF_PCD:
  256. case FIF_PCX:
  257. case FIF_PGM:
  258. case FIF_PGMRAW:
  259. case FIF_PNG:
  260. case FIF_PPM:
  261. case FIF_PPMRAW:
  262. case FIF_RAS:
  263. case FIF_TARGA:
  264. case FIF_TIFF:
  265. case FIF_WBMP:
  266. case FIF_PSD:
  267. case FIF_CUT:
  268. case FIF_XBM:
  269. case FIF_XPM:
  270. case FIF_DDS:
  271. case FIF_GIF:
  272. case FIF_HDR:
  273. case FIF_FAXG3:
  274. case FIF_SGI:
  275. case FIF_EXR:
  276. case FIF_J2K:
  277. case FIF_JP2:
  278. case FIF_PFM:
  279. case FIF_PICT:
  280. case FIF_RAW:
  281. case FIF_WEBP:
  282. case FIF_JXR:
  283. return static_cast<FIF> (value);
  284. default:
  285. sLog.exception ("unknown free image format: ", value);
  286. }
  287. }
  288. TextureUniquePtr TextureParser::parse (
  289. const BinaryReader& file, const std::string& filename,
  290. std::function<std::string (const std::string&)> metadataLoader
  291. ) {
  292. auto result = parse (file);
  293. if (metadataLoader) {
  294. parseSpritesheetMetadata (*result, filename, metadataLoader);
  295. }
  296. return result;
  297. }
  298. void TextureParser::parseSpritesheetMetadata (
  299. Texture& header, const std::string& filename, std::function<std::string (const std::string&)> metadataLoader
  300. ) {
  301. try {
  302. std::string texJsonContent = metadataLoader (filename + ".tex-json");
  303. nlohmann::json texJson = nlohmann::json::parse (texJsonContent);
  304. if (texJson.contains ("spritesheetsequences") && texJson["spritesheetsequences"].is_array ()) {
  305. auto& sequences = texJson["spritesheetsequences"];
  306. if (!sequences.empty ()) {
  307. auto& firstSeq = sequences[0];
  308. int frames = firstSeq.value ("frames", 0);
  309. float frameWidth = firstSeq.value ("width", 0.0f);
  310. float frameHeight = firstSeq.value ("height", 0.0f);
  311. float duration = firstSeq.value ("duration", 1.0f);
  312. if (frames > 0 && frameWidth > 0.0f && frameHeight > 0.0f && header.width > 0 && header.height > 0) {
  313. header.spritesheetCols = static_cast<uint32_t> (std::round (header.width / frameWidth));
  314. header.spritesheetRows = static_cast<uint32_t> (std::round (header.height / frameHeight));
  315. header.spritesheetFrames = static_cast<uint32_t> (frames);
  316. header.spritesheetDuration = duration;
  317. }
  318. }
  319. }
  320. } catch (const std::exception&) {
  321. // .tex-json file is optional, only used for spritesheet data
  322. }
  323. }