std/vp8: decode to a placeholder gradient
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index d4bd937..fa98f34 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -14559,11 +14559,17 @@
 
 // ---------------- Status Codes
 
+extern const char wuffs_vp8__error__bad_header[];
+extern const char wuffs_vp8__error__truncated_input[];
+extern const char wuffs_vp8__error__unsupported_vp8_file[];
+
 // ---------------- Public Consts
 
+#define WUFFS_VP8__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 0u
+
 // ---------------- Struct Declarations
 
-typedef struct wuffs_vp8__placeholder__struct wuffs_vp8__placeholder;
+typedef struct wuffs_vp8__decoder__struct wuffs_vp8__decoder;
 
 #ifdef __cplusplus
 extern "C" {
@@ -14578,14 +14584,14 @@
 // Pass 0 (or some combination of WUFFS_INITIALIZE__XXX) for options.
 
 wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT
-wuffs_vp8__placeholder__initialize(
-    wuffs_vp8__placeholder* self,
+wuffs_vp8__decoder__initialize(
+    wuffs_vp8__decoder* self,
     size_t sizeof_star_self,
     uint64_t wuffs_version,
     uint32_t options);
 
 size_t
-sizeof__wuffs_vp8__placeholder(void);
+sizeof__wuffs_vp8__decoder(void);
 
 // ---------------- Allocs
 
@@ -14595,13 +14601,108 @@
 // calling free on the returned pointer. That pointer is effectively a C++
 // std::unique_ptr<T, wuffs_unique_ptr_deleter>.
 
-wuffs_vp8__placeholder*
-wuffs_vp8__placeholder__alloc(void);
+wuffs_vp8__decoder*
+wuffs_vp8__decoder__alloc(void);
+
+static inline wuffs_base__image_decoder*
+wuffs_vp8__decoder__alloc_as__wuffs_base__image_decoder(void) {
+  return (wuffs_base__image_decoder*)(wuffs_vp8__decoder__alloc());
+}
 
 // ---------------- Upcasts
 
+static inline wuffs_base__image_decoder*
+wuffs_vp8__decoder__upcast_as__wuffs_base__image_decoder(
+    wuffs_vp8__decoder* p) {
+  return (wuffs_base__image_decoder*)p;
+}
+
 // ---------------- Public Function Prototypes
 
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC uint64_t
+wuffs_vp8__decoder__get_quirk(
+    const wuffs_vp8__decoder* self,
+    uint32_t a_key);
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_vp8__decoder__set_quirk(
+    wuffs_vp8__decoder* self,
+    uint32_t a_key,
+    uint64_t a_value);
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_vp8__decoder__decode_image_config(
+    wuffs_vp8__decoder* self,
+    wuffs_base__image_config* a_dst,
+    wuffs_base__io_buffer* a_src);
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_vp8__decoder__decode_frame_config(
+    wuffs_vp8__decoder* self,
+    wuffs_base__frame_config* a_dst,
+    wuffs_base__io_buffer* a_src);
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_vp8__decoder__decode_frame(
+    wuffs_vp8__decoder* self,
+    wuffs_base__pixel_buffer* a_dst,
+    wuffs_base__io_buffer* a_src,
+    wuffs_base__pixel_blend a_blend,
+    wuffs_base__slice_u8 a_workbuf,
+    wuffs_base__decode_frame_options* a_opts);
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__rect_ie_u32
+wuffs_vp8__decoder__frame_dirty_rect(
+    const wuffs_vp8__decoder* self);
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC uint32_t
+wuffs_vp8__decoder__num_animation_loops(
+    const wuffs_vp8__decoder* self);
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC uint64_t
+wuffs_vp8__decoder__num_decoded_frame_configs(
+    const wuffs_vp8__decoder* self);
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC uint64_t
+wuffs_vp8__decoder__num_decoded_frames(
+    const wuffs_vp8__decoder* self);
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_vp8__decoder__restart_frame(
+    wuffs_vp8__decoder* self,
+    uint64_t a_index,
+    uint64_t a_io_position);
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct
+wuffs_vp8__decoder__set_report_metadata(
+    wuffs_vp8__decoder* self,
+    uint32_t a_fourcc,
+    bool a_report);
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_vp8__decoder__tell_me_more(
+    wuffs_vp8__decoder* self,
+    wuffs_base__io_buffer* a_dst,
+    wuffs_base__more_information* a_minfo,
+    wuffs_base__io_buffer* a_src);
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64
+wuffs_vp8__decoder__workbuf_len(
+    const wuffs_vp8__decoder* self);
+
 #ifdef __cplusplus
 }  // extern "C"
 #endif
@@ -14615,7 +14716,7 @@
 
 #if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION)
 
-struct wuffs_vp8__placeholder__struct {
+struct wuffs_vp8__decoder__struct {
   // Do not access the private_impl's or private_data's fields directly. There
   // is no API/ABI compatibility or safety guarantee if you do so. Instead, use
   // the wuffs_foo__bar__baz functions.
@@ -14626,20 +14727,46 @@
   struct {
     uint32_t magic;
     uint32_t active_coroutine;
+    wuffs_base__vtable vtable_for__wuffs_base__image_decoder;
     wuffs_base__vtable null_vtable;
 
-    uint32_t f_placeholder;
+    uint32_t f_width;
+    uint32_t f_height;
+    uint8_t f_call_sequence;
+    uint64_t f_frame_config_io_position;
+    uint32_t f_dst_x;
+    uint32_t f_dst_y;
+    wuffs_base__pixel_swizzler f_swizzler;
+
+    uint32_t p_decode_image_config;
+    uint32_t p_do_decode_image_config;
+    uint32_t p_decode_frame_config;
+    uint32_t p_do_decode_frame_config;
+    uint32_t p_decode_frame;
+    uint32_t p_do_decode_frame;
   } private_impl;
 
+  struct {
+    struct {
+      uint64_t scratch;
+    } s_do_decode_image_config;
+  } private_data;
+
 #ifdef __cplusplus
 #if defined(WUFFS_BASE__HAVE_UNIQUE_PTR)
-  using unique_ptr = std::unique_ptr<wuffs_vp8__placeholder, wuffs_unique_ptr_deleter>;
+  using unique_ptr = std::unique_ptr<wuffs_vp8__decoder, wuffs_unique_ptr_deleter>;
 
   // On failure, the alloc_etc functions return nullptr. They don't throw.
 
   static inline unique_ptr
   alloc() {
-    return unique_ptr(wuffs_vp8__placeholder__alloc());
+    return unique_ptr(wuffs_vp8__decoder__alloc());
+  }
+
+  static inline wuffs_base__image_decoder::unique_ptr
+  alloc_as__wuffs_base__image_decoder() {
+    return wuffs_base__image_decoder::unique_ptr(
+        wuffs_vp8__decoder__alloc_as__wuffs_base__image_decoder());
   }
 #endif  // defined(WUFFS_BASE__HAVE_UNIQUE_PTR)
 
@@ -14655,10 +14782,10 @@
   // WUFFS_IMPLEMENTATION is #define'd). In C++, we define a complete type in
   // order to provide convenience methods. These forward on "this", so that you
   // can write "bar->baz(etc)" instead of "wuffs_foo__bar__baz(bar, etc)".
-  wuffs_vp8__placeholder__struct() = delete;
-  wuffs_vp8__placeholder__struct(const wuffs_vp8__placeholder__struct&) = delete;
-  wuffs_vp8__placeholder__struct& operator=(
-      const wuffs_vp8__placeholder__struct&) = delete;
+  wuffs_vp8__decoder__struct() = delete;
+  wuffs_vp8__decoder__struct(const wuffs_vp8__decoder__struct&) = delete;
+  wuffs_vp8__decoder__struct& operator=(
+      const wuffs_vp8__decoder__struct&) = delete;
 #endif  // defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION)
 
 #if !defined(WUFFS_IMPLEMENTATION)
@@ -14678,12 +14805,101 @@
       size_t sizeof_star_self,
       uint64_t wuffs_version,
       uint32_t options) {
-    return wuffs_vp8__placeholder__initialize(
+    return wuffs_vp8__decoder__initialize(
         this, sizeof_star_self, wuffs_version, options);
   }
 
+  inline wuffs_base__image_decoder*
+  upcast_as__wuffs_base__image_decoder() {
+    return (wuffs_base__image_decoder*)this;
+  }
+
+  inline uint64_t
+  get_quirk(
+      uint32_t a_key) const {
+    return wuffs_vp8__decoder__get_quirk(this, a_key);
+  }
+
+  inline wuffs_base__status
+  set_quirk(
+      uint32_t a_key,
+      uint64_t a_value) {
+    return wuffs_vp8__decoder__set_quirk(this, a_key, a_value);
+  }
+
+  inline wuffs_base__status
+  decode_image_config(
+      wuffs_base__image_config* a_dst,
+      wuffs_base__io_buffer* a_src) {
+    return wuffs_vp8__decoder__decode_image_config(this, a_dst, a_src);
+  }
+
+  inline wuffs_base__status
+  decode_frame_config(
+      wuffs_base__frame_config* a_dst,
+      wuffs_base__io_buffer* a_src) {
+    return wuffs_vp8__decoder__decode_frame_config(this, a_dst, a_src);
+  }
+
+  inline wuffs_base__status
+  decode_frame(
+      wuffs_base__pixel_buffer* a_dst,
+      wuffs_base__io_buffer* a_src,
+      wuffs_base__pixel_blend a_blend,
+      wuffs_base__slice_u8 a_workbuf,
+      wuffs_base__decode_frame_options* a_opts) {
+    return wuffs_vp8__decoder__decode_frame(this, a_dst, a_src, a_blend, a_workbuf, a_opts);
+  }
+
+  inline wuffs_base__rect_ie_u32
+  frame_dirty_rect() const {
+    return wuffs_vp8__decoder__frame_dirty_rect(this);
+  }
+
+  inline uint32_t
+  num_animation_loops() const {
+    return wuffs_vp8__decoder__num_animation_loops(this);
+  }
+
+  inline uint64_t
+  num_decoded_frame_configs() const {
+    return wuffs_vp8__decoder__num_decoded_frame_configs(this);
+  }
+
+  inline uint64_t
+  num_decoded_frames() const {
+    return wuffs_vp8__decoder__num_decoded_frames(this);
+  }
+
+  inline wuffs_base__status
+  restart_frame(
+      uint64_t a_index,
+      uint64_t a_io_position) {
+    return wuffs_vp8__decoder__restart_frame(this, a_index, a_io_position);
+  }
+
+  inline wuffs_base__empty_struct
+  set_report_metadata(
+      uint32_t a_fourcc,
+      bool a_report) {
+    return wuffs_vp8__decoder__set_report_metadata(this, a_fourcc, a_report);
+  }
+
+  inline wuffs_base__status
+  tell_me_more(
+      wuffs_base__io_buffer* a_dst,
+      wuffs_base__more_information* a_minfo,
+      wuffs_base__io_buffer* a_src) {
+    return wuffs_vp8__decoder__tell_me_more(this, a_dst, a_minfo, a_src);
+  }
+
+  inline wuffs_base__range_ii_u64
+  workbuf_len() const {
+    return wuffs_vp8__decoder__workbuf_len(this);
+  }
+
 #endif  // __cplusplus
-};  // struct wuffs_vp8__placeholder__struct
+};  // struct wuffs_vp8__decoder__struct
 
 #endif  // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION)
 
@@ -15234,6 +15450,7 @@
     uint8_t f_call_sequence;
     uint8_t f_code_length_code_lengths[19];
     bool f_sub_chunk_has_padding;
+    bool f_is_vp8_lossy;
     uint64_t f_frame_config_io_position;
     uint32_t f_riff_chunk_length;
     uint32_t f_sub_chunk_length;
@@ -15276,6 +15493,7 @@
   } private_impl;
 
   struct {
+    wuffs_vp8__decoder f_vp8;
     uint8_t f_palette[1024];
     uint32_t f_color_cache[2048];
     uint16_t f_codes[2328];
@@ -75876,19 +76094,89 @@
 
 // ---------------- Status Codes Implementations
 
+const char wuffs_vp8__error__bad_header[] = "#vp8: bad header";
+const char wuffs_vp8__error__truncated_input[] = "#vp8: truncated input";
+const char wuffs_vp8__error__unsupported_vp8_file[] = "#vp8: unsupported VP8 file";
+
 // ---------------- Private Consts
 
 // ---------------- Private Initializer Prototypes
 
 // ---------------- Private Function Prototypes
 
+WUFFS_BASE__GENERATED_C_CODE
+static wuffs_base__status
+wuffs_vp8__decoder__do_decode_image_config(
+    wuffs_vp8__decoder* self,
+    wuffs_base__image_config* a_dst,
+    wuffs_base__io_buffer* a_src);
+
+WUFFS_BASE__GENERATED_C_CODE
+static wuffs_base__status
+wuffs_vp8__decoder__do_decode_frame_config(
+    wuffs_vp8__decoder* self,
+    wuffs_base__frame_config* a_dst,
+    wuffs_base__io_buffer* a_src);
+
+WUFFS_BASE__GENERATED_C_CODE
+static wuffs_base__status
+wuffs_vp8__decoder__do_decode_frame(
+    wuffs_vp8__decoder* self,
+    wuffs_base__pixel_buffer* a_dst,
+    wuffs_base__io_buffer* a_src,
+    wuffs_base__pixel_blend a_blend,
+    wuffs_base__slice_u8 a_workbuf,
+    wuffs_base__decode_frame_options* a_opts);
+
+WUFFS_BASE__GENERATED_C_CODE
+static wuffs_base__status
+wuffs_vp8__decoder__make_a_placeholder_gradient(
+    wuffs_vp8__decoder* self,
+    wuffs_base__pixel_buffer* a_dst);
+
 // ---------------- VTables
 
+const wuffs_base__image_decoder__func_ptrs
+wuffs_vp8__decoder__func_ptrs_for__wuffs_base__image_decoder = {
+  (wuffs_base__status(*)(void*,
+      wuffs_base__pixel_buffer*,
+      wuffs_base__io_buffer*,
+      wuffs_base__pixel_blend,
+      wuffs_base__slice_u8,
+      wuffs_base__decode_frame_options*))(&wuffs_vp8__decoder__decode_frame),
+  (wuffs_base__status(*)(void*,
+      wuffs_base__frame_config*,
+      wuffs_base__io_buffer*))(&wuffs_vp8__decoder__decode_frame_config),
+  (wuffs_base__status(*)(void*,
+      wuffs_base__image_config*,
+      wuffs_base__io_buffer*))(&wuffs_vp8__decoder__decode_image_config),
+  (wuffs_base__rect_ie_u32(*)(const void*))(&wuffs_vp8__decoder__frame_dirty_rect),
+  (uint64_t(*)(const void*,
+      uint32_t))(&wuffs_vp8__decoder__get_quirk),
+  (uint32_t(*)(const void*))(&wuffs_vp8__decoder__num_animation_loops),
+  (uint64_t(*)(const void*))(&wuffs_vp8__decoder__num_decoded_frame_configs),
+  (uint64_t(*)(const void*))(&wuffs_vp8__decoder__num_decoded_frames),
+  (wuffs_base__status(*)(void*,
+      uint64_t,
+      uint64_t))(&wuffs_vp8__decoder__restart_frame),
+  (wuffs_base__status(*)(void*,
+      uint32_t,
+      uint64_t))(&wuffs_vp8__decoder__set_quirk),
+  (wuffs_base__empty_struct(*)(void*,
+      uint32_t,
+      bool))(&wuffs_vp8__decoder__set_report_metadata),
+  (wuffs_base__status(*)(void*,
+      wuffs_base__io_buffer*,
+      wuffs_base__more_information*,
+      wuffs_base__io_buffer*))(&wuffs_vp8__decoder__tell_me_more),
+  (wuffs_base__range_ii_u64(*)(const void*))(&wuffs_vp8__decoder__workbuf_len),
+};
+
 // ---------------- Initializer Implementations
 
 wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT
-wuffs_vp8__placeholder__initialize(
-    wuffs_vp8__placeholder* self,
+wuffs_vp8__decoder__initialize(
+    wuffs_vp8__decoder* self,
     size_t sizeof_star_self,
     uint64_t wuffs_version,
     uint32_t options){
@@ -75926,18 +76214,22 @@
   }
 
   self->private_impl.magic = WUFFS_BASE__MAGIC;
+  self->private_impl.vtable_for__wuffs_base__image_decoder.vtable_name =
+      wuffs_base__image_decoder__vtable_name;
+  self->private_impl.vtable_for__wuffs_base__image_decoder.function_pointers =
+      (const void*)(&wuffs_vp8__decoder__func_ptrs_for__wuffs_base__image_decoder);
   return wuffs_base__make_status(NULL);
 }
 
-wuffs_vp8__placeholder*
-wuffs_vp8__placeholder__alloc(void) {
-  wuffs_vp8__placeholder* x =
-      (wuffs_vp8__placeholder*)(calloc(1, sizeof(wuffs_vp8__placeholder)));
+wuffs_vp8__decoder*
+wuffs_vp8__decoder__alloc(void) {
+  wuffs_vp8__decoder* x =
+      (wuffs_vp8__decoder*)(calloc(1, sizeof(wuffs_vp8__decoder)));
   if (!x) {
     return NULL;
   }
-  if (wuffs_vp8__placeholder__initialize(
-      x, sizeof(wuffs_vp8__placeholder), WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED).repr) {
+  if (wuffs_vp8__decoder__initialize(
+      x, sizeof(wuffs_vp8__decoder), WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED).repr) {
     free(x);
     return NULL;
   }
@@ -75945,12 +76237,801 @@
 }
 
 size_t
-sizeof__wuffs_vp8__placeholder(void) {
-  return sizeof(wuffs_vp8__placeholder);
+sizeof__wuffs_vp8__decoder(void) {
+  return sizeof(wuffs_vp8__decoder);
 }
 
 // ---------------- Function Implementations
 
+// -------- func vp8.decoder.get_quirk
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC uint64_t
+wuffs_vp8__decoder__get_quirk(
+    const wuffs_vp8__decoder* self,
+    uint32_t a_key) {
+  if (!self) {
+    return 0;
+  }
+  if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&
+      (self->private_impl.magic != WUFFS_BASE__DISABLED)) {
+    return 0;
+  }
+
+  return 0u;
+}
+
+// -------- func vp8.decoder.set_quirk
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_vp8__decoder__set_quirk(
+    wuffs_vp8__decoder* self,
+    uint32_t a_key,
+    uint64_t a_value) {
+  if (!self) {
+    return wuffs_base__make_status(wuffs_base__error__bad_receiver);
+  }
+  if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+    return wuffs_base__make_status(
+        (self->private_impl.magic == WUFFS_BASE__DISABLED)
+        ? wuffs_base__error__disabled_by_previous_error
+        : wuffs_base__error__initialize_not_called);
+  }
+
+  return wuffs_base__make_status(wuffs_base__error__unsupported_option);
+}
+
+// -------- func vp8.decoder.decode_image_config
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_vp8__decoder__decode_image_config(
+    wuffs_vp8__decoder* self,
+    wuffs_base__image_config* a_dst,
+    wuffs_base__io_buffer* a_src) {
+  if (!self) {
+    return wuffs_base__make_status(wuffs_base__error__bad_receiver);
+  }
+  if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+    return wuffs_base__make_status(
+        (self->private_impl.magic == WUFFS_BASE__DISABLED)
+        ? wuffs_base__error__disabled_by_previous_error
+        : wuffs_base__error__initialize_not_called);
+  }
+  if (!a_src) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+    return wuffs_base__make_status(wuffs_base__error__bad_argument);
+  }
+  if ((self->private_impl.active_coroutine != 0) &&
+      (self->private_impl.active_coroutine != 1)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+    return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls);
+  }
+  self->private_impl.active_coroutine = 0;
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
+  wuffs_base__status v_status = wuffs_base__make_status(NULL);
+
+  uint32_t coro_susp_point = self->private_impl.p_decode_image_config;
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    while (true) {
+      {
+        wuffs_base__status t_0 = wuffs_vp8__decoder__do_decode_image_config(self, a_dst, a_src);
+        v_status = t_0;
+      }
+      if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) {
+        status = wuffs_base__make_status(wuffs_vp8__error__truncated_input);
+        goto exit;
+      }
+      status = v_status;
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1);
+    }
+
+    ok:
+    self->private_impl.p_decode_image_config = 0;
+    goto exit;
+  }
+
+  goto suspend;
+  suspend:
+  self->private_impl.p_decode_image_config = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+  self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0;
+
+  goto exit;
+  exit:
+  if (wuffs_base__status__is_error(&status)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+  }
+  return status;
+}
+
+// -------- func vp8.decoder.do_decode_image_config
+
+WUFFS_BASE__GENERATED_C_CODE
+static wuffs_base__status
+wuffs_vp8__decoder__do_decode_image_config(
+    wuffs_vp8__decoder* self,
+    wuffs_base__image_config* a_dst,
+    wuffs_base__io_buffer* a_src) {
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
+  uint32_t v_c32 = 0;
+
+  const uint8_t* iop_a_src = NULL;
+  const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_src && a_src->data.ptr) {
+    io0_a_src = a_src->data.ptr;
+    io1_a_src = io0_a_src + a_src->meta.ri;
+    iop_a_src = io1_a_src;
+    io2_a_src = io0_a_src + a_src->meta.wi;
+  }
+
+  uint32_t coro_susp_point = self->private_impl.p_do_decode_image_config;
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    if (self->private_impl.f_call_sequence != 0u) {
+      status = wuffs_base__make_status(wuffs_base__error__bad_call_sequence);
+      goto exit;
+    }
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
+      uint32_t t_0;
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 3)) {
+        t_0 = ((uint32_t)(wuffs_base__peek_u24le__no_bounds_check(iop_a_src)));
+        iop_a_src += 3;
+      } else {
+        self->private_data.s_do_decode_image_config.scratch = 0;
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
+        while (true) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+            status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+            goto suspend;
+          }
+          uint64_t* scratch = &self->private_data.s_do_decode_image_config.scratch;
+          uint32_t num_bits_0 = ((uint32_t)(*scratch >> 56));
+          *scratch <<= 8;
+          *scratch >>= 8;
+          *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_0;
+          if (num_bits_0 == 16) {
+            t_0 = ((uint32_t)(*scratch));
+            break;
+          }
+          num_bits_0 += 8u;
+          *scratch |= ((uint64_t)(num_bits_0)) << 56;
+        }
+      }
+      v_c32 = t_0;
+    }
+    if ((v_c32 & 1u) != 0u) {
+      status = wuffs_base__make_status(wuffs_vp8__error__unsupported_vp8_file);
+      goto exit;
+    }
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
+      uint32_t t_1;
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 3)) {
+        t_1 = ((uint32_t)(wuffs_base__peek_u24le__no_bounds_check(iop_a_src)));
+        iop_a_src += 3;
+      } else {
+        self->private_data.s_do_decode_image_config.scratch = 0;
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
+        while (true) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+            status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+            goto suspend;
+          }
+          uint64_t* scratch = &self->private_data.s_do_decode_image_config.scratch;
+          uint32_t num_bits_1 = ((uint32_t)(*scratch >> 56));
+          *scratch <<= 8;
+          *scratch >>= 8;
+          *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_1;
+          if (num_bits_1 == 16) {
+            t_1 = ((uint32_t)(*scratch));
+            break;
+          }
+          num_bits_1 += 8u;
+          *scratch |= ((uint64_t)(num_bits_1)) << 56;
+        }
+      }
+      v_c32 = t_1;
+    }
+    if (v_c32 != 2752925u) {
+      status = wuffs_base__make_status(wuffs_vp8__error__bad_header);
+      goto exit;
+    }
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
+      uint32_t t_2;
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) {
+        t_2 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src);
+        iop_a_src += 4;
+      } else {
+        self->private_data.s_do_decode_image_config.scratch = 0;
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
+        while (true) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+            status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+            goto suspend;
+          }
+          uint64_t* scratch = &self->private_data.s_do_decode_image_config.scratch;
+          uint32_t num_bits_2 = ((uint32_t)(*scratch >> 56));
+          *scratch <<= 8;
+          *scratch >>= 8;
+          *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_2;
+          if (num_bits_2 == 24) {
+            t_2 = ((uint32_t)(*scratch));
+            break;
+          }
+          num_bits_2 += 8u;
+          *scratch |= ((uint64_t)(num_bits_2)) << 56;
+        }
+      }
+      v_c32 = t_2;
+    }
+    self->private_impl.f_width = (16383u & (v_c32 >> 0u));
+    self->private_impl.f_height = (16383u & (v_c32 >> 16u));
+    self->private_impl.f_frame_config_io_position = wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src)));
+    if (a_dst != NULL) {
+      wuffs_base__image_config__set(
+          a_dst,
+          2415954056u,
+          0u,
+          self->private_impl.f_width,
+          self->private_impl.f_height,
+          self->private_impl.f_frame_config_io_position,
+          false);
+    }
+    self->private_impl.f_call_sequence = 32u;
+
+    goto ok;
+    ok:
+    self->private_impl.p_do_decode_image_config = 0;
+    goto exit;
+  }
+
+  goto suspend;
+  suspend:
+  self->private_impl.p_do_decode_image_config = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+
+  goto exit;
+  exit:
+  if (a_src && a_src->data.ptr) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+  }
+
+  return status;
+}
+
+// -------- func vp8.decoder.decode_frame_config
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_vp8__decoder__decode_frame_config(
+    wuffs_vp8__decoder* self,
+    wuffs_base__frame_config* a_dst,
+    wuffs_base__io_buffer* a_src) {
+  if (!self) {
+    return wuffs_base__make_status(wuffs_base__error__bad_receiver);
+  }
+  if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+    return wuffs_base__make_status(
+        (self->private_impl.magic == WUFFS_BASE__DISABLED)
+        ? wuffs_base__error__disabled_by_previous_error
+        : wuffs_base__error__initialize_not_called);
+  }
+  if (!a_src) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+    return wuffs_base__make_status(wuffs_base__error__bad_argument);
+  }
+  if ((self->private_impl.active_coroutine != 0) &&
+      (self->private_impl.active_coroutine != 2)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+    return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls);
+  }
+  self->private_impl.active_coroutine = 0;
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
+  wuffs_base__status v_status = wuffs_base__make_status(NULL);
+
+  uint32_t coro_susp_point = self->private_impl.p_decode_frame_config;
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    while (true) {
+      {
+        wuffs_base__status t_0 = wuffs_vp8__decoder__do_decode_frame_config(self, a_dst, a_src);
+        v_status = t_0;
+      }
+      if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) {
+        status = wuffs_base__make_status(wuffs_vp8__error__truncated_input);
+        goto exit;
+      }
+      status = v_status;
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1);
+    }
+
+    ok:
+    self->private_impl.p_decode_frame_config = 0;
+    goto exit;
+  }
+
+  goto suspend;
+  suspend:
+  self->private_impl.p_decode_frame_config = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+  self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 2 : 0;
+
+  goto exit;
+  exit:
+  if (wuffs_base__status__is_error(&status)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+  }
+  return status;
+}
+
+// -------- func vp8.decoder.do_decode_frame_config
+
+WUFFS_BASE__GENERATED_C_CODE
+static wuffs_base__status
+wuffs_vp8__decoder__do_decode_frame_config(
+    wuffs_vp8__decoder* self,
+    wuffs_base__frame_config* a_dst,
+    wuffs_base__io_buffer* a_src) {
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
+  const uint8_t* iop_a_src = NULL;
+  const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_src && a_src->data.ptr) {
+    io0_a_src = a_src->data.ptr;
+    io1_a_src = io0_a_src + a_src->meta.ri;
+    iop_a_src = io1_a_src;
+    io2_a_src = io0_a_src + a_src->meta.wi;
+  }
+
+  uint32_t coro_susp_point = self->private_impl.p_do_decode_frame_config;
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    if (self->private_impl.f_call_sequence == 32u) {
+    } else if (self->private_impl.f_call_sequence < 32u) {
+      if (a_src) {
+        a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+      }
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
+      status = wuffs_vp8__decoder__do_decode_image_config(self, NULL, a_src);
+      if (a_src) {
+        iop_a_src = a_src->data.ptr + a_src->meta.ri;
+      }
+      if (status.repr) {
+        goto suspend;
+      }
+    } else if (self->private_impl.f_call_sequence == 40u) {
+      if (self->private_impl.f_frame_config_io_position != wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src)))) {
+        status = wuffs_base__make_status(wuffs_base__error__bad_restart);
+        goto exit;
+      }
+    } else if (self->private_impl.f_call_sequence == 64u) {
+      self->private_impl.f_call_sequence = 96u;
+      status = wuffs_base__make_status(wuffs_base__note__end_of_data);
+      goto ok;
+    } else {
+      status = wuffs_base__make_status(wuffs_base__note__end_of_data);
+      goto ok;
+    }
+    if (a_dst != NULL) {
+      wuffs_base__frame_config__set(
+          a_dst,
+          wuffs_base__utility__make_rect_ie_u32(
+          0u,
+          0u,
+          self->private_impl.f_width,
+          self->private_impl.f_height),
+          ((wuffs_base__flicks)(0u)),
+          0u,
+          self->private_impl.f_frame_config_io_position,
+          0u,
+          false,
+          false,
+          0u);
+    }
+    self->private_impl.f_call_sequence = 64u;
+
+    ok:
+    self->private_impl.p_do_decode_frame_config = 0;
+    goto exit;
+  }
+
+  goto suspend;
+  suspend:
+  self->private_impl.p_do_decode_frame_config = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+
+  goto exit;
+  exit:
+  if (a_src && a_src->data.ptr) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+  }
+
+  return status;
+}
+
+// -------- func vp8.decoder.decode_frame
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_vp8__decoder__decode_frame(
+    wuffs_vp8__decoder* self,
+    wuffs_base__pixel_buffer* a_dst,
+    wuffs_base__io_buffer* a_src,
+    wuffs_base__pixel_blend a_blend,
+    wuffs_base__slice_u8 a_workbuf,
+    wuffs_base__decode_frame_options* a_opts) {
+  if (!self) {
+    return wuffs_base__make_status(wuffs_base__error__bad_receiver);
+  }
+  if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+    return wuffs_base__make_status(
+        (self->private_impl.magic == WUFFS_BASE__DISABLED)
+        ? wuffs_base__error__disabled_by_previous_error
+        : wuffs_base__error__initialize_not_called);
+  }
+  if (!a_dst || !a_src) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+    return wuffs_base__make_status(wuffs_base__error__bad_argument);
+  }
+  if ((self->private_impl.active_coroutine != 0) &&
+      (self->private_impl.active_coroutine != 3)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+    return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls);
+  }
+  self->private_impl.active_coroutine = 0;
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
+  wuffs_base__status v_status = wuffs_base__make_status(NULL);
+
+  uint32_t coro_susp_point = self->private_impl.p_decode_frame;
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    while (true) {
+      {
+        wuffs_base__status t_0 = wuffs_vp8__decoder__do_decode_frame(self,
+            a_dst,
+            a_src,
+            a_blend,
+            a_workbuf,
+            a_opts);
+        v_status = t_0;
+      }
+      if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) {
+        status = wuffs_base__make_status(wuffs_vp8__error__truncated_input);
+        goto exit;
+      }
+      status = v_status;
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1);
+    }
+
+    ok:
+    self->private_impl.p_decode_frame = 0;
+    goto exit;
+  }
+
+  goto suspend;
+  suspend:
+  self->private_impl.p_decode_frame = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+  self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 3 : 0;
+
+  goto exit;
+  exit:
+  if (wuffs_base__status__is_error(&status)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+  }
+  return status;
+}
+
+// -------- func vp8.decoder.do_decode_frame
+
+WUFFS_BASE__GENERATED_C_CODE
+static wuffs_base__status
+wuffs_vp8__decoder__do_decode_frame(
+    wuffs_vp8__decoder* self,
+    wuffs_base__pixel_buffer* a_dst,
+    wuffs_base__io_buffer* a_src,
+    wuffs_base__pixel_blend a_blend,
+    wuffs_base__slice_u8 a_workbuf,
+    wuffs_base__decode_frame_options* a_opts) {
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
+  wuffs_base__status v_status = wuffs_base__make_status(NULL);
+
+  uint32_t coro_susp_point = self->private_impl.p_do_decode_frame;
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    if (self->private_impl.f_call_sequence == 64u) {
+    } else if (self->private_impl.f_call_sequence < 64u) {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
+      status = wuffs_vp8__decoder__do_decode_frame_config(self, NULL, a_src);
+      if (status.repr) {
+        goto suspend;
+      }
+    } else {
+      status = wuffs_base__make_status(wuffs_base__note__end_of_data);
+      goto ok;
+    }
+    self->private_impl.f_dst_x = 0u;
+    self->private_impl.f_dst_y = 0u;
+    v_status = wuffs_base__pixel_swizzler__prepare(&self->private_impl.f_swizzler,
+        wuffs_base__pixel_buffer__pixel_format(a_dst),
+        wuffs_base__pixel_buffer__palette(a_dst),
+        wuffs_base__utility__make_pixel_format(2415954056u),
+        wuffs_base__utility__empty_slice_u8(),
+        a_blend);
+    if ( ! wuffs_base__status__is_ok(&v_status)) {
+      status = v_status;
+      if (wuffs_base__status__is_error(&status)) {
+        goto exit;
+      } else if (wuffs_base__status__is_suspension(&status)) {
+        status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension);
+        goto exit;
+      }
+      goto ok;
+    }
+    v_status = wuffs_vp8__decoder__make_a_placeholder_gradient(self, a_dst);
+    if ( ! wuffs_base__status__is_ok(&v_status)) {
+      status = v_status;
+      if (wuffs_base__status__is_error(&status)) {
+        goto exit;
+      } else if (wuffs_base__status__is_suspension(&status)) {
+        status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension);
+        goto exit;
+      }
+      goto ok;
+    }
+    self->private_impl.f_call_sequence = 96u;
+
+    ok:
+    self->private_impl.p_do_decode_frame = 0;
+    goto exit;
+  }
+
+  goto suspend;
+  suspend:
+  self->private_impl.p_do_decode_frame = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+
+  goto exit;
+  exit:
+  return status;
+}
+
+// -------- func vp8.decoder.make_a_placeholder_gradient
+
+WUFFS_BASE__GENERATED_C_CODE
+static wuffs_base__status
+wuffs_vp8__decoder__make_a_placeholder_gradient(
+    wuffs_vp8__decoder* self,
+    wuffs_base__pixel_buffer* a_dst) {
+  wuffs_base__pixel_format v_dst_pixfmt = {0};
+  uint32_t v_dst_bits_per_pixel = 0;
+  uint32_t v_dst_bytes_per_pixel = 0;
+  uint64_t v_dst_bytes_per_row = 0;
+  wuffs_base__table_u8 v_tab = {0};
+  wuffs_base__slice_u8 v_dst = {0};
+  uint64_t v_i = 0;
+  uint8_t v_bgrx[4] = {0};
+
+  v_dst_pixfmt = wuffs_base__pixel_buffer__pixel_format(a_dst);
+  v_dst_bits_per_pixel = wuffs_base__pixel_format__bits_per_pixel(&v_dst_pixfmt);
+  if ((v_dst_bits_per_pixel & 7u) != 0u) {
+    return wuffs_base__make_status(wuffs_base__error__unsupported_option);
+  }
+  v_dst_bytes_per_pixel = (v_dst_bits_per_pixel / 8u);
+  v_dst_bytes_per_row = ((uint64_t)((self->private_impl.f_width * v_dst_bytes_per_pixel)));
+  v_tab = wuffs_base__pixel_buffer__plane(a_dst, 0u);
+  v_bgrx[0u] = 128u;
+  while (self->private_impl.f_dst_y < self->private_impl.f_height) {
+    v_bgrx[1u] = ((uint8_t)(self->private_impl.f_dst_y));
+    self->private_impl.f_dst_x = 0u;
+    while (self->private_impl.f_dst_x < self->private_impl.f_width) {
+      v_bgrx[2u] = ((uint8_t)(self->private_impl.f_dst_x));
+      v_dst = wuffs_private_impl__table_u8__row_u32(v_tab, self->private_impl.f_dst_y);
+      if (v_dst_bytes_per_row < ((uint64_t)(v_dst.len))) {
+        v_dst = wuffs_base__slice_u8__subslice_j(v_dst, v_dst_bytes_per_row);
+      }
+      v_i = (((uint64_t)(self->private_impl.f_dst_x)) * ((uint64_t)(v_dst_bytes_per_pixel)));
+      if (v_i < ((uint64_t)(v_dst.len))) {
+        wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, wuffs_base__slice_u8__subslice_i(v_dst, v_i), wuffs_base__pixel_buffer__palette(a_dst), wuffs_base__make_slice_u8(v_bgrx, 4));
+      }
+      self->private_impl.f_dst_x += 1u;
+    }
+    self->private_impl.f_dst_y += 1u;
+  }
+  return wuffs_base__make_status(NULL);
+}
+
+// -------- func vp8.decoder.frame_dirty_rect
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__rect_ie_u32
+wuffs_vp8__decoder__frame_dirty_rect(
+    const wuffs_vp8__decoder* self) {
+  if (!self) {
+    return wuffs_base__utility__empty_rect_ie_u32();
+  }
+  if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&
+      (self->private_impl.magic != WUFFS_BASE__DISABLED)) {
+    return wuffs_base__utility__empty_rect_ie_u32();
+  }
+
+  return wuffs_base__utility__make_rect_ie_u32(
+      0u,
+      0u,
+      self->private_impl.f_width,
+      self->private_impl.f_height);
+}
+
+// -------- func vp8.decoder.num_animation_loops
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC uint32_t
+wuffs_vp8__decoder__num_animation_loops(
+    const wuffs_vp8__decoder* self) {
+  if (!self) {
+    return 0;
+  }
+  if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&
+      (self->private_impl.magic != WUFFS_BASE__DISABLED)) {
+    return 0;
+  }
+
+  return 0u;
+}
+
+// -------- func vp8.decoder.num_decoded_frame_configs
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC uint64_t
+wuffs_vp8__decoder__num_decoded_frame_configs(
+    const wuffs_vp8__decoder* self) {
+  if (!self) {
+    return 0;
+  }
+  if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&
+      (self->private_impl.magic != WUFFS_BASE__DISABLED)) {
+    return 0;
+  }
+
+  if (self->private_impl.f_call_sequence > 32u) {
+    return 1u;
+  }
+  return 0u;
+}
+
+// -------- func vp8.decoder.num_decoded_frames
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC uint64_t
+wuffs_vp8__decoder__num_decoded_frames(
+    const wuffs_vp8__decoder* self) {
+  if (!self) {
+    return 0;
+  }
+  if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&
+      (self->private_impl.magic != WUFFS_BASE__DISABLED)) {
+    return 0;
+  }
+
+  if (self->private_impl.f_call_sequence > 64u) {
+    return 1u;
+  }
+  return 0u;
+}
+
+// -------- func vp8.decoder.restart_frame
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_vp8__decoder__restart_frame(
+    wuffs_vp8__decoder* self,
+    uint64_t a_index,
+    uint64_t a_io_position) {
+  if (!self) {
+    return wuffs_base__make_status(wuffs_base__error__bad_receiver);
+  }
+  if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+    return wuffs_base__make_status(
+        (self->private_impl.magic == WUFFS_BASE__DISABLED)
+        ? wuffs_base__error__disabled_by_previous_error
+        : wuffs_base__error__initialize_not_called);
+  }
+
+  if (self->private_impl.f_call_sequence < 32u) {
+    return wuffs_base__make_status(wuffs_base__error__bad_call_sequence);
+  }
+  if ((a_index != 0u) || (a_io_position != self->private_impl.f_frame_config_io_position)) {
+    return wuffs_base__make_status(wuffs_base__error__bad_argument);
+  }
+  self->private_impl.f_call_sequence = 40u;
+  return wuffs_base__make_status(NULL);
+}
+
+// -------- func vp8.decoder.set_report_metadata
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct
+wuffs_vp8__decoder__set_report_metadata(
+    wuffs_vp8__decoder* self,
+    uint32_t a_fourcc,
+    bool a_report) {
+  return wuffs_base__make_empty_struct();
+}
+
+// -------- func vp8.decoder.tell_me_more
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_vp8__decoder__tell_me_more(
+    wuffs_vp8__decoder* self,
+    wuffs_base__io_buffer* a_dst,
+    wuffs_base__more_information* a_minfo,
+    wuffs_base__io_buffer* a_src) {
+  if (!self) {
+    return wuffs_base__make_status(wuffs_base__error__bad_receiver);
+  }
+  if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+    return wuffs_base__make_status(
+        (self->private_impl.magic == WUFFS_BASE__DISABLED)
+        ? wuffs_base__error__disabled_by_previous_error
+        : wuffs_base__error__initialize_not_called);
+  }
+  if (!a_dst || !a_src) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+    return wuffs_base__make_status(wuffs_base__error__bad_argument);
+  }
+  if ((self->private_impl.active_coroutine != 0) &&
+      (self->private_impl.active_coroutine != 4)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+    return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls);
+  }
+  self->private_impl.active_coroutine = 0;
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
+  status = wuffs_base__make_status(wuffs_base__error__no_more_information);
+  goto exit;
+
+  goto ok;
+  ok:
+  goto exit;
+  exit:
+  if (wuffs_base__status__is_error(&status)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+  }
+  return status;
+}
+
+// -------- func vp8.decoder.workbuf_len
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64
+wuffs_vp8__decoder__workbuf_len(
+    const wuffs_vp8__decoder* self) {
+  if (!self) {
+    return wuffs_base__utility__empty_range_ii_u64();
+  }
+  if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&
+      (self->private_impl.magic != WUFFS_BASE__DISABLED)) {
+    return wuffs_base__utility__empty_range_ii_u64();
+  }
+
+  return wuffs_base__utility__make_range_ii_u64(0u, 0u);
+}
+
 #endif  // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__VP8)
 
 #if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__WBMP)
