SogLoader.cpp 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. #include "SogLoader.h"
  2. #include <algorithm>
  3. #include <cmath>
  4. #include <cstring>
  5. #include <memory>
  6. #include <stdexcept>
  7. #include <nlohmann/json.hpp>
  8. extern "C" {
  9. #include <libavcodec/avcodec.h>
  10. #include <libavutil/frame.h>
  11. #include <libswscale/swscale.h>
  12. }
  13. namespace WallpaperEngine::Splat {
  14. namespace {
  15. constexpr float SH_C0 = 0.28209479177387814f;
  16. struct CodecContextDeleter {
  17. void operator() (AVCodecContext* ctx) const { avcodec_free_context (&ctx); }
  18. };
  19. struct PacketDeleter {
  20. void operator() (AVPacket* packet) const { av_packet_free (&packet); }
  21. };
  22. struct FrameDeleter {
  23. void operator() (AVFrame* frame) const { av_frame_free (&frame); }
  24. };
  25. struct SwsDeleter {
  26. void operator() (SwsContext* ctx) const { sws_freeContext (ctx); }
  27. };
  28. std::vector<float> readCodebook (const nlohmann::json& section, const char* name) {
  29. auto codebook = section.at ("codebook").get<std::vector<float>> ();
  30. if (codebook.size () != 256) {
  31. throw std::runtime_error (std::string ("SOG ") + name + " codebook must have 256 entries");
  32. }
  33. return codebook;
  34. }
  35. DecodedImage readImage (const FileReader& readFile, const nlohmann::json& section, size_t fileIndex) {
  36. const auto& files = section.at ("files");
  37. if (!files.is_array () || files.size () <= fileIndex) {
  38. throw std::runtime_error ("SOG meta lists too few files for a section");
  39. }
  40. return decodeWebP (readFile (files.at (fileIndex).get<std::string> ()));
  41. }
  42. inline float unpackByte (uint8_t value) { return static_cast<float> (value) / 255.0f; }
  43. inline uint32_t packColor (float r, float g, float b, float a) {
  44. const auto toByte = [] (float value) {
  45. return static_cast<uint32_t> (std::lround (std::clamp (value, 0.0f, 1.0f) * 255.0f));
  46. };
  47. return toByte (r) | (toByte (g) << 8) | (toByte (b) << 16) | (toByte (a) << 24);
  48. }
  49. } // namespace
  50. DecodedImage decodeWebP (const std::string& bytes) {
  51. const AVCodec* codec = avcodec_find_decoder (AV_CODEC_ID_WEBP);
  52. if (codec == nullptr) {
  53. throw std::runtime_error ("This FFmpeg build has no WebP decoder");
  54. }
  55. std::unique_ptr<AVCodecContext, CodecContextDeleter> context (avcodec_alloc_context3 (codec));
  56. if (!context || avcodec_open2 (context.get (), codec, nullptr) < 0) {
  57. throw std::runtime_error ("Cannot open the FFmpeg WebP decoder");
  58. }
  59. std::unique_ptr<AVPacket, PacketDeleter> packet (av_packet_alloc ());
  60. // av_new_packet allocates the input padding the decoders require
  61. if (!packet || av_new_packet (packet.get (), static_cast<int> (bytes.size ())) < 0) {
  62. throw std::runtime_error ("Cannot allocate a WebP packet");
  63. }
  64. std::memcpy (packet->data, bytes.data (), bytes.size ());
  65. std::unique_ptr<AVFrame, FrameDeleter> frame (av_frame_alloc ());
  66. if (avcodec_send_packet (context.get (), packet.get ()) < 0 || avcodec_send_packet (context.get (), nullptr) < 0
  67. || avcodec_receive_frame (context.get (), frame.get ()) < 0) {
  68. throw std::runtime_error ("Cannot decode a WebP image");
  69. }
  70. DecodedImage image;
  71. image.width = frame->width;
  72. image.height = frame->height;
  73. image.rgba.resize (static_cast<size_t> (image.width) * image.height * 4);
  74. std::unique_ptr<SwsContext, SwsDeleter> scaler (sws_getContext (
  75. frame->width, frame->height, static_cast<AVPixelFormat> (frame->format), frame->width, frame->height,
  76. AV_PIX_FMT_RGBA, SWS_POINT, nullptr, nullptr, nullptr
  77. ));
  78. if (!scaler) {
  79. throw std::runtime_error ("Cannot convert the decoded WebP to RGBA");
  80. }
  81. uint8_t* destination[4] = { image.rgba.data (), nullptr, nullptr, nullptr };
  82. int destinationStride[4] = { image.width * 4, 0, 0, 0 };
  83. sws_scale (scaler.get (), frame->data, frame->linesize, 0, frame->height, destination, destinationStride);
  84. return image;
  85. }
  86. SplatCloud loadSog (const std::string& metaText, const FileReader& readFile) {
  87. const auto meta = nlohmann::json::parse (metaText);
  88. if (meta.value ("version", 0) != 2) {
  89. throw std::runtime_error ("Only SOG version 2 files are supported");
  90. }
  91. const uint32_t count = meta.at ("count").get<uint32_t> ();
  92. const auto& means = meta.at ("means");
  93. const auto mins = means.at ("mins").get<std::vector<float>> ();
  94. const auto maxs = means.at ("maxs").get<std::vector<float>> ();
  95. if (mins.size () != 3 || maxs.size () != 3) {
  96. throw std::runtime_error ("SOG means mins/maxs must have 3 entries");
  97. }
  98. const auto& scalesMeta = meta.at ("scales");
  99. const auto& colorMeta = meta.at ("sh0");
  100. const auto scaleCodebook = readCodebook (scalesMeta, "scales");
  101. const auto colorCodebook = readCodebook (colorMeta, "sh0");
  102. const DecodedImage meansLow = readImage (readFile, means, 0);
  103. const DecodedImage meansHigh = readImage (readFile, means, 1);
  104. const DecodedImage quats = readImage (readFile, meta.at ("quats"), 0);
  105. const DecodedImage scales = readImage (readFile, scalesMeta, 0);
  106. const DecodedImage colors = readImage (readFile, colorMeta, 0);
  107. for (const DecodedImage* image : { &meansHigh, &quats, &scales, &colors }) {
  108. if (image->width != meansLow.width || image->height != meansLow.height) {
  109. throw std::runtime_error ("SOG images do not all have the same dimensions");
  110. }
  111. }
  112. if (static_cast<uint64_t> (meansLow.width) * meansLow.height < count) {
  113. throw std::runtime_error ("SOG images are too small for the declared splat count");
  114. }
  115. SplatCloud cloud;
  116. cloud.count = count;
  117. cloud.textureWidth = static_cast<uint32_t> (meansLow.width);
  118. cloud.textureHeight = static_cast<uint32_t> ((count + cloud.textureWidth - 1) / cloud.textureWidth);
  119. const size_t texels = static_cast<size_t> (cloud.textureWidth) * cloud.textureHeight;
  120. cloud.centerAndColor.assign (texels * 4, 0.0f);
  121. cloud.rotation.assign (texels * 4, 0.0f);
  122. cloud.scale.assign (texels * 4, 0.0f);
  123. const float normalizer = std::sqrt (2.0f);
  124. for (uint32_t i = 0; i < count; i++) {
  125. const uint8_t* low = &meansLow.rgba[static_cast<size_t> (i) * 4];
  126. const uint8_t* high = &meansHigh.rgba[static_cast<size_t> (i) * 4];
  127. const uint8_t* quat = &quats.rgba[static_cast<size_t> (i) * 4];
  128. const uint8_t* scale = &scales.rgba[static_cast<size_t> (i) * 4];
  129. const uint8_t* color = &colors.rgba[static_cast<size_t> (i) * 4];
  130. float* centerAndColor = &cloud.centerAndColor[static_cast<size_t> (i) * 4];
  131. for (int axis = 0; axis < 3; axis++) {
  132. const float normalized = static_cast<float> (low[axis] + high[axis] * 256) / 65535.0f;
  133. const float logSpace = mins[axis] + (maxs[axis] - mins[axis]) * normalized;
  134. centerAndColor[axis] = std::copysign (std::expm1 (std::fabs (logSpace)), logSpace);
  135. }
  136. const uint32_t packed = packColor (
  137. 0.5f + colorCodebook[color[0]] * SH_C0, 0.5f + colorCodebook[color[1]] * SH_C0,
  138. 0.5f + colorCodebook[color[2]] * SH_C0, unpackByte (color[3])
  139. );
  140. std::memcpy (&centerAndColor[3], &packed, sizeof (packed));
  141. // three quantized components plus the index of the dropped (largest) one, in (w, x, y, z) order
  142. const float a = (unpackByte (quat[0]) - 0.5f) * normalizer;
  143. const float b = (unpackByte (quat[1]) - 0.5f) * normalizer;
  144. const float c = (unpackByte (quat[2]) - 0.5f) * normalizer;
  145. const float d = std::sqrt (std::max (0.0f, 1.0f - (a * a + b * b + c * c)));
  146. float x, y, z, w;
  147. switch (quat[3] - 252) {
  148. case 0:
  149. x = a;
  150. y = b;
  151. z = c;
  152. w = d;
  153. break;
  154. case 1:
  155. x = d;
  156. y = b;
  157. z = c;
  158. w = a;
  159. break;
  160. case 2:
  161. x = b;
  162. y = d;
  163. z = c;
  164. w = a;
  165. break;
  166. case 3:
  167. x = b;
  168. y = c;
  169. z = d;
  170. w = a;
  171. break;
  172. default:
  173. x = 0.0f;
  174. y = 0.0f;
  175. z = 0.0f;
  176. w = 1.0f;
  177. break;
  178. }
  179. const float length = std::sqrt (x * x + y * y + z * z + w * w);
  180. const float inverseLength = length > 0.0f ? 1.0f / length : 1.0f;
  181. float* rotation = &cloud.rotation[static_cast<size_t> (i) * 4];
  182. rotation[0] = x * inverseLength;
  183. rotation[1] = y * inverseLength;
  184. rotation[2] = z * inverseLength;
  185. rotation[3] = w * inverseLength;
  186. float* scaleOut = &cloud.scale[static_cast<size_t> (i) * 4];
  187. scaleOut[0] = std::exp (scaleCodebook[scale[0]]);
  188. scaleOut[1] = std::exp (scaleCodebook[scale[1]]);
  189. scaleOut[2] = std::exp (scaleCodebook[scale[2]]);
  190. }
  191. return cloud;
  192. }
  193. } // namespace WallpaperEngine::Splat