123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- /*
- * Copyright (c) 2023, Nico Weber <thakis@chromium.org>
- * Copyright (c) 2024, doctortheemh <doctortheemh@gmail.com>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
- #include <AK/Error.h>
- #include <LibGfx/ImageFormats/WebPLoader.h>
- #include <webp/decode.h>
- #include <webp/demux.h>
- #include <webp/mux.h>
- namespace Gfx {
- struct WebPLoadingContext {
- enum State {
- NotDecoded = 0,
- Error,
- HeaderDecoded,
- BitmapDecoded,
- };
- State state { State::NotDecoded };
- ReadonlyBytes data;
- // Image properties
- IntSize size;
- bool has_alpha;
- bool has_animation;
- size_t frame_count;
- size_t loop_count;
- ByteBuffer icc_data;
- Vector<ImageFrameDescriptor> frame_descriptors;
- };
- WebPImageDecoderPlugin::WebPImageDecoderPlugin(ReadonlyBytes data, OwnPtr<WebPLoadingContext> context)
- : m_context(move(context))
- {
- m_context->data = data;
- }
- WebPImageDecoderPlugin::~WebPImageDecoderPlugin() = default;
- IntSize WebPImageDecoderPlugin::size()
- {
- return m_context->size;
- }
- static ErrorOr<void> decode_webp_header(WebPLoadingContext& context)
- {
- if (context.state >= WebPLoadingContext::HeaderDecoded)
- return {};
- int width = 0;
- int height = 0;
- int getinfo_result = WebPGetInfo(context.data.data(), context.data.size(), &width, &height);
- if (getinfo_result != 1)
- return Error::from_string_literal("Failed to decode webp image data");
- WebPBitstreamFeatures webp_bitstream_features {};
- VP8StatusCode vp8_result = WebPGetFeatures(context.data.data(), context.data.size(), &webp_bitstream_features);
- if (vp8_result != VP8_STATUS_OK)
- return Error::from_string_literal("Failed to get WebP bitstream features");
- // Image header now decoded, save some results for fast access in other parts of the plugin.
- context.size = IntSize { width, height };
- context.has_animation = webp_bitstream_features.has_animation;
- context.has_alpha = webp_bitstream_features.has_alpha;
- context.frame_count = 1;
- context.loop_count = 0;
- if (webp_bitstream_features.has_animation == 1) {
- WebPAnimDecoderOptions anim_decoder_options {};
- WebPAnimDecoderOptionsInit(&anim_decoder_options);
- anim_decoder_options.color_mode = MODE_BGRA;
- anim_decoder_options.use_threads = 1;
- WebPData webp_data { .bytes = context.data.data(), .size = context.data.size() };
- auto* anim_decoder = WebPAnimDecoderNew(&webp_data, &anim_decoder_options);
- if (anim_decoder == nullptr)
- return Error::from_string_literal("Failed to allocate WebPAnimDecoderNew failed");
- ScopeGuard guard { [=]() { WebPAnimDecoderDelete(anim_decoder); } };
- WebPAnimInfo anim_info {};
- int anim_getinfo_result = WebPAnimDecoderGetInfo(anim_decoder, &anim_info);
- if (anim_getinfo_result != 1)
- return Error::from_string_literal("Failed to get WebP animation info");
- context.frame_count = anim_info.frame_count;
- context.loop_count = anim_info.loop_count;
- }
- WebPData webp_data { .bytes = context.data.data(), .size = context.data.size() };
- WebPMux* mux = WebPMuxCreate(&webp_data, 0);
- ScopeGuard guard { [=]() { WebPMuxDelete(mux); } };
- uint32_t flag = 0;
- WebPMuxError err = WebPMuxGetFeatures(mux, &flag);
- if (err != WEBP_MUX_OK)
- return Error::from_string_literal("Failed to get webp features");
- if (flag & ICCP_FLAG) {
- WebPData icc_profile;
- err = WebPMuxGetChunk(mux, "ICCP", &icc_profile);
- if (err != WEBP_MUX_OK)
- return Error::from_string_literal("Failed to get ICCP chunk of webp");
- context.icc_data = TRY(context.icc_data.copy(icc_profile.bytes, icc_profile.size));
- }
- context.state = WebPLoadingContext::State::HeaderDecoded;
- return {};
- }
- static ErrorOr<void> decode_webp_image(WebPLoadingContext& context)
- {
- VERIFY(context.state >= WebPLoadingContext::State::HeaderDecoded);
- if (context.has_animation) {
- WebPAnimDecoderOptions anim_decoder_options {};
- WebPAnimDecoderOptionsInit(&anim_decoder_options);
- anim_decoder_options.color_mode = MODE_BGRA;
- anim_decoder_options.use_threads = 1;
- WebPData webp_data { .bytes = context.data.data(), .size = context.data.size() };
- auto* anim_decoder = WebPAnimDecoderNew(&webp_data, &anim_decoder_options);
- if (anim_decoder == nullptr)
- return Error::from_string_literal("Failed to allocate WebPAnimDecoderNew failed");
- ScopeGuard guard { [=]() { WebPAnimDecoderDelete(anim_decoder); } };
- int old_timestamp = 0;
- while (WebPAnimDecoderHasMoreFrames(anim_decoder)) {
- uint8_t* frame_data = nullptr;
- int timestamp = 0;
- if (!WebPAnimDecoderGetNext(anim_decoder, &frame_data, ×tamp))
- return Error::from_string_literal("Failed to decode animated frame");
- auto bitmap_format = context.has_alpha ? BitmapFormat::BGRA8888 : BitmapFormat::BGRx8888;
- auto bitmap = TRY(Bitmap::create(bitmap_format, context.size));
- memcpy(bitmap->scanline_u8(0), frame_data, context.size.width() * context.size.height() * 4);
- auto duration = timestamp - old_timestamp;
- old_timestamp = timestamp;
- context.frame_descriptors.append(ImageFrameDescriptor { bitmap, duration });
- }
- } else {
- auto bitmap_format = context.has_alpha ? BitmapFormat::BGRA8888 : BitmapFormat::BGRx8888;
- auto bitmap = TRY(Bitmap::create(bitmap_format, context.size));
- auto image_data = WebPDecodeBGRAInto(context.data.data(), context.data.size(), bitmap->scanline_u8(0), bitmap->data_size(), bitmap->pitch());
- if (image_data == nullptr)
- return Error::from_string_literal("Failed to decode webp image into bitmap");
- auto duration = 0;
- context.frame_descriptors.append(ImageFrameDescriptor { bitmap, duration });
- }
- return {};
- }
- bool WebPImageDecoderPlugin::sniff(ReadonlyBytes data)
- {
- WebPLoadingContext context;
- context.data = data;
- return !decode_webp_header(context).is_error();
- }
- ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> WebPImageDecoderPlugin::create(ReadonlyBytes data)
- {
- auto context = TRY(try_make<WebPLoadingContext>());
- auto plugin = TRY(adopt_nonnull_own_or_enomem(new (nothrow) WebPImageDecoderPlugin(data, move(context))));
- TRY(decode_webp_header(*plugin->m_context));
- return plugin;
- }
- bool WebPImageDecoderPlugin::is_animated()
- {
- return m_context->has_animation;
- }
- size_t WebPImageDecoderPlugin::loop_count()
- {
- if (!is_animated())
- return 0;
- return m_context->loop_count;
- }
- size_t WebPImageDecoderPlugin::frame_count()
- {
- if (!is_animated())
- return 1;
- return m_context->frame_count;
- }
- size_t WebPImageDecoderPlugin::first_animated_frame_index()
- {
- return 0;
- }
- ErrorOr<ImageFrameDescriptor> WebPImageDecoderPlugin::frame(size_t index, Optional<IntSize>)
- {
- if (index >= frame_count())
- return Error::from_string_literal("WebPImageDecoderPlugin: Invalid frame index");
- if (m_context->state == WebPLoadingContext::State::Error)
- return Error::from_string_literal("WebPImageDecoderPlugin: Decoding failed");
- if (m_context->state < WebPLoadingContext::State::BitmapDecoded) {
- TRY(decode_webp_image(*m_context));
- m_context->state = WebPLoadingContext::State::BitmapDecoded;
- }
- if (index >= m_context->frame_descriptors.size())
- return Error::from_string_literal("WebPImageDecoderPlugin: Invalid frame index");
- return m_context->frame_descriptors[index];
- }
- ErrorOr<Optional<ReadonlyBytes>> WebPImageDecoderPlugin::icc_data()
- {
- if (m_context->state < WebPLoadingContext::State::HeaderDecoded)
- (void)frame(0);
- if (!m_context->icc_data.is_empty())
- return m_context->icc_data;
- return OptionalNone {};
- }
- }
|