@@ -77060,6 +78141,7 @@
 static wuffs_base__status
 wuffs_webp__decoder__do_decode_image_config_limited(
     wuffs_webp__decoder* self,
+    wuffs_base__image_config* a_dst,
     wuffs_base__io_buffer* a_src);
 
 WUFFS_BASE__GENERATED_C_CODE
@@ -77204,6 +78286,13 @@
     }
   }
 
+  {
+    wuffs_base__status z = wuffs_vp8__decoder__initialize(
+        &self->private_data.f_vp8, sizeof(self->private_data.f_vp8), WUFFS_VERSION, options);
+    if (z.repr) {
+      return z;
+    }
+  }
   self->private_impl.magic = WUFFS_BASE__MAGIC;
   self->private_impl.vtable_for__wuffs_base__image_decoder.vtable_name =
       wuffs_base__image_decoder__vtable_name;
@@ -79200,7 +80289,7 @@
           if (a_src) {
             a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
           }
-          wuffs_base__status t_2 = wuffs_webp__decoder__do_decode_image_config_limited(self, a_src);
+          wuffs_base__status t_2 = wuffs_webp__decoder__do_decode_image_config_limited(self, a_dst, a_src);
           v_status = t_2;
           if (a_src) {
             iop_a_src = a_src->data.ptr + a_src->meta.ri;
@@ -79232,7 +80321,7 @@
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(5);
     }
     self->private_impl.f_frame_config_io_position = wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src)));
