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:
parent
812763e5b9
commit
5c002c13c9
1 changed files with 93 additions and 8 deletions
|
@ -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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue