WebPLoader.cpp 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. /*
  2. * Copyright (c) 2023, Nico Weber <thakis@chromium.org>
  3. * Copyright (c) 2024, doctortheemh <doctortheemh@gmail.com>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <AK/Error.h>
  8. #include <LibGfx/ImageFormats/WebPLoader.h>
  9. #include <webp/decode.h>
  10. #include <webp/demux.h>
  11. #include <webp/mux.h>
  12. namespace Gfx {
  13. struct WebPLoadingContext {
  14. enum State {
  15. NotDecoded = 0,
  16. Error,
  17. HeaderDecoded,
  18. BitmapDecoded,
  19. };
  20. State state { State::NotDecoded };
  21. ReadonlyBytes data;
  22. // Image properties
  23. IntSize size;
  24. bool has_alpha;
  25. bool has_animation;
  26. size_t frame_count;
  27. size_t loop_count;
  28. ByteBuffer icc_data;
  29. Vector<ImageFrameDescriptor> frame_descriptors;
  30. };
  31. WebPImageDecoderPlugin::WebPImageDecoderPlugin(ReadonlyBytes data, OwnPtr<WebPLoadingContext> context)
  32. : m_context(move(context))
  33. {
  34. m_context->data = data;
  35. }
  36. WebPImageDecoderPlugin::~WebPImageDecoderPlugin() = default;
  37. IntSize WebPImageDecoderPlugin::size()
  38. {
  39. return m_context->size;
  40. }
  41. static ErrorOr<void> decode_webp_header(WebPLoadingContext& context)
  42. {
  43. if (context.state >= WebPLoadingContext::HeaderDecoded)
  44. return {};
  45. int width = 0;
  46. int height = 0;
  47. int getinfo_result = WebPGetInfo(context.data.data(), context.data.size(), &width, &height);
  48. if (getinfo_result != 1)
  49. return Error::from_string_literal("Failed to decode webp image data");
  50. WebPBitstreamFeatures webp_bitstream_features {};
  51. VP8StatusCode vp8_result = WebPGetFeatures(context.data.data(), context.data.size(), &webp_bitstream_features);
  52. if (vp8_result != VP8_STATUS_OK)
  53. return Error::from_string_literal("Failed to get WebP bitstream features");
  54. // Image header now decoded, save some results for fast access in other parts of the plugin.
  55. context.size = IntSize { width, height };
  56. context.has_animation = webp_bitstream_features.has_animation;
  57. context.has_alpha = webp_bitstream_features.has_alpha;
  58. context.frame_count = 1;
  59. context.loop_count = 0;
  60. if (webp_bitstream_features.has_animation == 1) {
  61. WebPAnimDecoderOptions anim_decoder_options {};
  62. WebPAnimDecoderOptionsInit(&anim_decoder_options);
  63. anim_decoder_options.color_mode = MODE_BGRA;
  64. anim_decoder_options.use_threads = 1;
  65. WebPData webp_data { .bytes = context.data.data(), .size = context.data.size() };
  66. auto* anim_decoder = WebPAnimDecoderNew(&webp_data, &anim_decoder_options);
  67. if (anim_decoder == nullptr)
  68. return Error::from_string_literal("Failed to allocate WebPAnimDecoderNew failed");
  69. ScopeGuard guard { [=]() { WebPAnimDecoderDelete(anim_decoder); } };
  70. WebPAnimInfo anim_info {};
  71. int anim_getinfo_result = WebPAnimDecoderGetInfo(anim_decoder, &anim_info);
  72. if (anim_getinfo_result != 1)
  73. return Error::from_string_literal("Failed to get WebP animation info");
  74. context.frame_count = anim_info.frame_count;
  75. context.loop_count = anim_info.loop_count;
  76. }
  77. WebPData webp_data { .bytes = context.data.data(), .size = context.data.size() };
  78. WebPMux* mux = WebPMuxCreate(&webp_data, 0);
  79. ScopeGuard guard { [=]() { WebPMuxDelete(mux); } };
  80. uint32_t flag = 0;
  81. WebPMuxError err = WebPMuxGetFeatures(mux, &flag);
  82. if (err != WEBP_MUX_OK)
  83. return Error::from_string_literal("Failed to get webp features");
  84. if (flag & ICCP_FLAG) {
  85. WebPData icc_profile;
  86. err = WebPMuxGetChunk(mux, "ICCP", &icc_profile);
  87. if (err != WEBP_MUX_OK)
  88. return Error::from_string_literal("Failed to get ICCP chunk of webp");
  89. context.icc_data = TRY(context.icc_data.copy(icc_profile.bytes, icc_profile.size));
  90. }
  91. context.state = WebPLoadingContext::State::HeaderDecoded;
  92. return {};
  93. }
  94. static ErrorOr<void> decode_webp_image(WebPLoadingContext& context)
  95. {
  96. VERIFY(context.state >= WebPLoadingContext::State::HeaderDecoded);
  97. if (context.has_animation) {
  98. WebPAnimDecoderOptions anim_decoder_options {};
  99. WebPAnimDecoderOptionsInit(&anim_decoder_options);
  100. anim_decoder_options.color_mode = MODE_BGRA;
  101. anim_decoder_options.use_threads = 1;
  102. WebPData webp_data { .bytes = context.data.data(), .size = context.data.size() };
  103. auto* anim_decoder = WebPAnimDecoderNew(&webp_data, &anim_decoder_options);
  104. if (anim_decoder == nullptr)
  105. return Error::from_string_literal("Failed to allocate WebPAnimDecoderNew failed");
  106. ScopeGuard guard { [=]() { WebPAnimDecoderDelete(anim_decoder); } };
  107. int old_timestamp = 0;
  108. while (WebPAnimDecoderHasMoreFrames(anim_decoder)) {
  109. uint8_t* frame_data = nullptr;
  110. int timestamp = 0;
  111. if (!WebPAnimDecoderGetNext(anim_decoder, &frame_data, &timestamp))
  112. return Error::from_string_literal("Failed to decode animated frame");
  113. auto bitmap_format = context.has_alpha ? BitmapFormat::BGRA8888 : BitmapFormat::BGRx8888;
  114. auto bitmap = TRY(Bitmap::create(bitmap_format, context.size));
  115. memcpy(bitmap->scanline_u8(0), frame_data, context.size.width() * context.size.height() * 4);
  116. auto duration = timestamp - old_timestamp;
  117. old_timestamp = timestamp;
  118. context.frame_descriptors.append(ImageFrameDescriptor { bitmap, duration });
  119. }
  120. } else {
  121. auto bitmap_format = context.has_alpha ? BitmapFormat::BGRA8888 : BitmapFormat::BGRx8888;
  122. auto bitmap = TRY(Bitmap::create(bitmap_format, context.size));
  123. auto image_data = WebPDecodeBGRAInto(context.data.data(), context.data.size(), bitmap->scanline_u8(0), bitmap->data_size(), bitmap->pitch());
  124. if (image_data == nullptr)
  125. return Error::from_string_literal("Failed to decode webp image into bitmap");
  126. auto duration = 0;
  127. context.frame_descriptors.append(ImageFrameDescriptor { bitmap, duration });
  128. }
  129. return {};
  130. }
  131. bool WebPImageDecoderPlugin::sniff(ReadonlyBytes data)
  132. {
  133. WebPLoadingContext context;
  134. context.data = data;
  135. return !decode_webp_header(context).is_error();
  136. }
  137. ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> WebPImageDecoderPlugin::create(ReadonlyBytes data)
  138. {
  139. auto context = TRY(try_make<WebPLoadingContext>());
  140. auto plugin = TRY(adopt_nonnull_own_or_enomem(new (nothrow) WebPImageDecoderPlugin(data, move(context))));
  141. TRY(decode_webp_header(*plugin->m_context));
  142. return plugin;
  143. }
  144. bool WebPImageDecoderPlugin::is_animated()
  145. {
  146. return m_context->has_animation;
  147. }
  148. size_t WebPImageDecoderPlugin::loop_count()
  149. {
  150. if (!is_animated())
  151. return 0;
  152. return m_context->loop_count;
  153. }
  154. size_t WebPImageDecoderPlugin::frame_count()
  155. {
  156. if (!is_animated())
  157. return 1;
  158. return m_context->frame_count;
  159. }
  160. size_t WebPImageDecoderPlugin::first_animated_frame_index()
  161. {
  162. return 0;
  163. }
  164. ErrorOr<ImageFrameDescriptor> WebPImageDecoderPlugin::frame(size_t index, Optional<IntSize>)
  165. {
  166. if (index >= frame_count())
  167. return Error::from_string_literal("WebPImageDecoderPlugin: Invalid frame index");
  168. if (m_context->state == WebPLoadingContext::State::Error)
  169. return Error::from_string_literal("WebPImageDecoderPlugin: Decoding failed");
  170. if (m_context->state < WebPLoadingContext::State::BitmapDecoded) {
  171. TRY(decode_webp_image(*m_context));
  172. m_context->state = WebPLoadingContext::State::BitmapDecoded;
  173. }
  174. if (index >= m_context->frame_descriptors.size())
  175. return Error::from_string_literal("WebPImageDecoderPlugin: Invalid frame index");
  176. return m_context->frame_descriptors[index];
  177. }
  178. ErrorOr<Optional<ReadonlyBytes>> WebPImageDecoderPlugin::icc_data()
  179. {
  180. if (m_context->state < WebPLoadingContext::State::HeaderDecoded)
  181. (void)frame(0);
  182. if (!m_context->icc_data.is_empty())
  183. return m_context->icc_data;
  184. return OptionalNone {};
  185. }
  186. }