-    if (a_dst != NULL) {
+    if ( ! self->private_impl.f_is_vp8_lossy && (a_dst != NULL)) {
       wuffs_base__image_config__set(
           a_dst,
           self->private_impl.f_pixfmt,
@@ -79268,6 +80357,7 @@
 static wuffs_base__status
 wuffs_webp__decoder__do_decode_image_config_limited(
     wuffs_webp__decoder* self,
+    wuffs_base__image_config* a_dst,
     wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = wuffs_base__make_status(NULL);
 
@@ -79353,90 +80443,8 @@
       v_c32 = t_1;
     }
     if (v_c32 == 540561494u) {
-      status = wuffs_base__make_status(wuffs_webp__error__unsupported_webp_file);
-      goto exit;
+      self->private_impl.f_is_vp8_lossy = true;
     } else if (v_c32 == 1278758998u) {
-      {
-        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
-        uint32_t t_2;
-        if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) {
-          t_2 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src);
-          iop_a_src += 4;
-        } else {
-          self->private_data.s_do_decode_image_config_limited.scratch = 0;
-          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
-          while (true) {
-            if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
-              status = wuffs_base__make_status(wuffs_base__suspension__short_read);
-              goto suspend;
-            }
-            uint64_t* scratch = &self->private_data.s_do_decode_image_config_limited.scratch;
-            uint32_t num_bits_2 = ((uint32_t)(*scratch >> 56));
-            *scratch <<= 8;
-            *scratch >>= 8;
-            *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_2;
-            if (num_bits_2 == 24) {
-              t_2 = ((uint32_t)(*scratch));
-              break;
-            }
-            num_bits_2 += 8u;
-            *scratch |= ((uint64_t)(num_bits_2)) << 56;
-          }
-        }
-        self->private_impl.f_sub_chunk_length = t_2;
-      }
-      if (self->private_impl.f_sub_chunk_length < 4u) {
-        status = wuffs_base__make_status(wuffs_webp__error__bad_header);
-        goto exit;
-      }
-      self->private_impl.f_sub_chunk_has_padding = ((self->private_impl.f_sub_chunk_length & 1u) != 0u);
-      while (true) {
-        {
-          const bool o_0_closed_a_src = a_src->meta.closed;
-          const uint8_t* o_0_io2_a_src = io2_a_src;
-          wuffs_private_impl__io_reader__limit(&io2_a_src, iop_a_src,
-              ((uint64_t)(self->private_impl.f_sub_chunk_length)));
-          if (a_src) {
-            size_t n = ((size_t)(io2_a_src - a_src->data.ptr));
-            a_src->meta.closed = a_src->meta.closed && (a_src->meta.wi <= n);
-            a_src->meta.wi = n;
-          }
-          v_r_mark = ((uint64_t)(iop_a_src - io0_a_src));
-          {
-            if (a_src) {
-              a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
-            }
-            wuffs_base__status t_3 = wuffs_webp__decoder__do_decode_image_config_limited_vp8l(self, a_src);
-            v_status = t_3;
-            if (a_src) {
-              iop_a_src = a_src->data.ptr + a_src->meta.ri;
-            }
-          }
-          wuffs_private_impl__u32__sat_sub_indirect(&self->private_impl.f_sub_chunk_length, ((uint32_t)(wuffs_private_impl__io__count_since(v_r_mark, ((uint64_t)(iop_a_src - io0_a_src))))));
-          io2_a_src = o_0_io2_a_src;
-          if (a_src) {
-            a_src->meta.closed = o_0_closed_a_src;
-            a_src->meta.wi = ((size_t)(io2_a_src - a_src->data.ptr));
-          }
-        }
-        if (wuffs_base__status__is_ok(&v_status)) {
-          break;
-        } else if ( ! wuffs_base__status__is_suspension(&v_status)) {
-          status = v_status;
-          if (wuffs_base__status__is_error(&status)) {
-            goto exit;
-          } else if (wuffs_base__status__is_suspension(&status)) {
-            status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension);
-            goto exit;
-          }
-          goto ok;
-        } else if ((v_status.repr == wuffs_base__suspension__short_read) && (self->private_impl.f_sub_chunk_length == 0u)) {
-          status = wuffs_base__make_status(wuffs_webp__error__short_chunk);
-          goto exit;
-        }
-        status = v_status;
-        WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(7);
-      }
     } else if (v_c32 == 1480085590u) {
       status = wuffs_base__make_status(wuffs_webp__error__unsupported_webp_file);
       goto exit;
@@ -79444,6 +80452,100 @@
       status = wuffs_base__make_status(wuffs_webp__error__bad_header);
       goto exit;
     }
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
+      uint32_t t_2;
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) {
+        t_2 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src);
+        iop_a_src += 4;
+      } else {
+        self->private_data.s_do_decode_image_config_limited.scratch = 0;
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
+        while (true) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+            status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+            goto suspend;
+          }
+          uint64_t* scratch = &self->private_data.s_do_decode_image_config_limited.scratch;
+          uint32_t num_bits_2 = ((uint32_t)(*scratch >> 56));
+          *scratch <<= 8;
+          *scratch >>= 8;
+          *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_2;
+          if (num_bits_2 == 24) {
+            t_2 = ((uint32_t)(*scratch));
+            break;
+          }
+          num_bits_2 += 8u;
+          *scratch |= ((uint64_t)(num_bits_2)) << 56;
+        }
+      }
+      self->private_impl.f_sub_chunk_length = t_2;
+    }
+    if (self->private_impl.f_sub_chunk_length < 4u) {
+      status = wuffs_base__make_status(wuffs_webp__error__bad_header);
+      goto exit;
+    }
+    self->private_impl.f_sub_chunk_has_padding = ((self->private_impl.f_sub_chunk_length & 1u) != 0u);
+    while (true) {
+      {
+        const bool o_0_closed_a_src = a_src->meta.closed;
+        const uint8_t* o_0_io2_a_src = io2_a_src;
+        wuffs_private_impl__io_reader__limit(&io2_a_src, iop_a_src,
+            ((uint64_t)(self->private_impl.f_sub_chunk_length)));
+        if (a_src) {
+          size_t n = ((size_t)(io2_a_src - a_src->data.ptr));
+          a_src->meta.closed = a_src->meta.closed && (a_src->meta.wi <= n);
+          a_src->meta.wi = n;
+        }
+        v_r_mark = ((uint64_t)(iop_a_src - io0_a_src));
+        if (self->private_impl.f_is_vp8_lossy) {
+          {
+            if (a_src) {
+              a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+            }
+            wuffs_base__status t_3 = wuffs_vp8__decoder__decode_image_config(&self->private_data.f_vp8, a_dst, a_src);
+            v_status = t_3;
+            if (a_src) {
+              iop_a_src = a_src->data.ptr + a_src->meta.ri;
+            }
+          }
+        } else {
+          {
+            if (a_src) {
+              a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+            }
+            wuffs_base__status t_4 = wuffs_webp__decoder__do_decode_image_config_limited_vp8l(self, a_src);
+            v_status = t_4;
+            if (a_src) {
+              iop_a_src = a_src->data.ptr + a_src->meta.ri;
+            }
+          }
+        }
+        wuffs_private_impl__u32__sat_sub_indirect(&self->private_impl.f_sub_chunk_length, ((uint32_t)(wuffs_private_impl__io__count_since(v_r_mark, ((uint64_t)(iop_a_src - io0_a_src))))));
+        io2_a_src = o_0_io2_a_src;
+        if (a_src) {
+          a_src->meta.closed = o_0_closed_a_src;
+          a_src->meta.wi = ((size_t)(io2_a_src - a_src->data.ptr));
+        }
+      }
+      if (wuffs_base__status__is_ok(&v_status)) {
+        break;
+      } else if ( ! wuffs_base__status__is_suspension(&v_status)) {
+        status = v_status;
+        if (wuffs_base__status__is_error(&status)) {
+          goto exit;
+        } else if (wuffs_base__status__is_suspension(&status)) {
+          status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension);
+          goto exit;
+        }
+        goto ok;
+      } else if ((v_status.repr == wuffs_base__suspension__short_read) && (self->private_impl.f_sub_chunk_length == 0u)) {
+        status = wuffs_base__make_status(wuffs_webp__error__short_chunk);
+        goto exit;
+      }
+      status = v_status;
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(7);
+    }
 
     ok:
     self->private_impl.p_do_decode_image_config_limited = 0;
