std/webp: add new package (and vp8 placeholder)
diff --git a/doc/changelog.md b/doc/changelog.md
index e7eff93..3e1b574 100644
--- a/doc/changelog.md
+++ b/doc/changelog.md
@@ -21,6 +21,8 @@
 - Added `std/lzma`.
 - Added `std/netpbm`.
 - Added `std/sha256`.
+- Added `std/vp8`.
+- Added `std/webp`.
 - Added `std/xxhash32`.
 - Added `std/xxhash64`.
 - Added `std/xz`.
diff --git a/doc/std/README.md b/doc/std/README.md
index d9cca11..410fa2f 100644
--- a/doc/std/README.md
+++ b/doc/std/README.md
@@ -64,7 +64,9 @@
 - `PNG:       BASE, ADLER32, CRC32, DEFLATE, ZLIB`
 - `SHA256:    BASE`
 - `TGA:       BASE`
+- `VP8:       BASE`
 - `WBMP:      BASE`
+- `WEBP:      BASE, VP8`
 - `XXHASH32:  BASE`
 - `XXHASH64:  BASE`
 - `XZ:        BASE, CRC32, CRC64, LZMA, SHA256`
diff --git a/doc/std/image-decoders.md b/doc/std/image-decoders.md
index 6ec5de2..cc40a0d 100644
--- a/doc/std/image-decoders.md
+++ b/doc/std/image-decoders.md
@@ -131,6 +131,7 @@
 - [std/png](/std/png)
 - [std/tga](/std/tga)
 - [std/wbmp](/std/wbmp)
+- [std/webp](/std/webp)
 
 
 ## Examples
diff --git a/example/convert-to-nia/convert-to-nia.c b/example/convert-to-nia/convert-to-nia.c
index d439edd..d221253 100644
--- a/example/convert-to-nia/convert-to-nia.c
+++ b/example/convert-to-nia/convert-to-nia.c
@@ -67,7 +67,9 @@
 #define WUFFS_CONFIG__MODULE__NIE
 #define WUFFS_CONFIG__MODULE__PNG
 #define WUFFS_CONFIG__MODULE__TGA
+#define WUFFS_CONFIG__MODULE__VP8
 #define WUFFS_CONFIG__MODULE__WBMP
+#define WUFFS_CONFIG__MODULE__WEBP
 #define WUFFS_CONFIG__MODULE__ZLIB
 
 // Defining the WUFFS_CONFIG__DST_PIXEL_FORMAT__ENABLE_ALLOWLIST (and the
@@ -164,6 +166,7 @@
   wuffs_png__decoder png;
   wuffs_tga__decoder tga;
   wuffs_wbmp__decoder wbmp;
+  wuffs_webp__decoder webp;
 } g_potential_decoders;
 
 wuffs_crc32__ieee_hasher g_digest_hasher;
@@ -413,6 +416,16 @@
           wuffs_wbmp__decoder__upcast_as__wuffs_base__image_decoder(
               &g_potential_decoders.wbmp);
       return NULL;
+
+    case WUFFS_BASE__FOURCC__WEBP:
+      status = wuffs_webp__decoder__initialize(
+          &g_potential_decoders.webp, sizeof g_potential_decoders.webp,
+          WUFFS_VERSION, WUFFS_INITIALIZE__DEFAULT_OPTIONS);
+      TRY(wuffs_base__status__message(&status));
+      g_image_decoder =
+          wuffs_webp__decoder__upcast_as__wuffs_base__image_decoder(
+              &g_potential_decoders.webp);
+      return NULL;
   }
   return "main: unsupported file format";
 }
diff --git a/example/imageviewer/imageviewer.cc b/example/imageviewer/imageviewer.cc
index b2d3763..8ce1b1a 100644
--- a/example/imageviewer/imageviewer.cc
+++ b/example/imageviewer/imageviewer.cc
@@ -80,7 +80,9 @@
 #define WUFFS_CONFIG__MODULE__NIE
 #define WUFFS_CONFIG__MODULE__PNG
 #define WUFFS_CONFIG__MODULE__TGA
+#define WUFFS_CONFIG__MODULE__VP8
 #define WUFFS_CONFIG__MODULE__WBMP
+#define WUFFS_CONFIG__MODULE__WEBP
 #define WUFFS_CONFIG__MODULE__ZLIB
 
 // Defining the WUFFS_CONFIG__DST_PIXEL_FORMAT__ENABLE_ALLOWLIST (and the
diff --git a/example/sdl-imageviewer/sdl-imageviewer.cc b/example/sdl-imageviewer/sdl-imageviewer.cc
index ebbe81d..c82d495 100644
--- a/example/sdl-imageviewer/sdl-imageviewer.cc
+++ b/example/sdl-imageviewer/sdl-imageviewer.cc
@@ -78,7 +78,9 @@
 #define WUFFS_CONFIG__MODULE__NIE
 #define WUFFS_CONFIG__MODULE__PNG
 #define WUFFS_CONFIG__MODULE__TGA
+#define WUFFS_CONFIG__MODULE__VP8
 #define WUFFS_CONFIG__MODULE__WBMP
+#define WUFFS_CONFIG__MODULE__WEBP
 #define WUFFS_CONFIG__MODULE__ZLIB
 
 // Defining the WUFFS_CONFIG__DST_PIXEL_FORMAT__ENABLE_ALLOWLIST (and the
diff --git a/internal/cgen/auxiliary/image.cc b/internal/cgen/auxiliary/image.cc
index dd5e8e2..a075c50 100644
--- a/internal/cgen/auxiliary/image.cc
+++ b/internal/cgen/auxiliary/image.cc
@@ -100,6 +100,11 @@
     case WUFFS_BASE__FOURCC__WBMP:
       return wuffs_wbmp__decoder::alloc_as__wuffs_base__image_decoder();
 #endif
+
+#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__WEBP)
+    case WUFFS_BASE__FOURCC__WEBP:
+      return wuffs_webp__decoder::alloc_as__wuffs_base__image_decoder();
+#endif
   }
 
   return wuffs_base__image_decoder::unique_ptr(nullptr);
diff --git a/internal/cgen/auxiliary/image.hh b/internal/cgen/auxiliary/image.hh
index efe3d6c..9688183 100644
--- a/internal/cgen/auxiliary/image.hh
+++ b/internal/cgen/auxiliary/image.hh
@@ -91,6 +91,7 @@
   //  - WUFFS_BASE__FOURCC__PNG
   //  - WUFFS_BASE__FOURCC__TGA
   //  - WUFFS_BASE__FOURCC__WBMP
+  //  - WUFFS_BASE__FOURCC__WEBP
   virtual wuffs_base__image_decoder::unique_ptr  //
   SelectDecoder(uint32_t fourcc,
                 wuffs_base__slice_u8 prefix_data,
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index 241a2ae..97dbee0 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -13249,6 +13249,140 @@
 
 #endif  // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__TGA) || defined(WUFFS_NONMONOLITHIC)
 
+#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__VP8) || defined(WUFFS_NONMONOLITHIC)
+
+// ---------------- Status Codes
+
+// ---------------- Public Consts
+
+// ---------------- Struct Declarations
+
+typedef struct wuffs_vp8__placeholder__struct wuffs_vp8__placeholder;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// ---------------- Public Initializer Prototypes
+
+// For any given "wuffs_foo__bar* self", "wuffs_foo__bar__initialize(self,
+// etc)" should be called before any other "wuffs_foo__bar__xxx(self, etc)".
+//
+// Pass sizeof(*self) and WUFFS_VERSION for sizeof_star_self and wuffs_version.
+// 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,
+    size_t sizeof_star_self,
+    uint64_t wuffs_version,
+    uint32_t options);
+
+size_t
+sizeof__wuffs_vp8__placeholder(void);
+
+// ---------------- Allocs
+
+// These functions allocate and initialize Wuffs structs. They return NULL if
+// memory allocation fails. If they return non-NULL, there is no need to call
+// wuffs_foo__bar__initialize, but the caller is responsible for eventually
+// 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);
+
+// ---------------- Upcasts
+
+// ---------------- Public Function Prototypes
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+// ---------------- Struct Definitions
+
+// These structs' fields, and the sizeof them, are private implementation
+// details that aren't guaranteed to be stable across Wuffs versions.
+//
+// See https://en.wikipedia.org/wiki/Opaque_pointer#C
+
+#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION)
+
+struct wuffs_vp8__placeholder__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.
+  //
+  // It is a struct, not a struct*, so that the outermost wuffs_foo__bar struct
+  // can be stack allocated when WUFFS_IMPLEMENTATION is defined.
+
+  struct {
+    uint32_t magic;
+    uint32_t active_coroutine;
+    wuffs_base__vtable null_vtable;
+
+    uint32_t f_placeholder;
+  } private_impl;
+
+#ifdef __cplusplus
+#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR)
+  using unique_ptr = std::unique_ptr<wuffs_vp8__placeholder, 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());
+  }
+#endif  // defined(WUFFS_BASE__HAVE_UNIQUE_PTR)
+
+#if defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION)
+  // Disallow constructing or copying an object via standard C++ mechanisms,
+  // e.g. the "new" operator, as this struct is intentionally opaque. Its total
+  // size and field layout is not part of the public, stable, memory-safe API.
+  // Use malloc or memcpy and the sizeof__wuffs_foo__bar function instead, and
+  // call wuffs_foo__bar__baz methods (which all take a "this"-like pointer as
+  // their first argument) rather than tweaking bar.private_impl.qux fields.
+  //
+  // In C, we can just leave wuffs_foo__bar as an incomplete type (unless
+  // 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;
+#endif  // defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION)
+
+#if !defined(WUFFS_IMPLEMENTATION)
+  // As above, the size of the struct is not part of the public API, and unless
+  // WUFFS_IMPLEMENTATION is #define'd, this struct type T should be heap
+  // allocated, not stack allocated. Its size is not intended to be known at
+  // compile time, but it is unfortunately divulged as a side effect of
+  // defining C++ convenience methods. Use "sizeof__T()", calling the function,
+  // instead of "sizeof T", invoking the operator. To make the two values
+  // different, so that passing the latter will be rejected by the initialize
+  // function, we add an arbitrary amount of dead weight.
+  uint8_t dead_weight[123000000];  // 123 MB.
+#endif  // !defined(WUFFS_IMPLEMENTATION)
+
+  inline wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT
+  initialize(
+      size_t sizeof_star_self,
+      uint64_t wuffs_version,
+      uint32_t options) {
+    return wuffs_vp8__placeholder__initialize(
+        this, sizeof_star_self, wuffs_version, options);
+  }
+
+#endif  // __cplusplus
+};  // struct wuffs_vp8__placeholder__struct
+
+#endif  // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION)
+
+#endif  // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__VP8) || defined(WUFFS_NONMONOLITHIC)
+
 #if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__WBMP) || defined(WUFFS_NONMONOLITHIC)
 
 // ---------------- Status Codes
@@ -13604,6 +13738,345 @@
 
 #endif  // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__WBMP) || defined(WUFFS_NONMONOLITHIC)
 
+#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__WEBP) || defined(WUFFS_NONMONOLITHIC)
+
+// ---------------- Status Codes
+
+extern const char wuffs_webp__error__truncated_input[];
+
+// ---------------- Public Consts
+
+#define WUFFS_WEBP__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 0u
+
+// ---------------- Struct Declarations
+
+typedef struct wuffs_webp__decoder__struct wuffs_webp__decoder;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// ---------------- Public Initializer Prototypes
+
+// For any given "wuffs_foo__bar* self", "wuffs_foo__bar__initialize(self,
+// etc)" should be called before any other "wuffs_foo__bar__xxx(self, etc)".
+//
+// Pass sizeof(*self) and WUFFS_VERSION for sizeof_star_self and wuffs_version.
+// Pass 0 (or some combination of WUFFS_INITIALIZE__XXX) for options.
+
+wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT
+wuffs_webp__decoder__initialize(
+    wuffs_webp__decoder* self,
+    size_t sizeof_star_self,
+    uint64_t wuffs_version,
+    uint32_t options);
+
+size_t
+sizeof__wuffs_webp__decoder(void);
+
+// ---------------- Allocs
+
+// These functions allocate and initialize Wuffs structs. They return NULL if
+// memory allocation fails. If they return non-NULL, there is no need to call
+// wuffs_foo__bar__initialize, but the caller is responsible for eventually
+// calling free on the returned pointer. That pointer is effectively a C++
+// std::unique_ptr<T, wuffs_unique_ptr_deleter>.
+
+wuffs_webp__decoder*
+wuffs_webp__decoder__alloc(void);
+
+static inline wuffs_base__image_decoder*
+wuffs_webp__decoder__alloc_as__wuffs_base__image_decoder(void) {
+  return (wuffs_base__image_decoder*)(wuffs_webp__decoder__alloc());
+}
+
+// ---------------- Upcasts
+
+static inline wuffs_base__image_decoder*
+wuffs_webp__decoder__upcast_as__wuffs_base__image_decoder(
+    wuffs_webp__decoder* p) {
+  return (wuffs_base__image_decoder*)p;
+}
+
+// ---------------- Public Function Prototypes
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC uint64_t
+wuffs_webp__decoder__get_quirk(
+    const wuffs_webp__decoder* self,
+    uint32_t a_key);
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_webp__decoder__set_quirk(
+    wuffs_webp__decoder* self,
+    uint32_t a_key,
+    uint64_t a_value);
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_webp__decoder__decode_image_config(
+    wuffs_webp__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_webp__decoder__decode_frame_config(
+    wuffs_webp__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_webp__decoder__decode_frame(
+    wuffs_webp__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_webp__decoder__frame_dirty_rect(
+    const wuffs_webp__decoder* self);
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC uint32_t
+wuffs_webp__decoder__num_animation_loops(
+    const wuffs_webp__decoder* self);
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC uint64_t
+wuffs_webp__decoder__num_decoded_frame_configs(
+    const wuffs_webp__decoder* self);
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC uint64_t
+wuffs_webp__decoder__num_decoded_frames(
+    const wuffs_webp__decoder* self);
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_webp__decoder__restart_frame(
+    wuffs_webp__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_webp__decoder__set_report_metadata(
+    wuffs_webp__decoder* self,
+    uint32_t a_fourcc,
+    bool a_report);
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_webp__decoder__tell_me_more(
+    wuffs_webp__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_webp__decoder__workbuf_len(
+    const wuffs_webp__decoder* self);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+// ---------------- Struct Definitions
+
+// These structs' fields, and the sizeof them, are private implementation
+// details that aren't guaranteed to be stable across Wuffs versions.
+//
+// See https://en.wikipedia.org/wiki/Opaque_pointer#C
+
+#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION)
+
+struct wuffs_webp__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.
+  //
+  // It is a struct, not a struct*, so that the outermost wuffs_foo__bar struct
+  // can be stack allocated when WUFFS_IMPLEMENTATION is defined.
+
+  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_pixfmt;
+    uint32_t f_width;
+    uint32_t f_height;
+    uint8_t f_call_sequence;
+    uint64_t f_frame_config_io_position;
+    wuffs_base__pixel_swizzler f_swizzler;
+
+    uint32_t p_decode_image_config;
+    uint32_t p_decode_frame_config;
+    uint32_t p_do_decode_frame_config;
+    uint32_t p_decode_frame;
+  } private_impl;
+
+#ifdef __cplusplus
+#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR)
+  using unique_ptr = std::unique_ptr<wuffs_webp__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_webp__decoder__alloc());
+  }
+
+  static inline wuffs_base__image_decoder::unique_ptr
+  alloc_as__wuffs_base__image_decoder() {
+    return wuffs_base__image_decoder::unique_ptr(
+        wuffs_webp__decoder__alloc_as__wuffs_base__image_decoder());
+  }
+#endif  // defined(WUFFS_BASE__HAVE_UNIQUE_PTR)
+
+#if defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION)
+  // Disallow constructing or copying an object via standard C++ mechanisms,
+  // e.g. the "new" operator, as this struct is intentionally opaque. Its total
+  // size and field layout is not part of the public, stable, memory-safe API.
+  // Use malloc or memcpy and the sizeof__wuffs_foo__bar function instead, and
+  // call wuffs_foo__bar__baz methods (which all take a "this"-like pointer as
+  // their first argument) rather than tweaking bar.private_impl.qux fields.
+  //
+  // In C, we can just leave wuffs_foo__bar as an incomplete type (unless
+  // 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_webp__decoder__struct() = delete;
+  wuffs_webp__decoder__struct(const wuffs_webp__decoder__struct&) = delete;
+  wuffs_webp__decoder__struct& operator=(
+      const wuffs_webp__decoder__struct&) = delete;
+#endif  // defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION)
+
+#if !defined(WUFFS_IMPLEMENTATION)
+  // As above, the size of the struct is not part of the public API, and unless
+  // WUFFS_IMPLEMENTATION is #define'd, this struct type T should be heap
+  // allocated, not stack allocated. Its size is not intended to be known at
+  // compile time, but it is unfortunately divulged as a side effect of
+  // defining C++ convenience methods. Use "sizeof__T()", calling the function,
+  // instead of "sizeof T", invoking the operator. To make the two values
+  // different, so that passing the latter will be rejected by the initialize
+  // function, we add an arbitrary amount of dead weight.
+  uint8_t dead_weight[123000000];  // 123 MB.
+#endif  // !defined(WUFFS_IMPLEMENTATION)
+
+  inline wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT
+  initialize(
+      size_t sizeof_star_self,
+      uint64_t wuffs_version,
+      uint32_t options) {
+    return wuffs_webp__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_webp__decoder__get_quirk(this, a_key);
+  }
+
+  inline wuffs_base__status
+  set_quirk(
+      uint32_t a_key,
+      uint64_t a_value) {
+    return wuffs_webp__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_webp__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_webp__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_webp__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_webp__decoder__frame_dirty_rect(this);
+  }
+
+  inline uint32_t
+  num_animation_loops() const {
+    return wuffs_webp__decoder__num_animation_loops(this);
+  }
+
+  inline uint64_t
+  num_decoded_frame_configs() const {
+    return wuffs_webp__decoder__num_decoded_frame_configs(this);
+  }
+
+  inline uint64_t
+  num_decoded_frames() const {
+    return wuffs_webp__decoder__num_decoded_frames(this);
+  }
+
+  inline wuffs_base__status
+  restart_frame(
+      uint64_t a_index,
+      uint64_t a_io_position) {
+    return wuffs_webp__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_webp__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_webp__decoder__tell_me_more(this, a_dst, a_minfo, a_src);
+  }
+
+  inline wuffs_base__range_ii_u64
+  workbuf_len() const {
+    return wuffs_webp__decoder__workbuf_len(this);
+  }
+
+#endif  // __cplusplus
+};  // struct wuffs_webp__decoder__struct
+
+#endif  // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION)
+
+#endif  // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__WEBP) || defined(WUFFS_NONMONOLITHIC)
+
 #if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__XXHASH32) || defined(WUFFS_NONMONOLITHIC)
 
 // ---------------- Status Codes
@@ -14673,6 +15146,7 @@
   //  - WUFFS_BASE__FOURCC__PNG
   //  - WUFFS_BASE__FOURCC__TGA
   //  - WUFFS_BASE__FOURCC__WBMP
+  //  - WUFFS_BASE__FOURCC__WEBP
   virtual wuffs_base__image_decoder::unique_ptr  //
   SelectDecoder(uint32_t fourcc,
                 wuffs_base__slice_u8 prefix_data,
@@ -67680,6 +68154,87 @@
 
 #endif  // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__TGA)
 
+#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__VP8)
+
+// ---------------- Status Codes Implementations
+
+// ---------------- Private Consts
+
+// ---------------- Private Initializer Prototypes
+
+// ---------------- Private Function Prototypes
+
+// ---------------- VTables
+
+// ---------------- Initializer Implementations
+
+wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT
+wuffs_vp8__placeholder__initialize(
+    wuffs_vp8__placeholder* self,
+    size_t sizeof_star_self,
+    uint64_t wuffs_version,
+    uint32_t options){
+  if (!self) {
+    return wuffs_base__make_status(wuffs_base__error__bad_receiver);
+  }
+  if (sizeof(*self) != sizeof_star_self) {
+    return wuffs_base__make_status(wuffs_base__error__bad_sizeof_receiver);
+  }
+  if (((wuffs_version >> 32) != WUFFS_VERSION_MAJOR) ||
+      (((wuffs_version >> 16) & 0xFFFF) > WUFFS_VERSION_MINOR)) {
+    return wuffs_base__make_status(wuffs_base__error__bad_wuffs_version);
+  }
+
+  if ((options & WUFFS_INITIALIZE__ALREADY_ZEROED) != 0) {
+    // The whole point of this if-check is to detect an uninitialized *self.
+    // We disable the warning on GCC. Clang-5.0 does not have this warning.
+#if !defined(__clang__) && defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#endif
+    if (self->private_impl.magic != 0) {
+      return wuffs_base__make_status(wuffs_base__error__initialize_falsely_claimed_already_zeroed);
+    }
+#if !defined(__clang__) && defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+  } else {
+    if ((options & WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED) == 0) {
+      memset(self, 0, sizeof(*self));
+      options |= WUFFS_INITIALIZE__ALREADY_ZEROED;
+    } else {
+      memset(&(self->private_impl), 0, sizeof(self->private_impl));
+    }
+  }
+
+  self->private_impl.magic = WUFFS_BASE__MAGIC;
+  return wuffs_base__make_status(NULL);
+}
+
+wuffs_vp8__placeholder*
+wuffs_vp8__placeholder__alloc(void) {
+  wuffs_vp8__placeholder* x =
+      (wuffs_vp8__placeholder*)(calloc(sizeof(wuffs_vp8__placeholder), 1));
+  if (!x) {
+    return NULL;
+  }
+  if (wuffs_vp8__placeholder__initialize(
+      x, sizeof(wuffs_vp8__placeholder), WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED).repr) {
+    free(x);
+    return NULL;
+  }
+  return x;
+}
+
+size_t
+sizeof__wuffs_vp8__placeholder(void) {
+  return sizeof(wuffs_vp8__placeholder);
+}
+
+// ---------------- Function Implementations
+
+#endif  // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__VP8)
+
 #if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__WBMP)
 
 // ---------------- Status Codes Implementations
@@ -68602,6 +69157,720 @@
 
 #endif  // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__WBMP)
 
+#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__WEBP)
+
+// ---------------- Status Codes Implementations
+
+const char wuffs_webp__error__truncated_input[] = "#webp: truncated input";
+
+// ---------------- Private Consts
+
+// ---------------- Private Initializer Prototypes
+
+// ---------------- Private Function Prototypes
+
+WUFFS_BASE__GENERATED_C_CODE
+static wuffs_base__status
+wuffs_webp__decoder__do_decode_image_config(
+    wuffs_webp__decoder* self,
+    wuffs_base__image_config* a_dst,
+    wuffs_base__io_buffer* a_src);
+
+WUFFS_BASE__GENERATED_C_CODE
+static wuffs_base__status
+wuffs_webp__decoder__do_decode_frame_config(
+    wuffs_webp__decoder* self,
+    wuffs_base__frame_config* a_dst,
+    wuffs_base__io_buffer* a_src);
+
+WUFFS_BASE__GENERATED_C_CODE
+static wuffs_base__status
+wuffs_webp__decoder__do_decode_frame(
+    wuffs_webp__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);
+
+// ---------------- VTables
+
+const wuffs_base__image_decoder__func_ptrs
+wuffs_webp__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_webp__decoder__decode_frame),
+  (wuffs_base__status(*)(void*,
+      wuffs_base__frame_config*,
+      wuffs_base__io_buffer*))(&wuffs_webp__decoder__decode_frame_config),
+  (wuffs_base__status(*)(void*,
+      wuffs_base__image_config*,
+      wuffs_base__io_buffer*))(&wuffs_webp__decoder__decode_image_config),
+  (wuffs_base__rect_ie_u32(*)(const void*))(&wuffs_webp__decoder__frame_dirty_rect),
+  (uint64_t(*)(const void*,
+      uint32_t))(&wuffs_webp__decoder__get_quirk),
+  (uint32_t(*)(const void*))(&wuffs_webp__decoder__num_animation_loops),
+  (uint64_t(*)(const void*))(&wuffs_webp__decoder__num_decoded_frame_configs),
+  (uint64_t(*)(const void*))(&wuffs_webp__decoder__num_decoded_frames),
+  (wuffs_base__status(*)(void*,
+      uint64_t,
+      uint64_t))(&wuffs_webp__decoder__restart_frame),
+  (wuffs_base__status(*)(void*,
+      uint32_t,
+      uint64_t))(&wuffs_webp__decoder__set_quirk),
+  (wuffs_base__empty_struct(*)(void*,
+      uint32_t,
+      bool))(&wuffs_webp__decoder__set_report_metadata),
+  (wuffs_base__status(*)(void*,
+      wuffs_base__io_buffer*,
+      wuffs_base__more_information*,
+      wuffs_base__io_buffer*))(&wuffs_webp__decoder__tell_me_more),
+  (wuffs_base__range_ii_u64(*)(const void*))(&wuffs_webp__decoder__workbuf_len),
+};
+
+// ---------------- Initializer Implementations
+
+wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT
+wuffs_webp__decoder__initialize(
+    wuffs_webp__decoder* self,
+    size_t sizeof_star_self,
+    uint64_t wuffs_version,
+    uint32_t options){
+  if (!self) {
+    return wuffs_base__make_status(wuffs_base__error__bad_receiver);
+  }
+  if (sizeof(*self) != sizeof_star_self) {
+    return wuffs_base__make_status(wuffs_base__error__bad_sizeof_receiver);
+  }
+  if (((wuffs_version >> 32) != WUFFS_VERSION_MAJOR) ||
+      (((wuffs_version >> 16) & 0xFFFF) > WUFFS_VERSION_MINOR)) {
+    return wuffs_base__make_status(wuffs_base__error__bad_wuffs_version);
+  }
+
+  if ((options & WUFFS_INITIALIZE__ALREADY_ZEROED) != 0) {
+    // The whole point of this if-check is to detect an uninitialized *self.
+    // We disable the warning on GCC. Clang-5.0 does not have this warning.
+#if !defined(__clang__) && defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#endif
+    if (self->private_impl.magic != 0) {
+      return wuffs_base__make_status(wuffs_base__error__initialize_falsely_claimed_already_zeroed);
+    }
+#if !defined(__clang__) && defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+  } else {
+    if ((options & WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED) == 0) {
+      memset(self, 0, sizeof(*self));
+      options |= WUFFS_INITIALIZE__ALREADY_ZEROED;
+    } else {
+      memset(&(self->private_impl), 0, sizeof(self->private_impl));
+    }
+  }
+
+  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_webp__decoder__func_ptrs_for__wuffs_base__image_decoder);
+  return wuffs_base__make_status(NULL);
+}
+
+wuffs_webp__decoder*
+wuffs_webp__decoder__alloc(void) {
+  wuffs_webp__decoder* x =
+      (wuffs_webp__decoder*)(calloc(sizeof(wuffs_webp__decoder), 1));
+  if (!x) {
+    return NULL;
+  }
+  if (wuffs_webp__decoder__initialize(
+      x, sizeof(wuffs_webp__decoder), WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED).repr) {
+    free(x);
+    return NULL;
+  }
+  return x;
+}
+
+size_t
+sizeof__wuffs_webp__decoder(void) {
+  return sizeof(wuffs_webp__decoder);
+}
+
+// ---------------- Function Implementations
+
+// -------- func webp.decoder.get_quirk
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC uint64_t
+wuffs_webp__decoder__get_quirk(
+    const wuffs_webp__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 webp.decoder.set_quirk
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_webp__decoder__set_quirk(
+    wuffs_webp__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 webp.decoder.decode_image_config
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_webp__decoder__decode_image_config(
+    wuffs_webp__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_webp__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_webp__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 webp.decoder.do_decode_image_config
+
+WUFFS_BASE__GENERATED_C_CODE
+static wuffs_base__status
+wuffs_webp__decoder__do_decode_image_config(
+    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);
+
+  if (self->private_impl.f_call_sequence != 0u) {
+    status = wuffs_base__make_status(wuffs_base__error__bad_call_sequence);
+    goto exit;
+  }
+  self->private_impl.f_pixfmt = 2164295816u;
+  self->private_impl.f_width = 160u;
+  self->private_impl.f_height = 120u;
+  if (a_dst != NULL) {
+    wuffs_base__image_config__set(
+        a_dst,
+        self->private_impl.f_pixfmt,
+        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:
+  goto exit;
+  exit:
+  return status;
+}
+
+// -------- func webp.decoder.decode_frame_config
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_webp__decoder__decode_frame_config(
+    wuffs_webp__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_webp__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_webp__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 webp.decoder.do_decode_frame_config
+
+WUFFS_BASE__GENERATED_C_CODE
+static wuffs_base__status
+wuffs_webp__decoder__do_decode_frame_config(
+    wuffs_webp__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_webp__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 webp.decoder.decode_frame
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_webp__decoder__decode_frame(
+    wuffs_webp__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_webp__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_webp__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 webp.decoder.do_decode_frame
+
+WUFFS_BASE__GENERATED_C_CODE
+static wuffs_base__status
+wuffs_webp__decoder__do_decode_frame(
+    wuffs_webp__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);
+
+  self->private_impl.f_call_sequence = 96u;
+
+  goto ok;
+  ok:
+  goto exit;
+  exit:
+  return status;
+}
+
+// -------- func webp.decoder.frame_dirty_rect
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__rect_ie_u32
+wuffs_webp__decoder__frame_dirty_rect(
+    const wuffs_webp__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 webp.decoder.num_animation_loops
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC uint32_t
+wuffs_webp__decoder__num_animation_loops(
+    const wuffs_webp__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 webp.decoder.num_decoded_frame_configs
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC uint64_t
+wuffs_webp__decoder__num_decoded_frame_configs(
+    const wuffs_webp__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 webp.decoder.num_decoded_frames
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC uint64_t
+wuffs_webp__decoder__num_decoded_frames(
+    const wuffs_webp__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 webp.decoder.restart_frame
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_webp__decoder__restart_frame(
+    wuffs_webp__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 webp.decoder.set_report_metadata
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct
+wuffs_webp__decoder__set_report_metadata(
+    wuffs_webp__decoder* self,
+    uint32_t a_fourcc,
+    bool a_report) {
+  return wuffs_base__make_empty_struct();
+}
+
+// -------- func webp.decoder.tell_me_more
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_webp__decoder__tell_me_more(
+    wuffs_webp__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 webp.decoder.workbuf_len
+
+WUFFS_BASE__GENERATED_C_CODE
+WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64
+wuffs_webp__decoder__workbuf_len(
+    const wuffs_webp__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__WEBP)
+
 #if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__XXHASH32)
 
 // ---------------- Status Codes Implementations
@@ -72747,6 +74016,11 @@
     case WUFFS_BASE__FOURCC__WBMP:
       return wuffs_wbmp__decoder::alloc_as__wuffs_base__image_decoder();
 #endif
+
+#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__WEBP)
+    case WUFFS_BASE__FOURCC__WEBP:
+      return wuffs_webp__decoder::alloc_as__wuffs_base__image_decoder();
+#endif
   }
 
   return wuffs_base__image_decoder::unique_ptr(nullptr);
diff --git a/script/manual-test-truncated-input.cc b/script/manual-test-truncated-input.cc
index 6022490..842d98a 100644
--- a/script/manual-test-truncated-input.cc
+++ b/script/manual-test-truncated-input.cc
@@ -66,7 +66,9 @@
 #define WUFFS_CONFIG__MODULE__NIE
 #define WUFFS_CONFIG__MODULE__PNG
 #define WUFFS_CONFIG__MODULE__TGA
+#define WUFFS_CONFIG__MODULE__VP8
 #define WUFFS_CONFIG__MODULE__WBMP
+#define WUFFS_CONFIG__MODULE__WEBP
 #define WUFFS_CONFIG__MODULE__ZLIB
 
 // If building this program in an environment that doesn't easily accommodate
@@ -129,6 +131,9 @@
     case WUFFS_BASE__FOURCC__WBMP:
       dec = wuffs_wbmp__decoder::alloc_as__wuffs_base__image_decoder();
       break;
+    case WUFFS_BASE__FOURCC__WEBP:
+      dec = wuffs_webp__decoder::alloc_as__wuffs_base__image_decoder();
+      break;
     default:
       return unsupported_file_format;
   }
diff --git a/script/print-image-metadata.cc b/script/print-image-metadata.cc
index 03a9b6e..a9b34d8 100644
--- a/script/print-image-metadata.cc
+++ b/script/print-image-metadata.cc
@@ -54,7 +54,9 @@
 #define WUFFS_CONFIG__MODULE__NIE
 #define WUFFS_CONFIG__MODULE__PNG
 #define WUFFS_CONFIG__MODULE__TGA
+#define WUFFS_CONFIG__MODULE__VP8
 #define WUFFS_CONFIG__MODULE__WBMP
+#define WUFFS_CONFIG__MODULE__WEBP
 #define WUFFS_CONFIG__MODULE__ZLIB
 
 // If building this program in an environment that doesn't easily accommodate
@@ -391,6 +393,9 @@
       case WUFFS_BASE__FOURCC__WBMP:
         dec = wuffs_wbmp__decoder::alloc_as__wuffs_base__image_decoder();
         break;
+      case WUFFS_BASE__FOURCC__WEBP:
+        dec = wuffs_webp__decoder::alloc_as__wuffs_base__image_decoder();
+        break;
       default:
         return "main: unsupported file format";
     }
diff --git a/std/vp8/decode_vp8.wuffs b/std/vp8/decode_vp8.wuffs
new file mode 100644
index 0000000..98b6cf1
--- /dev/null
+++ b/std/vp8/decode_vp8.wuffs
@@ -0,0 +1,24 @@
+// Copyright 2024 The Wuffs Authors.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+//
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+// --------
+
+// 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 struct placeholder?(
+        placeholder : base.u32,
+)
diff --git a/std/webp/decode_webp.wuffs b/std/webp/decode_webp.wuffs
new file mode 100644
index 0000000..99481fc
--- /dev/null
+++ b/std/webp/decode_webp.wuffs
@@ -0,0 +1,183 @@
+// Copyright 2024 The Wuffs Authors.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+//
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+use "std/vp8"
+
+pub status "#truncated input"
+
+pub const DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE : base.u64 = 0
+
+pub struct decoder? implements base.image_decoder(
+        pixfmt : base.u32,
+        width  : base.u32[..= 0xFF_FFFF],
+        height : base.u32[..= 0xFF_FFFF],
+
+        // 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,
+
+        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
+    } endwhile
+}
+
+pri func decoder.do_decode_image_config?(dst: nptr base.image_config, src: base.io_reader) {
+    if this.call_sequence <> 0x00 {
+        return base."#bad call sequence"
+    }
+
+    this.pixfmt = base.PIXEL_FORMAT__BGRA_NONPREMUL
+    this.width = 160
+    this.height = 120
+
+    if args.dst <> nullptr {
+        args.dst.set!(
+                pixfmt: this.pixfmt,
+                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
+    } endwhile
+}
+
+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
+    } endwhile
+}
+
+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) {
+    this.call_sequence = 0x60
+}
+
+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.
+}
+
+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/test/3pdata/nia-checksums-of-blinksuite.txt b/test/3pdata/nia-checksums-of-blinksuite.txt
index 6b7e9fa..b9d79f3 100644
--- a/test/3pdata/nia-checksums-of-blinksuite.txt
+++ b/test/3pdata/nia-checksums-of-blinksuite.txt
@@ -30,6 +30,7 @@
 OK. 267741e0 test/3pdata/blinksuite/blue-100.png
 OK. 89449c01 test/3pdata/blinksuite/blue-wheel-srgb-color-profile.jpg
 OK. 8fce70a9 test/3pdata/blinksuite/blue-wheel-srgb-color-profile.png
+OK. 2ef5fd5d test/3pdata/blinksuite/blue-wheel-srgb-color-profile.webp
 OK. 2d7bad9f test/3pdata/blinksuite/border-image-srgb-color-profile.png
 OK. 9ca73f21 test/3pdata/blinksuite/border-image.png
 OK. 277600f9 test/3pdata/blinksuite/boston.gif
@@ -42,6 +43,8 @@
 OK. b3389858 test/3pdata/blinksuite/color-checker-srgb-color-profile.png
 OK. 10721531 test/3pdata/blinksuite/count-down-color-test.gif
 OK. 1abc4dc6 test/3pdata/blinksuite/count-down-color-test.png
+OK. 2ef5fd5d test/3pdata/blinksuite/count-down-color-test.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/crbug.364830.webp
 OK. 24ab1efd test/3pdata/blinksuite/crbug779261.gif
 OK. ae034abe test/3pdata/blinksuite/crbug807324.png
 OK. 4f50fdb0 test/3pdata/blinksuite/crbug827754.png
@@ -66,6 +69,7 @@
 OK. a2a940bc test/3pdata/blinksuite/cs-uma-ycck.jpg
 OK. b6000be6 test/3pdata/blinksuite/dice.png
 BAD 1023c20a test/3pdata/blinksuite/empty-frame.png
+OK. 2ef5fd5d test/3pdata/blinksuite/flip.webp
 OK. eff36075 test/3pdata/blinksuite/flowchart.jpg
 OK. 20ff1df8 test/3pdata/blinksuite/full2loop.gif
 OK. dc814e29 test/3pdata/blinksuite/gif-loop-count.gif
@@ -89,8 +93,14 @@
 OK. 93f21c2a test/3pdata/blinksuite/icc-v2-gbr-420-width-not-whole-mcu.jpg
 OK. f37cea44 test/3pdata/blinksuite/icc-v2-gbr-422-whole-mcus.jpg
 OK. 1de5a906 test/3pdata/blinksuite/icc-v2-gbr.jpg
+OK. 2ef5fd5d test/3pdata/blinksuite/invalid-animated-webp.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/invalid-animated-webp2.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/invalid-animated-webp3.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/invalid-animated-webp4.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/invalid_vp8_vp8x.webp
 OK. 0da5f37e test/3pdata/blinksuite/jpeg-height-exif-orientation.jpg
 OK. 4409dca6 test/3pdata/blinksuite/large-gif-checkerboard.gif
+OK. 2ef5fd5d 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
@@ -161,6 +171,7 @@
 OK. 6ed3c2e7 test/3pdata/blinksuite/rgb-jpeg-with-adobe-marker-only.jpg
 OK. cfb6f39d test/3pdata/blinksuite/rgb-png-with-cmyk-color-profile.png
 OK. a3454fb7 test/3pdata/blinksuite/short-app-extension-string.gif
+OK. 2ef5fd5d test/3pdata/blinksuite/size-failure.b186640109.webp
 BAD 2398258b test/3pdata/blinksuite/size-failure.gif
 OK. 5c4b1a8b test/3pdata/blinksuite/small-square-with-colorspin-profile.jpg
 OK. 44dc95b9 test/3pdata/blinksuite/sprite_all_fragments_same.png
@@ -168,6 +179,25 @@
 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. 2ef5fd5d test/3pdata/blinksuite/test.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/test2.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/test3.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/truncated.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/truncated2.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/webp-animated-icc-xmp.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/webp-animated-large.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/webp-animated-no-blend.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/webp-animated-opaque.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/webp-animated-semitransparent1.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/webp-animated-semitransparent2.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/webp-animated-semitransparent3.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/webp-animated-semitransparent4.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/webp-animated.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/webp-color-no-profile-lossy.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/webp-color-profile-crash.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/webp-color-profile-lossless.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/webp-color-profile-lossy-alpha.webp
+OK. 2ef5fd5d test/3pdata/blinksuite/webp-color-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/c/std/vp8.c b/test/c/std/vp8.c
new file mode 100644
index 0000000..6d4eeb9
--- /dev/null
+++ b/test/c/std/vp8.c
@@ -0,0 +1,122 @@
+// Copyright 2024 The Wuffs Authors.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+//
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+// ----------------
+
+/*
+This test program is typically run indirectly, by the "wuffs test" or "wuffs
+bench" commands. These commands take an optional "-mimic" flag to check that
+Wuffs' output mimics (i.e. exactly matches) other libraries' output, such as
+giflib for GIF, libpng for PNG, etc.
+
+To manually run this test:
+
+for CC in clang gcc; do
+  $CC -std=c99 -Wall -Werror vp8.c && ./a.out
+  rm -f a.out
+done
+
+Each edition should print "PASS", amongst other information, and exit(0).
+
+Add the "wuffs mimic cflags" (everything after the colon below) to the C
+compiler flags (after the .c file) to run the mimic tests.
+
+To manually run the benchmarks, replace "-Wall -Werror" with "-O3" and replace
+the first "./a.out" with "./a.out -bench". Combine these changes with the
+"wuffs mimic cflags" to run the mimic benchmarks.
+*/
+
+// ¿ wuffs mimic cflags: -DWUFFS_MIMIC
+
+// Wuffs ships as a "single file C library" or "header file library" as per
+// https://github.com/nothings/stb/blob/master/docs/stb_howto.txt
+//
+// To use that single file as a "foo.c"-like implementation, instead of a
+// "foo.h"-like header, #define WUFFS_IMPLEMENTATION before #include'ing or
+// compiling it.
+#define WUFFS_IMPLEMENTATION
+
+// Defining the WUFFS_CONFIG__MODULE* macros are optional, but it lets users of
+// release/c/etc.c choose which parts of Wuffs to build. That file contains the
+// entire Wuffs standard library, implementing a variety of codecs and file
+// formats. Without this macro definition, an optimizing compiler or linker may
+// very well discard Wuffs code for unused codecs, but listing the Wuffs
+// modules we use makes that process explicit. Preprocessing means that such
+// code simply isn't compiled.
+#define WUFFS_CONFIG__MODULES
+#define WUFFS_CONFIG__MODULE__BASE
+#define WUFFS_CONFIG__MODULE__VP8
+
+// If building this program in an environment that doesn't easily accommodate
+// relative includes, you can use the script/inline-c-relative-includes.go
+// program to generate a stand-alone C file.
+#include "../../../release/c/wuffs-unsupported-snapshot.c"
+#include "../testlib/testlib.c"
+#ifdef WUFFS_MIMIC
+// No mimic library.
+#endif
+
+// ---------------- VP8 Tests
+
+// No VP8 tests.
+
+// ---------------- Mimic Tests
+
+#ifdef WUFFS_MIMIC
+
+// No mimic tests.
+
+#endif  // WUFFS_MIMIC
+
+// ---------------- VP8 Benches
+
+// No VP8 benches.
+
+// ---------------- Mimic Benches
+
+#ifdef WUFFS_MIMIC
+
+// No mimic benches.
+
+#endif  // WUFFS_MIMIC
+
+// ---------------- Manifest
+
+proc g_tests[] = {
+
+// No VP8 tests.
+
+#ifdef WUFFS_MIMIC
+
+// No mimic tests.
+
+#endif  // WUFFS_MIMIC
+
+    NULL,
+};
+
+proc g_benches[] = {
+
+// No VP8 benches.
+
+#ifdef WUFFS_MIMIC
+
+// No mimic benches.
+
+#endif  // WUFFS_MIMIC
+
+    NULL,
+};
+
+int  //
+main(int argc, char** argv) {
+  g_proc_package_name = "std/vp8";
+  return test_main(argc, argv, g_tests, g_benches);
+}
diff --git a/test/c/std/webp.c b/test/c/std/webp.c
new file mode 100644
index 0000000..6af503b
--- /dev/null
+++ b/test/c/std/webp.c
@@ -0,0 +1,154 @@
+// Copyright 2024 The Wuffs Authors.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+//
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+// ----------------
+
+/*
+This test program is typically run indirectly, by the "wuffs test" or "wuffs
+bench" commands. These commands take an optional "-mimic" flag to check that
+Wuffs' output mimics (i.e. exactly matches) other libraries' output, such as
+giflib for GIF, libpng for PNG, etc.
+
+To manually run this test:
+
+for CC in clang gcc; do
+  $CC -std=c99 -Wall -Werror webp.c && ./a.out
+  rm -f a.out
+done
+
+Each edition should print "PASS", amongst other information, and exit(0).
+
+Add the "wuffs mimic cflags" (everything after the colon below) to the C
+compiler flags (after the .c file) to run the mimic tests.
+
+To manually run the benchmarks, replace "-Wall -Werror" with "-O3" and replace
+the first "./a.out" with "./a.out -bench". Combine these changes with the
+"wuffs mimic cflags" to run the mimic benchmarks.
+*/
+
+// ¿ wuffs mimic cflags: -DWUFFS_MIMIC
+
+// Wuffs ships as a "single file C library" or "header file library" as per
+// https://github.com/nothings/stb/blob/master/docs/stb_howto.txt
+//
+// To use that single file as a "foo.c"-like implementation, instead of a
+// "foo.h"-like header, #define WUFFS_IMPLEMENTATION before #include'ing or
+// compiling it.
+#define WUFFS_IMPLEMENTATION
+
+// Defining the WUFFS_CONFIG__MODULE* macros are optional, but it lets users of
+// release/c/etc.c choose which parts of Wuffs to build. That file contains the
+// entire Wuffs standard library, implementing a variety of codecs and file
+// formats. Without this macro definition, an optimizing compiler or linker may
+// very well discard Wuffs code for unused codecs, but listing the Wuffs
+// modules we use makes that process explicit. Preprocessing means that such
+// code simply isn't compiled.
+#define WUFFS_CONFIG__MODULES
+#define WUFFS_CONFIG__MODULE__BASE
+#define WUFFS_CONFIG__MODULE__VP8
+#define WUFFS_CONFIG__MODULE__WEBP
+
+// If building this program in an environment that doesn't easily accommodate
+// relative includes, you can use the script/inline-c-relative-includes.go
+// program to generate a stand-alone C file.
+#include "../../../release/c/wuffs-unsupported-snapshot.c"
+#include "../testlib/testlib.c"
+#ifdef WUFFS_MIMIC
+// No mimic library.
+#endif
+
+// ---------------- WebP Tests
+
+const char*  //
+wuffs_webp_decode(uint64_t* n_bytes_out,
+                  wuffs_base__io_buffer* dst,
+                  uint32_t wuffs_initialize_flags,
+                  wuffs_base__pixel_format pixfmt,
+                  uint32_t* quirks_ptr,
+                  size_t quirks_len,
+                  wuffs_base__io_buffer* src) {
+  wuffs_webp__decoder dec;
+  CHECK_STATUS("initialize",
+               wuffs_webp__decoder__initialize(&dec, sizeof dec, WUFFS_VERSION,
+                                               wuffs_initialize_flags));
+  return do_run__wuffs_base__image_decoder(
+      wuffs_webp__decoder__upcast_as__wuffs_base__image_decoder(&dec),
+      n_bytes_out, dst, pixfmt, quirks_ptr, quirks_len, src);
+}
+
+// --------
+
+const char*  //
+test_wuffs_webp_decode_interface() {
+  CHECK_FOCUS(__func__);
+  wuffs_webp__decoder dec;
+  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.lossless.webp", 0, SIZE_MAX, 160, 120,
+      0x00000000);
+}
+
+// ---------------- Mimic Tests
+
+#ifdef WUFFS_MIMIC
+
+// No mimic tests.
+
+#endif  // WUFFS_MIMIC
+
+// ---------------- WebP Benches
+
+// No WebP benches.
+
+// ---------------- Mimic Benches
+
+#ifdef WUFFS_MIMIC
+
+// No mimic benches.
+
+#endif  // WUFFS_MIMIC
+
+// ---------------- Manifest
+
+proc g_tests[] = {
+
+    test_wuffs_webp_decode_interface,
+
+#ifdef WUFFS_MIMIC
+
+// No mimic tests.
+
+#endif  // WUFFS_MIMIC
+
+    NULL,
+};
+
+proc g_benches[] = {
+
+// No WebP benches.
+
+#ifdef WUFFS_MIMIC
+
+// No mimic benches.
+
+#endif  // WUFFS_MIMIC
+
+    NULL,
+};
+
+int  //
+main(int argc, char** argv) {
+  g_proc_package_name = "std/webp";
+  return test_main(argc, argv, g_tests, g_benches);
+}
diff --git a/test/nia-checksums-of-data.txt b/test/nia-checksums-of-data.txt
index 6bcd316..c013a49 100644
--- a/test/nia-checksums-of-data.txt
+++ b/test/nia-checksums-of-data.txt
@@ -26,20 +26,26 @@
 OK. e08a7cc8 test/data/artificial-png/key-value-pairs.png
 OK. 076cb375 test/data/bricks-color.bmp
 OK. 72a1f9cc test/data/bricks-color.jpeg
+OK. 2ef5fd5d test/data/bricks-color.lossless.webp
+OK. 2ef5fd5d test/data/bricks-color.lossy.webp
 OK. 076cb375 test/data/bricks-color.png
 OK. 076cb375 test/data/bricks-color.tga
 OK. f36c2e80 test/data/bricks-dither.bmp
 OK. f36c2e80 test/data/bricks-dither.gif
+OK. 2ef5fd5d test/data/bricks-dither.lossless.webp
 OK. f36c2e80 test/data/bricks-dither.no-ancillary.png
 OK. f36c2e80 test/data/bricks-dither.png
 OK. c2bce675 test/data/bricks-gray.bmp
 OK. c2bce675 test/data/bricks-gray.gif
 OK. 3a2478ad test/data/bricks-gray.jpeg
+OK. 2ef5fd5d test/data/bricks-gray.lossless.webp
+OK. 2ef5fd5d test/data/bricks-gray.lossy.webp
 OK. c2bce675 test/data/bricks-gray.no-ancillary.png
 OK. c2bce675 test/data/bricks-gray.png
 OK. c2bce675 test/data/bricks-gray.tga
 OK. 7aebfe6c test/data/bricks-nodither.bmp
 OK. 7aebfe6c test/data/bricks-nodither.gif
+OK. 2ef5fd5d test/data/bricks-nodither.lossless.webp
 OK. 7aebfe6c test/data/bricks-nodither.png
 OK. 7aebfe6c test/data/bricks-nodither.tga
 OK. ae2e547d test/data/bricks-nodither.wbmp
@@ -50,19 +56,27 @@
 OK. 030f5a48 test/data/harvesters.bmp
 OK. c18b3d5a test/data/harvesters.gif
 OK. f217df74 test/data/harvesters.jpeg
+OK. 2ef5fd5d test/data/harvesters.lossless.webp
+OK. 2ef5fd5d test/data/harvesters.lossy.webp
 OK. 030f5a48 test/data/harvesters.png
 OK. e776c90f test/data/hat.bmp
 OK. 6dcba6a4 test/data/hat.gif
 OK. 2298f3ca test/data/hat.jpeg
+OK. 2ef5fd5d test/data/hat.lossless.webp
+OK. 2ef5fd5d test/data/hat.lossy.webp
 OK. e776c90f test/data/hat.png
 OK. d30bfe5d test/data/hat.wbmp
 OK. 33a44f22 test/data/hibiscus.primitive.bmp
 OK. 25e212b3 test/data/hibiscus.primitive.gif
 OK. 9624fa44 test/data/hibiscus.primitive.jpeg
+OK. 2ef5fd5d test/data/hibiscus.primitive.lossless.webp
+OK. 2ef5fd5d test/data/hibiscus.primitive.lossy.webp
 OK. 33a44f22 test/data/hibiscus.primitive.png
 OK. 60040742 test/data/hibiscus.regular.bmp
 OK. b727da8b test/data/hibiscus.regular.gif
 OK. 41e39405 test/data/hibiscus.regular.jpeg
+OK. 2ef5fd5d test/data/hibiscus.regular.lossless.webp
+OK. 2ef5fd5d test/data/hibiscus.regular.lossy.webp
 OK. 60040742 test/data/hibiscus.regular.png
 OK. dcbb225a test/data/hippopotamus.bmp
 OK. ed4b78fc test/data/hippopotamus.interlaced.gif
@@ -70,6 +84,8 @@
 BAD c3c4bd65 test/data/hippopotamus.interlaced.truncated.gif
 BAD 7c6a771b test/data/hippopotamus.interlaced.truncated.png
 OK. 96bdbbb3 test/data/hippopotamus.jpeg
+OK. 2ef5fd5d test/data/hippopotamus.lossless.webp
+OK. 2ef5fd5d test/data/hippopotamus.lossy.webp
 OK. d3bbed27 test/data/hippopotamus.masked-with-muybridge.gif
 OK. 7e6acf01 test/data/hippopotamus.masked-with-muybridge.png
 OK. dcbb225a test/data/hippopotamus.nie
@@ -114,6 +130,8 @@
 OK. bf7e8c96 test/data/pjw-thumbnail.bmp
 OK. bf7e8c96 test/data/pjw-thumbnail.gif
 OK. 7c67a37f test/data/pjw-thumbnail.jpeg
+OK. 2ef5fd5d test/data/pjw-thumbnail.lossless.webp
+OK. 2ef5fd5d test/data/pjw-thumbnail.lossy.webp
 OK. bf7e8c96 test/data/pjw-thumbnail.png
 OK. 38cb4cbf test/data/red-blue-gradient.dcip3d65-no-chrm-no-gama.png
 OK. 38cb4cbf test/data/red-blue-gradient.gamma1dot0.png