1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 04:17:35 +00:00

LibGfx/WebP: Some steps towards supporting animated webp files

No observable behavior change, but when building with WEBP_DEBUG 1,
this now prints frame data.
This commit is contained in:
Nico Weber 2023-05-05 14:51:53 -04:00 committed by Andreas Kling
parent 812763e5b9
commit 5c002c13c9

View file

@ -87,6 +87,28 @@ struct ANIMChunk {
u16 loop_count;
};
struct ANMFChunk {
u32 frame_x;
u32 frame_y;
u32 frame_width;
u32 frame_height;
u32 frame_duration_in_milliseconds;
enum class BlendingMethod {
UseAlphaBlending = 0,
DoNotBlend = 1,
};
BlendingMethod blending_method;
enum class DisposalMethod {
DoNotDispose = 0,
DisposeToBackgroundColor = 1,
};
DisposalMethod disposal_method;
ReadonlyBytes frame_data;
};
}
struct WebPLoadingContext {
@ -97,6 +119,7 @@ struct WebPLoadingContext {
FirstChunkRead,
FirstChunkDecoded,
ChunksDecoded,
AnimationFrameChunksDecoded,
BitmapDecoded,
};
State state { State::NotDecoded };
@ -127,6 +150,10 @@ struct WebPLoadingContext {
Optional<Chunk> animation_header_chunk; // 'ANIM'
Vector<Chunk> animation_frame_chunks; // 'ANMF'
// 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;
Optional<Chunk> iccp_chunk; // 'ICCP'
Optional<Chunk> exif_chunk; // 'EXIF'
Optional<Chunk> xmp_chunk; // 'XMP '
@ -1326,9 +1353,49 @@ static ErrorOr<ANIMChunk> decode_webp_chunk_ANIM(WebPLoadingContext& context, Ch
u32 background_color = (u32)data[0] | ((u32)data[1] << 8) | ((u32)data[2] << 16) | ((u32)data[3] << 24);
u16 loop_count = data[4] | (data[5] << 8);
dbgln_if(WEBP_DEBUG, "background_color {:x} loop_count {}", background_color, loop_count);
return ANIMChunk { background_color, loop_count };
}
// https://developers.google.com/speed/webp/docs/riff_container#animation
static ErrorOr<ANMFChunk> decode_webp_chunk_ANMF(WebPLoadingContext& context, Chunk const& anmf_chunk)
{
VERIFY(anmf_chunk.type == FourCC("ANMF"));
if (anmf_chunk.data.size() < 16)
return context.error("WebPImageDecoderPlugin: ANMF chunk too small");
u8 const* data = anmf_chunk.data.data();
// "The X coordinate of the upper left corner of the frame is Frame X * 2."
u32 frame_x = ((u32)data[0] | ((u32)data[1] << 8) | ((u32)data[2] << 16)) * 2;
// "The Y coordinate of the upper left corner of the frame is Frame Y * 2."
u32 frame_y = ((u32)data[3] | ((u32)data[4] << 8) | ((u32)data[5] << 16)) * 2;
// "The frame width is 1 + Frame Width Minus One."
u32 frame_width = ((u32)data[6] | ((u32)data[7] << 8) | ((u32)data[8] << 16)) + 1;
// "The frame height is 1 + Frame Height Minus One."
u32 frame_height = ((u32)data[9] | ((u32)data[10] << 8) | ((u32)data[11] << 16)) + 1;
// "The time to wait before displaying the next frame, in 1 millisecond units.
// Note the interpretation of frame duration of 0 (and often <= 10) is implementation defined.
// Many tools and browsers assign a minimum duration similar to GIF."
u32 frame_duration = (u32)data[12] | ((u32)data[13] << 8) | ((u32)data[14] << 16);
u8 flags = data[15];
auto blending_method = static_cast<ANMFChunk::BlendingMethod>((flags >> 1) & 1);
auto disposal_method = static_cast<ANMFChunk::DisposalMethod>(flags & 1);
ReadonlyBytes frame_data = anmf_chunk.data.slice(16);
dbgln_if(WEBP_DEBUG, "frame_x {} frame_y {} frame_width {} frame_height {} frame_duration {} blending_method {} disposal_method {}",
frame_x, frame_y, frame_width, frame_height, frame_duration, (int)blending_method, (int)disposal_method);
return ANMFChunk { frame_x, frame_y, frame_width, frame_height, frame_duration, blending_method, disposal_method, frame_data };
}
// https://developers.google.com/speed/webp/docs/riff_container#extended_file_format
static ErrorOr<void> decode_webp_extended(WebPLoadingContext& context, ReadonlyBytes chunks)
{
@ -1460,6 +1527,23 @@ static ErrorOr<void> decode_webp_chunks(WebPLoadingContext& context)
return {};
}
static ErrorOr<void> decode_webp_animation_frame_chunks(WebPLoadingContext& context)
{
if (context.state >= WebPLoadingContext::State::AnimationFrameChunksDecoded)
return {};
context.animation_header_chunk_data = TRY(decode_webp_chunk_ANIM(context, context.animation_header_chunk.value()));
Vector<ANMFChunk> decoded_chunks;
TRY(decoded_chunks.try_ensure_capacity(context.animation_frame_chunks.size()));
for (auto const& chunk : context.animation_frame_chunks)
TRY(decoded_chunks.try_append(TRY(decode_webp_chunk_ANMF(context, chunk))));
context.animation_frame_chunks_data = move(decoded_chunks);
context.state = WebPLoadingContext::State::AnimationFrameChunksDecoded;
return {};
}
WebPImageDecoderPlugin::WebPImageDecoderPlugin(ReadonlyBytes data, OwnPtr<WebPLoadingContext> context)
: m_context(move(context))
{
@ -1530,16 +1614,12 @@ size_t WebPImageDecoderPlugin::loop_count()
if (!is_animated())
return 0;
if (m_context->state < WebPLoadingContext::State::ChunksDecoded) {
if (decode_webp_chunks(*m_context).is_error())
if (m_context->state < WebPLoadingContext::State::AnimationFrameChunksDecoded) {
if (decode_webp_animation_frame_chunks(*m_context).is_error())
return 0;
}
auto anim_or_error = decode_webp_chunk_ANIM(*m_context, m_context->animation_header_chunk.value());
if (anim_or_error.is_error())
return 0;
return anim_or_error.value().loop_count;
return m_context->animation_header_chunk_data->loop_count;
}
size_t WebPImageDecoderPlugin::frame_count()
@ -1571,8 +1651,13 @@ ErrorOr<ImageFrameDescriptor> WebPImageDecoderPlugin::frame(size_t index)
if (m_context->state < WebPLoadingContext::State::ChunksDecoded)
TRY(decode_webp_chunks(*m_context));
if (is_animated())
if (is_animated()) {
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");
}
if (m_context->image_data_chunk.has_value() && m_context->image_data_chunk->type == FourCC("VP8L")) {
if (m_context->state < WebPLoadingContext::State::BitmapDecoded) {