@@ -79606,9 +80708,16 @@
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
     while (true) {
-      {
-        wuffs_base__status t_0 = wuffs_webp__decoder__do_decode_frame_config(self, a_dst, a_src);
-        v_status = t_0;
+      if (self->private_impl.f_is_vp8_lossy) {
+        {
+          wuffs_base__status t_0 = wuffs_vp8__decoder__decode_frame_config(&self->private_data.f_vp8, a_dst, a_src);
+          v_status = t_0;
+        }
+      } else {
+        {
+          wuffs_base__status t_1 = wuffs_webp__decoder__do_decode_frame_config(self, a_dst, a_src);
+          v_status = t_1;
+        }
       }
       if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) {
         status = wuffs_base__make_status(wuffs_webp__error__truncated_input);
@@ -79762,14 +80871,26 @@
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
     while (true) {
-      {
-        wuffs_base__status t_0 = wuffs_webp__decoder__do_decode_frame(self,
-            a_dst,
-            a_src,
-            a_blend,
-            a_workbuf,
-            a_opts);
-        v_status = t_0;
+      if (self->private_impl.f_is_vp8_lossy) {
+        {
+          wuffs_base__status t_0 = wuffs_vp8__decoder__decode_frame(&self->private_data.f_vp8,
+              a_dst,
+              a_src,
+              a_blend,
+              a_workbuf,
+              a_opts);
+          v_status = t_0;
+        }
+      } else {
+        {
+          wuffs_base__status t_1 = wuffs_webp__decoder__do_decode_frame(self,
+              a_dst,
+              a_src,
+              a_blend,
+              a_workbuf,
+              a_opts);
+          v_status = t_1;
+        }
       }
       if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) {
         status = wuffs_base__make_status(wuffs_webp__error__truncated_input);
@@ -80683,6 +81804,9 @@
     return wuffs_base__utility__empty_rect_ie_u32();
   }
 
+  if (self->private_impl.f_is_vp8_lossy) {
+    return wuffs_vp8__decoder__frame_dirty_rect(&self->private_data.f_vp8);
+  }
   return wuffs_base__utility__make_rect_ie_u32(
       0u,
       0u,
@@ -80721,6 +81845,9 @@
     return 0;
   }
 
+  if (self->private_impl.f_is_vp8_lossy) {
+    return wuffs_vp8__decoder__num_decoded_frame_configs(&self->private_data.f_vp8);
+  }
   if (self->private_impl.f_call_sequence > 32u) {
     return 1u;
   }
@@ -80741,6 +81868,9 @@
     return 0;
   }
 
+  if (self->private_impl.f_is_vp8_lossy) {
+    return wuffs_vp8__decoder__num_decoded_frames(&self->private_data.f_vp8);
+  }
   if (self->private_impl.f_call_sequence > 64u) {
     return 1u;
   }
@@ -80765,6 +81895,12 @@
         : wuffs_base__error__initialize_not_called);
   }
 
