mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 16:27:35 +00:00
LibGfx/WebP: Implement decoding of animation frame bitmaps
With this, lossless animated webp files work :^) (Missing: Loop count handling is not yet implemented, and alpha blending between frames isn't done in linear space.)
This commit is contained in:
parent
23ce1f641c
commit
ce45ad6112
1 changed files with 87 additions and 3 deletions
|
@ -12,6 +12,7 @@
|
|||
#include <AK/Vector.h>
|
||||
#include <LibCompress/Deflate.h>
|
||||
#include <LibGfx/ImageFormats/WebPLoader.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
|
||||
// Overview: https://developers.google.com/speed/webp/docs/compression
|
||||
// Container: https://developers.google.com/speed/webp/docs/riff_container
|
||||
|
@ -157,6 +158,7 @@ struct WebPLoadingContext {
|
|||
// These are set in state >= AnimationFrameChunksDecoded, if first_chunk.type == 'VP8X' && vp8x_header.has_animation.
|
||||
Optional<ANIMChunk> animation_header_chunk_data;
|
||||
Optional<Vector<ANMFChunk>> animation_frame_chunks_data;
|
||||
size_t current_frame { 0 };
|
||||
|
||||
Optional<Chunk> iccp_chunk; // 'ICCP'
|
||||
Optional<Chunk> exif_chunk; // 'EXIF'
|
||||
|
@ -1447,7 +1449,6 @@ static ErrorOr<void> decode_webp_extended(WebPLoadingContext& context, ReadonlyB
|
|||
|
||||
// https://developers.google.com/speed/webp/docs/riff_container#alpha
|
||||
// "A frame containing a 'VP8L' chunk SHOULD NOT contain this chunk."
|
||||
// FIXME: Also check in ANMF chunks.
|
||||
if (context.image_data.alpha_chunk.has_value() && context.image_data.image_data_chunk.has_value() && context.image_data.image_data_chunk->type == FourCC("VP8L")) {
|
||||
dbgln_if(WEBP_DEBUG, "WebPImageDecoderPlugin: VP8L frames should not have ALPH chunks. Ignoring ALPH chunk.");
|
||||
context.image_data.alpha_chunk.clear();
|
||||
|
@ -1548,6 +1549,90 @@ static ErrorOr<void> decode_webp_animation_frame_chunks(WebPLoadingContext& cont
|
|||
return {};
|
||||
}
|
||||
|
||||
static ErrorOr<ImageData> decode_webp_animation_frame_image_data(WebPLoadingContext& context, ANMFChunk const& frame)
|
||||
{
|
||||
ReadonlyBytes chunks = frame.frame_data;
|
||||
|
||||
ImageData image_data;
|
||||
|
||||
auto chunk = TRY(decode_webp_advance_chunk(context, chunks));
|
||||
if (chunk.type == FourCC("ALPH")) {
|
||||
image_data.alpha_chunk = chunk;
|
||||
chunk = TRY(decode_webp_advance_chunk(context, chunks));
|
||||
}
|
||||
|
||||
if (chunk.type != FourCC("VP8 ") && chunk.type != FourCC("VP8L"))
|
||||
return Error::from_string_literal("WebPImageDecoderPlugin: no image data found in animation frame");
|
||||
|
||||
image_data.image_data_chunk = chunk;
|
||||
|
||||
// https://developers.google.com/speed/webp/docs/riff_container#alpha
|
||||
// "A frame containing a 'VP8L' chunk SHOULD NOT contain this chunk."
|
||||
if (image_data.alpha_chunk.has_value() && image_data.image_data_chunk.has_value() && image_data.image_data_chunk->type == FourCC("VP8L")) {
|
||||
dbgln_if(WEBP_DEBUG, "WebPImageDecoderPlugin: VP8L frames should not have ALPH chunks. Ignoring ALPH chunk.");
|
||||
image_data.alpha_chunk.clear();
|
||||
}
|
||||
|
||||
return image_data;
|
||||
}
|
||||
|
||||
// https://developers.google.com/speed/webp/docs/riff_container#assembling_the_canvas_from_frames
|
||||
static ErrorOr<ImageFrameDescriptor> decode_webp_animation_frame(WebPLoadingContext& context, size_t frame_index)
|
||||
{
|
||||
if (frame_index >= context.animation_frame_chunks_data->size())
|
||||
return context.error("frame_index size too high");
|
||||
|
||||
VERIFY(context.first_chunk->type == FourCC("VP8X"));
|
||||
VERIFY(context.vp8x_header.has_animation);
|
||||
|
||||
Color clear_color = Color::from_argb(context.animation_header_chunk_data->background_color);
|
||||
|
||||
size_t start_frame = context.current_frame + 1;
|
||||
dbgln_if(WEBP_DEBUG, "start_frame {} context.current_frame {}", start_frame, context.current_frame);
|
||||
if (context.state < WebPLoadingContext::State::BitmapDecoded) {
|
||||
start_frame = 0;
|
||||
context.bitmap = TRY(Bitmap::create(BitmapFormat::BGRA8888, { context.vp8x_header.width, context.vp8x_header.height }));
|
||||
context.bitmap->fill(clear_color);
|
||||
} else if (frame_index < context.current_frame) {
|
||||
start_frame = 0;
|
||||
}
|
||||
|
||||
Painter painter(*context.bitmap);
|
||||
|
||||
// FIXME: Honor context.animation_header_chunk_data.loop_count.
|
||||
|
||||
for (size_t i = start_frame; i <= frame_index; ++i) {
|
||||
dbgln_if(WEBP_DEBUG, "drawing frame {} to produce frame {}", i, frame_index);
|
||||
|
||||
auto const& frame_description = context.animation_frame_chunks_data.value()[i];
|
||||
|
||||
if (i > 0) {
|
||||
auto const& previous_frame = context.animation_frame_chunks_data.value()[i - 1];
|
||||
if (previous_frame.disposal_method == ANMFChunk::DisposalMethod::DisposeToBackgroundColor)
|
||||
painter.clear_rect({ previous_frame.frame_x, previous_frame.frame_y, previous_frame.frame_width, previous_frame.frame_height }, clear_color);
|
||||
}
|
||||
|
||||
auto frame_image_data = TRY(decode_webp_animation_frame_image_data(context, frame_description));
|
||||
VERIFY(frame_image_data.image_data_chunk.has_value());
|
||||
|
||||
if (frame_image_data.image_data_chunk->type == FourCC("VP8 "))
|
||||
return context.error("WebPImageDecoderPlugin: decoding lossy webps not yet implemented");
|
||||
|
||||
auto frame_bitmap = TRY(decode_webp_chunk_VP8L(context, frame_image_data.image_data_chunk.value()));
|
||||
if (static_cast<u32>(frame_bitmap->width()) != frame_description.frame_width || static_cast<u32>(frame_bitmap->height()) != frame_description.frame_height)
|
||||
return context.error("WebPImageDecoderPlugin: decoded frame bitmap size doesn't match frame description size");
|
||||
|
||||
// FIXME: "Alpha-blending SHOULD be done in linear color space..."
|
||||
bool apply_alpha = frame_description.blending_method == ANMFChunk::BlendingMethod::UseAlphaBlending;
|
||||
painter.blit({ frame_description.frame_x, frame_description.frame_y }, *frame_bitmap, { {}, frame_bitmap->size() }, /*opacity=*/1.0, apply_alpha);
|
||||
|
||||
context.current_frame = i;
|
||||
context.state = WebPLoadingContext::State::BitmapDecoded;
|
||||
}
|
||||
|
||||
return ImageFrameDescriptor { context.bitmap, static_cast<int>(context.animation_frame_chunks_data.value()[frame_index].frame_duration_in_milliseconds) };
|
||||
}
|
||||
|
||||
WebPImageDecoderPlugin::WebPImageDecoderPlugin(ReadonlyBytes data, OwnPtr<WebPLoadingContext> context)
|
||||
: m_context(move(context))
|
||||
{
|
||||
|
@ -1659,8 +1744,7 @@ ErrorOr<ImageFrameDescriptor> WebPImageDecoderPlugin::frame(size_t index)
|
|||
if (m_context->state < WebPLoadingContext::State::AnimationFrameChunksDecoded) {
|
||||
TRY(decode_webp_animation_frame_chunks(*m_context));
|
||||
}
|
||||
// FIXME: Do something with the animation frames.
|
||||
return Error::from_string_literal("WebPImageDecoderPlugin: decoding of animated files not yet implemented");
|
||||
return decode_webp_animation_frame(*m_context, index);
|
||||
}
|
||||
|
||||
if (m_context->image_data.image_data_chunk.has_value() && m_context->image_data.image_data_chunk->type == FourCC("VP8L")) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue