瀏覽代碼

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.
Nico Weber 2 年之前
父節點
當前提交
5c002c13c9
共有 1 個文件被更改,包括 93 次插入8 次删除
  1. 93 8
      Userland/Libraries/LibGfx/ImageFormats/WebPLoader.cpp

+ 93 - 8
Userland/Libraries/LibGfx/ImageFormats/WebPLoader.cpp

@@ -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) {