+  wuffs_base__status v_status = wuffs_base__make_status(NULL);
+
+  if (self->private_impl.f_is_vp8_lossy) {
+    v_status = wuffs_vp8__decoder__restart_frame(&self->private_data.f_vp8, a_index, a_io_position);
+    return wuffs_private_impl__status__ensure_not_a_suspension(v_status);
+  }
   if (self->private_impl.f_call_sequence < 32u) {
     return wuffs_base__make_status(wuffs_base__error__bad_call_sequence);
   }
@@ -80843,6 +81979,9 @@
     return wuffs_base__utility__empty_range_ii_u64();
   }
 
+  if (self->private_impl.f_is_vp8_lossy) {
+    return wuffs_vp8__decoder__workbuf_len(&self->private_data.f_vp8);
+  }
   return wuffs_base__utility__make_range_ii_u64(((uint64_t)(self->private_impl.f_workbuf_offset_for_transform[3u])), ((uint64_t)(self->private_impl.f_workbuf_offset_for_transform[3u])));
 }
 
diff --git a/std/vp8/decode_vp8.wuffs b/std/vp8/decode_vp8.wuffs
index 98b6cf1..27151c4 100644
--- a/std/vp8/decode_vp8.wuffs
+++ b/std/vp8/decode_vp8.wuffs
@@ -8,17 +8,276 @@
 //
 // SPDX-License-Identifier: Apache-2.0 OR MIT
 
-// --------
+pub status "#bad header"
+pub status "#truncated input"
+pub status "#unsupported VP8 file"
 
-// This code is a placeholder. std/webp depends on std/vp8 (a lossy image
-// codec) even though, in the very short term, std/webp only implements the
-// lossless VP8L format. In the medium term, though, std/webp will also support
-// VP8 and the resultant code changes will be smaller and simpler if std/webp
-// depends on std/vp8 right from the start.
-//
-// Also, a std/webm package does not exist yet, but might in the future. If it
-// does, it will also depend on std/vp8 (but not std/webp).
+pub const DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE : base.u64 = 0
 
-pub struct placeholder?(
-        placeholder : base.u32,
+pub struct decoder? implements base.image_decoder(
+        width  : base.u32[..= 0x3FFF],
+        height : base.u32[..= 0x3FFF],
+
+        // The call sequence state machine is discussed in
+        // (/doc/std/image-decoders-call-sequence.md).
+        call_sequence : base.u8,
+
+        frame_config_io_position : base.u64,
+
+        dst_x : base.u32,
+        dst_y : base.u32,
+
+        swizzler : base.pixel_swizzler,
+        util     : base.utility,
 )
+
+pub func decoder.get_quirk(key: base.u32) base.u64 {
+    return 0
+}
+
+pub func decoder.set_quirk!(key: base.u32, value: base.u64) base.status {
+    return base."#unsupported option"
+}
+
+pub func decoder.decode_image_config?(dst: nptr base.image_config, src: base.io_reader) {
+    var status : base.status
+
+    while true {
+        status =? this.do_decode_image_config?(dst: args.dst, src: args.src)
+        if (status == base."$short read") and args.src.is_closed() {
+            return "#truncated input"
+        }
+        yield? status
+    }
+}
+
+pri func decoder.do_decode_image_config?(dst: nptr base.image_config, src: base.io_reader) {
+    var c32 : base.u32
+
+    if this.call_sequence <> 0x00 {
+        return base."#bad call sequence"
+    }
+
+    c32 = args.src.read_u24le_as_u32?()
+    if (c32 & 0x01) <> 0 {
+        // TODO: support non-key frames.
+        return "#unsupported VP8 file"
+    }
+
+    c32 = args.src.read_u24le_as_u32?()
+    if c32 <> '\x9D\x01\x2A'le {
+        return "#bad header"
+    }
+
+    c32 = args.src.read_u32le?()
+    this.width = 0x3FFF & (c32 >> 0)
+    this.height = 0x3FFF & (c32 >> 16)
+
+    this.frame_config_io_position = args.src.position()
+
+    if args.dst <> nullptr {
+        args.dst.set!(
+                pixfmt: base.PIXEL_FORMAT__BGRX,
+                pixsub: 0,
+                width: this.width,
+                height: this.height,
+                first_frame_io_position: this.frame_config_io_position,
+                first_frame_is_opaque: false)
+    }
+
+    this.call_sequence = 0x20
+}
+
+pub func decoder.decode_frame_config?(dst: nptr base.frame_config, src: base.io_reader) {
+    var status : base.status
+
+    while true {
+        status =? this.do_decode_frame_config?(dst: args.dst, src: args.src)
+        if (status == base."$short read") and args.src.is_closed() {
+            return "#truncated input"
+        }
+        yield? status
+    }
+}
+
+pri func decoder.do_decode_frame_config?(dst: nptr base.frame_config, src: base.io_reader) {
+    if this.call_sequence == 0x20 {
+        // No-op.
+    } else if this.call_sequence < 0x20 {
+        this.do_decode_image_config?(dst: nullptr, src: args.src)
+    } else if this.call_sequence == 0x28 {
+        if this.frame_config_io_position <> args.src.position() {
+            return base."#bad restart"
+        }
+    } else if this.call_sequence == 0x40 {
+        this.call_sequence = 0x60
+        return base."@end of data"
+    } else {
+        return base."@end of data"
+    }
+
+    if args.dst <> nullptr {
+        args.dst.set!(bounds: this.util.make_rect_ie_u32(
+                min_incl_x: 0,
+                min_incl_y: 0,
+                max_excl_x: this.width,
+                max_excl_y: this.height),
+                duration: 0,
+                index: 0,
+                io_position: this.frame_config_io_position,
+                disposal: 0,
+                opaque_within_bounds: false,
+                overwrite_instead_of_blend: false,
+                background_color: 0x0000_0000)
+    }
+
+    this.call_sequence = 0x40
+}
+
+pub func decoder.decode_frame?(dst: ptr base.pixel_buffer, src: base.io_reader, blend: base.pixel_blend, workbuf: slice base.u8, opts: nptr base.decode_frame_options) {
+    var status : base.status
+
+    while true {
+        status =? this.do_decode_frame?(dst: args.dst, src: args.src, blend: args.blend, workbuf: args.workbuf, opts: args.opts)
+        if (status == base."$short read") and args.src.is_closed() {
+            return "#truncated input"
+        }
+        yield? status
+    }
+}
+
+pri func decoder.do_decode_frame?(dst: ptr base.pixel_buffer, src: base.io_reader, blend: base.pixel_blend, workbuf: slice base.u8, opts: nptr base.decode_frame_options) {
+    var status : base.status
+
+    if this.call_sequence == 0x40 {
+        // No-op.
+    } else if this.call_sequence < 0x40 {
+        this.do_decode_frame_config?(dst: nullptr, src: args.src)
+    } else {
+        return base."@end of data"
+    }
+
+    this.dst_x = 0
+    this.dst_y = 0
+
+    status = this.swizzler.prepare!(
+            dst_pixfmt: args.dst.pixel_format(),
+            dst_palette: args.dst.palette(),
+            src_pixfmt: this.util.make_pixel_format(repr: base.PIXEL_FORMAT__BGRX),
+            src_palette: this.util.empty_slice_u8(),
+            blend: args.blend)
+    if not status.is_ok() {
+        return status
+    }
+
+    // TODO: actually decode the pixels.
+    status = this.make_a_placeholder_gradient!(dst: args.dst)
+    if not status.is_ok() {
+        return status
+    }
+
+    this.call_sequence = 0x60
+}
+
+pri func decoder.make_a_placeholder_gradient!(dst: ptr base.pixel_buffer) base.status {
+    var dst_pixfmt          : base.pixel_format
+    var dst_bits_per_pixel  : base.u32[..= 256]
+    var dst_bytes_per_pixel : base.u32[..= 32]
+    var dst_bytes_per_row   : base.u64
+    var tab                 : table base.u8
+    var dst                 : slice base.u8
+    var i                   : base.u64
+    var bgrx                : array[4] base.u8
+
+    // TODO: the dst_pixfmt variable shouldn't be necessary. We should be able
+    // to chain the two calls: "args.dst.pixel_format().bits_per_pixel()".
+    dst_pixfmt = args.dst.pixel_format()
+    dst_bits_per_pixel = dst_pixfmt.bits_per_pixel()
+    if (dst_bits_per_pixel & 7) <> 0 {
+        return base."#unsupported option"
+    }
+    dst_bytes_per_pixel = dst_bits_per_pixel / 8
+    dst_bytes_per_row = (this.width * dst_bytes_per_pixel) as base.u64
+    tab = args.dst.plane(p: 0)
+
+    bgrx[0] = 0x80
+
+    while this.dst_y < this.height {
+        assert this.dst_y < 0x3FFF via "a < b: a < c; c <= b"(c: this.height)
+        bgrx[1] = (this.dst_y & 0xFF) as base.u8
+
+        this.dst_x = 0
+        while this.dst_x < this.width,
+                inv this.dst_y < 0x3FFF,
+        {
+            assert this.dst_x < 0x3FFF via "a < b: a < c; c <= b"(c: this.width)
+            bgrx[2] = (this.dst_x & 0xFF) as base.u8
+
+            dst = tab.row_u32(y: this.dst_y)
+            if dst_bytes_per_row < dst.length() {
+                dst = dst[.. dst_bytes_per_row]
+            }
+            i = (this.dst_x as base.u64) * (dst_bytes_per_pixel as base.u64)
+            if i < dst.length() {
+                this.swizzler.swizzle_interleaved_from_slice!(
+                        dst: dst[i ..],
+                        dst_palette: args.dst.palette(),
+                        src: bgrx[.. 4])
+            }
+
+            this.dst_x += 1
+        }
+        this.dst_y += 1
+    }
+
+    return ok
+}
+
+pub func decoder.frame_dirty_rect() base.rect_ie_u32 {
+    return this.util.make_rect_ie_u32(
+            min_incl_x: 0,
+            min_incl_y: 0,
+            max_excl_x: this.width,
+            max_excl_y: this.height)
+}
+
+pub func decoder.num_animation_loops() base.u32 {
+    return 0
+}
+
+pub func decoder.num_decoded_frame_configs() base.u64 {
+    if this.call_sequence > 0x20 {
+        return 1
+    }
+    return 0
+}
+
+pub func decoder.num_decoded_frames() base.u64 {
+    if this.call_sequence > 0x40 {
+        return 1
+    }
+    return 0
+}
+
+pub func decoder.restart_frame!(index: base.u64, io_position: base.u64) base.status {
+    if this.call_sequence < 0x20 {
+        return base."#bad call sequence"
+    }
+    if (args.index <> 0) or (args.io_position <> this.frame_config_io_position) {
+        return base."#bad argument"
+    }
+    this.call_sequence = 0x28
+    return ok
+}
+
+pub func decoder.set_report_metadata!(fourcc: base.u32, report: base.bool) {
+    // No-op. VP8 doesn't support metadata (but WEBP does).
+}
+
+pub func decoder.tell_me_more?(dst: base.io_writer, minfo: nptr base.more_information, src: base.io_reader) {
+    return base."#no more information"
+}
+
+pub func decoder.workbuf_len() base.range_ii_u64 {
+    return this.util.make_range_ii_u64(min_incl: 0, max_incl: 0)
+}
diff --git a/std/webp/decode_webp.wuffs b/std/webp/decode_webp.wuffs
index fa9d05e..4af5f01 100644
--- a/std/webp/decode_webp.wuffs
+++ b/std/webp/decode_webp.wuffs
@@ -42,6 +42,8 @@
 
         sub_chunk_has_padding : base.bool,
 
+        is_vp8_lossy : base.bool,
+
         frame_config_io_position : base.u64,
 
         riff_chunk_length : base.u32,
@@ -91,6 +93,8 @@
         // apply_transform_color_indexing modifies the pixel buffer in-place.
         workbuf_offset_for_color_indexing : base.u32[..= 0x4000_0000],
 
+        vp8 : vp8.decoder,
+
         swizzler : base.pixel_swizzler,
         util     : base.utility,
 ) + (
@@ -178,7 +182,7 @@
     while true {
         io_limit (io: args.src, limit: this.riff_chunk_length as base.u64) {
             r_mark = args.src.mark()
-            status =? this.do_decode_image_config_limited?(src: args.src)
+            status =? this.do_decode_image_config_limited?(dst: args.dst, src: args.src)
             this.riff_chunk_length ~sat-=
                     (args.src.count_since(mark: r_mark) & 0xFFFF_FFFF) as base.u32
         }
@@ -195,7 +199,7 @@
 
     this.frame_config_io_position = args.src.position()
 
-    if args.dst <> nullptr {
+    if (not this.is_vp8_lossy) and (args.dst <> nullptr) {
         args.dst.set!(
                 pixfmt: this.pixfmt,
                 pixsub: 0,
@@ -208,7 +212,7 @@
     this.call_sequence = 0x20
 }
 
-pri func decoder.do_decode_image_config_limited?(src: base.io_reader) {
+pri func decoder.do_decode_image_config_limited?(dst: nptr base.image_config, src: base.io_reader) {
     var c32    : base.u32
     var r_mark : base.u64
     var status : base.status
@@ -220,39 +224,42 @@
 
     c32 = args.src.read_u32le?()
     if c32 == 'VP8 'le {
-        return "#unsupported WebP file"
-
+        this.is_vp8_lossy = true
     } else if c32 == 'VP8L'le {
-        this.sub_chunk_length = args.src.read_u32le?()
-        if this.sub_chunk_length < 4 {
-            return "#bad header"
-        }
-        this.sub_chunk_has_padding = (this.sub_chunk_length & 1) <> 0
-
-        while true {
-            io_limit (io: args.src, limit: this.sub_chunk_length as base.u64) {
-                r_mark = args.src.mark()
-                status =? this.do_decode_image_config_limited_vp8l?(src: args.src)
-                this.sub_chunk_length ~sat-=
-                        (args.src.count_since(mark: r_mark) & 0xFFFF_FFFF) as base.u32
-            }
-
-            if status.is_ok() {
-                break
-            } else if not status.is_suspension() {
-                return status
-            } else if (status == base."$short read") and (this.sub_chunk_length == 0) {
-                return "#short chunk"
-            }
-            yield? status
-        }
-
+        // No-op.
     } else if c32 == 'VP8X'le {
         return "#unsupported WebP file"
-
     } else {
         return "#bad header"
     }
+
+    this.sub_chunk_length = args.src.read_u32le?()
+    if this.sub_chunk_length < 4 {
+        return "#bad header"
+    }
+    this.sub_chunk_has_padding = (this.sub_chunk_length & 1) <> 0
+
+    while true {
+        io_limit (io: args.src, limit: this.sub_chunk_length as base.u64) {
+            r_mark = args.src.mark()
+            if this.is_vp8_lossy {
+                status =? this.vp8.decode_image_config?(dst: args.dst, src: args.src)
+            } else {
+                status =? this.do_decode_image_config_limited_vp8l?(src: args.src)
+            }
+            this.sub_chunk_length ~sat-=
+                    (args.src.count_since(mark: r_mark) & 0xFFFF_FFFF) as base.u32
+        }
+
+        if status.is_ok() {
+            break
+        } else if not status.is_suspension() {
+            return status
+        } else if (status == base."$short read") and (this.sub_chunk_length == 0) {
+            return "#short chunk"
+        }
+        yield? status
+    }
 }
 
 pri func decoder.do_decode_image_config_limited_vp8l?(src: base.io_reader) {
@@ -288,7 +295,11 @@
     var status : base.status
 
     while true {
-        status =? this.do_decode_frame_config?(dst: args.dst, src: args.src)
+        if this.is_vp8_lossy {
+            status =? this.vp8.decode_frame_config?(dst: args.dst, src: args.src)
+        } else {
+            status =? this.do_decode_frame_config?(dst: args.dst, src: args.src)
+        }
         if (status == base."$short read") and args.src.is_closed() {
             return "#truncated input"
         }
@@ -334,7 +345,11 @@
     var status : base.status
 
     while true {
-        status =? this.do_decode_frame?(dst: args.dst, src: args.src, blend: args.blend, workbuf: args.workbuf, opts: args.opts)
+        if this.is_vp8_lossy {
+            status =? this.vp8.decode_frame?(dst: args.dst, src: args.src, blend: args.blend, workbuf: args.workbuf, opts: args.opts)
+        } else {
+            status =? this.do_decode_frame?(dst: args.dst, src: args.src, blend: args.blend, workbuf: args.workbuf, opts: args.opts)
+        }
         if (status == base."$short read") and args.src.is_closed() {
             return "#truncated input"
         }
@@ -810,6 +825,9 @@
 }
 
 pub func decoder.frame_dirty_rect() base.rect_ie_u32 {
+    if this.is_vp8_lossy {
+        return this.vp8.frame_dirty_rect()
+    }
     return this.util.make_rect_ie_u32(
             min_incl_x: 0,
             min_incl_y: 0,
@@ -822,6 +840,9 @@
 }
 
 pub func decoder.num_decoded_frame_configs() base.u64 {
+    if this.is_vp8_lossy {
+        return this.vp8.num_decoded_frame_configs()
+    }
     if this.call_sequence > 0x20 {
         return 1
     }
@@ -829,6 +850,9 @@
 }
 
 pub func decoder.num_decoded_frames() base.u64 {
+    if this.is_vp8_lossy {
+        return this.vp8.num_decoded_frames()
+    }
     if this.call_sequence > 0x40 {
         return 1
     }
@@ -836,6 +860,12 @@
 }
 
 pub func decoder.restart_frame!(index: base.u64, io_position: base.u64) base.status {
+    var status : base.status
+
+    if this.is_vp8_lossy {
+        status = this.vp8.restart_frame!(index: args.index, io_position: args.io_position)
+        return status
+    }
     if this.call_sequence < 0x20 {
         return base."#bad call sequence"
     }
@@ -855,6 +885,9 @@
 }
 
 pub func decoder.workbuf_len() base.range_ii_u64 {
+    if this.is_vp8_lossy {
+        return this.vp8.workbuf_len()
+    }
     return this.util.make_range_ii_u64(
             min_incl: this.workbuf_offset_for_transform[3] as base.u64,
             max_incl: this.workbuf_offset_for_transform[3] as base.u64)
diff --git a/test/3pdata/nia-checksums-of-blinksuite.txt b/test/3pdata/nia-checksums-of-blinksuite.txt
index 45e9782..6b0a289 100644
--- a/test/3pdata/nia-checksums-of-blinksuite.txt
+++ b/test/3pdata/nia-checksums-of-blinksuite.txt
@@ -91,6 +91,7 @@
 OK. 1de5a906 test/3pdata/blinksuite/icc-v2-gbr.jpg
 OK. 0da5f37e test/3pdata/blinksuite/jpeg-height-exif-orientation.jpg
 OK. 4409dca6 test/3pdata/blinksuite/large-gif-checkerboard.gif
+OK. 42c50963 test/3pdata/blinksuite/large.webp
 BAD 2f310e57 test/3pdata/blinksuite/missing-eoi.jpg
 OK. 72bbe2b6 test/3pdata/blinksuite/motion-jpeg-single-frame.jpg
 OK. 0268284a test/3pdata/blinksuite/mu.png
@@ -169,7 +170,10 @@
 OK. 1b1f5f96 test/3pdata/blinksuite/stripes-large.png
 OK. 6b85cdb9 test/3pdata/blinksuite/stripes-small.png
 OK. 16c2c0fe test/3pdata/blinksuite/test-load.jpg
+OK. 2e88e913 test/3pdata/blinksuite/test.webp
 OK. 19d3a207 test/3pdata/blinksuite/test3.webp
+OK. 60f368dc test/3pdata/blinksuite/truncated2.webp
+OK. 194d8b64 test/3pdata/blinksuite/webp-color-no-profile-lossy.webp
 BAD ed67a9a5 test/3pdata/blinksuite/wrong-block-length.gif
 OK. 577c5c4c test/3pdata/blinksuite/ycbcr-420-fast-int-progressive.jpg
 OK. bf4cd8e2 test/3pdata/blinksuite/ycbcr-420-float-progressive.jpg
diff --git a/test/3pdata/nia-checksums-of-webpsuite.txt b/test/3pdata/nia-checksums-of-webpsuite.txt
index f041710..a7a1b2a 100644
--- a/test/3pdata/nia-checksums-of-webpsuite.txt
+++ b/test/3pdata/nia-checksums-of-webpsuite.txt
@@ -1,5 +1,7 @@
 # Generated by script/print-nia-checksums.sh
 OK. ca4c0038 test/3pdata/webpsuite/bad_palette_index.webp
+OK. 807f5a23 test/3pdata/webpsuite/bryce.webp
+OK. 2ee5c270 test/3pdata/webpsuite/bug3.webp
 OK. 6fbda804 test/3pdata/webpsuite/color_cache_bits_11.webp
 BAD d338aed8 test/3pdata/webpsuite/dual_transform.webp
 OK. 1ef61f19 test/3pdata/webpsuite/grid.bmp
@@ -47,9 +49,77 @@
 BAD 62adeb37 test/3pdata/webpsuite/lossless_vec_2_7.webp
 OK. 6706e719 test/3pdata/webpsuite/lossless_vec_2_8.webp
 BAD 62adeb37 test/3pdata/webpsuite/lossless_vec_2_9.webp
+OK. d1ed71a2 test/3pdata/webpsuite/lossy_extreme_probabilities.webp
+OK. 2e88e913 test/3pdata/webpsuite/lossy_q0_f100.webp
 OK. 6957defa test/3pdata/webpsuite/near_lossless_75.webp
 OK. 7c80a001 test/3pdata/webpsuite/one_color_no_palette.webp
 OK. 6706e719 test/3pdata/webpsuite/peak.bmp
 OK. 315bbd77 test/3pdata/webpsuite/peak.pgm
 OK. 6706e719 test/3pdata/webpsuite/peak.png
 OK. 6706e719 test/3pdata/webpsuite/peak.ppm
+OK. 69f5f4ec test/3pdata/webpsuite/segment01.webp
+OK. 69f5f4ec test/3pdata/webpsuite/segment02.webp
+OK. 69f5f4ec test/3pdata/webpsuite/segment03.webp
+OK. 61e94e53 test/3pdata/webpsuite/small_13x1.webp
+OK. 2d7e6391 test/3pdata/webpsuite/small_1x1.webp
+OK. 7c595111 test/3pdata/webpsuite/small_1x13.webp
+OK. 9240e9d0 test/3pdata/webpsuite/small_31x13.webp
+OK. 2e88e913 test/3pdata/webpsuite/test-nostrong.webp
+OK. 2e88e913 test/3pdata/webpsuite/test.webp
+OK. 8b56f1b6 test/3pdata/webpsuite/very_short.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-00-comprehensive-001.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-00-comprehensive-002.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-00-comprehensive-003.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-00-comprehensive-004.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-00-comprehensive-005.webp
+OK. e3e955c2 test/3pdata/webpsuite/vp80-00-comprehensive-006.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-00-comprehensive-007.webp
+OK. 69cf2fa7 test/3pdata/webpsuite/vp80-00-comprehensive-008.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-00-comprehensive-009.webp
+OK. 3796a852 test/3pdata/webpsuite/vp80-00-comprehensive-010.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-00-comprehensive-011.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-00-comprehensive-012.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-00-comprehensive-013.webp
+OK. e3e955c2 test/3pdata/webpsuite/vp80-00-comprehensive-014.webp
+OK. 3796a852 test/3pdata/webpsuite/vp80-00-comprehensive-015.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-00-comprehensive-016.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-00-comprehensive-017.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-01-intra-1400.webp
+OK. 4937f445 test/3pdata/webpsuite/vp80-01-intra-1411.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-01-intra-1416.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-01-intra-1417.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-02-inter-1402.webp
+OK. 4937f445 test/3pdata/webpsuite/vp80-02-inter-1412.webp
+OK. 96b4ed67 test/3pdata/webpsuite/vp80-02-inter-1418.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-02-inter-1424.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-03-segmentation-1401.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-03-segmentation-1403.webp
+OK. 1187f564 test/3pdata/webpsuite/vp80-03-segmentation-1407.webp
+OK. 1187f564 test/3pdata/webpsuite/vp80-03-segmentation-1408.webp
+OK. 1187f564 test/3pdata/webpsuite/vp80-03-segmentation-1409.webp
+OK. 1187f564 test/3pdata/webpsuite/vp80-03-segmentation-1410.webp
+OK. 4937f445 test/3pdata/webpsuite/vp80-03-segmentation-1413.webp
+OK. 3796a852 test/3pdata/webpsuite/vp80-03-segmentation-1414.webp
+OK. 3796a852 test/3pdata/webpsuite/vp80-03-segmentation-1415.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-03-segmentation-1425.webp
+OK. 1187f564 test/3pdata/webpsuite/vp80-03-segmentation-1426.webp
+OK. 1187f564 test/3pdata/webpsuite/vp80-03-segmentation-1427.webp
+OK. 1187f564 test/3pdata/webpsuite/vp80-03-segmentation-1432.webp
+OK. 1187f564 test/3pdata/webpsuite/vp80-03-segmentation-1435.webp
+OK. 1187f564 test/3pdata/webpsuite/vp80-03-segmentation-1436.webp
+OK. 1187f564 test/3pdata/webpsuite/vp80-03-segmentation-1437.webp
+OK. 1187f564 test/3pdata/webpsuite/vp80-03-segmentation-1441.webp
+OK. 1187f564 test/3pdata/webpsuite/vp80-03-segmentation-1442.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-04-partitions-1404.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-04-partitions-1405.webp
+OK. c42a950e test/3pdata/webpsuite/vp80-04-partitions-1406.webp
+OK. 1187f564 test/3pdata/webpsuite/vp80-05-sharpness-1428.webp
+OK. 1187f564 test/3pdata/webpsuite/vp80-05-sharpness-1429.webp
+OK. 1187f564 test/3pdata/webpsuite/vp80-05-sharpness-1430.webp
+OK. 1187f564 test/3pdata/webpsuite/vp80-05-sharpness-1431.webp
+OK. 1187f564 test/3pdata/webpsuite/vp80-05-sharpness-1433.webp
+OK. 1187f564 test/3pdata/webpsuite/vp80-05-sharpness-1434.webp
+OK. 1187f564 test/3pdata/webpsuite/vp80-05-sharpness-1438.webp
+OK. 1187f564 test/3pdata/webpsuite/vp80-05-sharpness-1439.webp
+OK. 1187f564 test/3pdata/webpsuite/vp80-05-sharpness-1440.webp
+OK. 35ab76d3 test/3pdata/webpsuite/vp80-05-sharpness-1443.webp
diff --git a/test/c/std/webp.c b/test/c/std/webp.c
index 9c528b1..23d3b19 100644
--- a/test/c/std/webp.c
+++ b/test/c/std/webp.c
@@ -88,7 +88,7 @@
 // --------
 
 const char*  //
-test_wuffs_webp_decode_interface() {
+test_wuffs_webp_decode_interface_lossless() {
   CHECK_FOCUS(__func__);
   wuffs_webp__decoder* dec = &g_webp_decoder;
   CHECK_STATUS("initialize",
@@ -101,6 +101,19 @@
       0xFF022460);
 }
 
+const char*  //
+test_wuffs_webp_decode_interface_lossy() {
+  CHECK_FOCUS(__func__);
+  wuffs_webp__decoder* dec = &g_webp_decoder;
+  CHECK_STATUS("initialize",
+               wuffs_webp__decoder__initialize(
+                   dec, sizeof *dec, WUFFS_VERSION,
+                   WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED));
+  return do_test__wuffs_base__image_decoder(
+      wuffs_webp__decoder__upcast_as__wuffs_base__image_decoder(dec),
+      "test/data/bricks-color.lossy.webp", 0, SIZE_MAX, 160, 120, 0xFF9F7780);
+}
+
 // ---------------- Mimic Tests
 
 #ifdef WUFFS_MIMIC
@@ -280,7 +293,8 @@
 
 proc g_tests[] = {
 
-    test_wuffs_webp_decode_interface,
+    test_wuffs_webp_decode_interface_lossless,
+    test_wuffs_webp_decode_interface_lossy,
 
 #ifdef WUFFS_MIMIC