Add std/bmp benchmark
diff --git a/test/c/std/bmp.c b/test/c/std/bmp.c
index 1e20e2f..9e7d2ed 100644
--- a/test/c/std/bmp.c
+++ b/test/c/std/bmp.c
@@ -70,6 +70,23 @@
 // ---------------- BMP Tests
 
 const char*  //
+wuffs_bmp_decode(uint64_t* n_bytes_out,
+                 wuffs_base__io_buffer* dst,
+                 uint32_t wuffs_initialize_flags,
+                 wuffs_base__pixel_format pixfmt,
+                 wuffs_base__io_buffer* src) {
+  wuffs_bmp__decoder dec;
+  CHECK_STATUS("initialize",
+               wuffs_bmp__decoder__initialize(&dec, sizeof dec, WUFFS_VERSION,
+                                              wuffs_initialize_flags));
+  return do_run__wuffs_base__image_decoder(
+      wuffs_bmp__decoder__upcast_as__wuffs_base__image_decoder(&dec),
+      n_bytes_out, dst, pixfmt, src);
+}
+
+// --------
+
+const char*  //
 test_wuffs_bmp_decode_interface() {
   CHECK_FOCUS(__func__);
   wuffs_bmp__decoder dec;
@@ -172,9 +189,16 @@
 
 #endif  // WUFFS_MIMIC
 
-  // ---------------- BMP Benches
+// ---------------- BMP Benches
 
-  // No BMP benches.
+const char*  //
+bench_wuffs_bmp_decode_40k() {
+  CHECK_FOCUS(__func__);
+  return do_bench_image_decode(
+      &wuffs_bmp_decode, WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED,
+      wuffs_base__make_pixel_format(WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL),
+      "test/data/hat.bmp", 0, SIZE_MAX, 1000);
+}
 
   // ---------------- Mimic Benches
 
@@ -203,7 +227,7 @@
 
 proc g_benches[] = {
 
-// No BMP benches.
+    bench_wuffs_bmp_decode_40k,
 
 #ifdef WUFFS_MIMIC
 
diff --git a/test/c/testlib/testlib.c b/test/c/testlib/testlib.c
index 657e44a..d7cd760 100644
--- a/test/c/testlib/testlib.c
+++ b/test/c/testlib/testlib.c
@@ -946,6 +946,98 @@
 // --------
 
 const char*  //
+do_run__wuffs_base__image_decoder(wuffs_base__image_decoder* b,
+                                  uint64_t* n_bytes_out,
+                                  wuffs_base__io_buffer* dst,
+                                  wuffs_base__pixel_format pixfmt,
+                                  wuffs_base__io_buffer* src) {
+  wuffs_base__image_config ic = ((wuffs_base__image_config){});
+  wuffs_base__frame_config fc = ((wuffs_base__frame_config){});
+  wuffs_base__pixel_buffer pb = ((wuffs_base__pixel_buffer){});
+
+  uint32_t bits_per_pixel = wuffs_base__pixel_format__bits_per_pixel(&pixfmt);
+  if (bits_per_pixel == 0) {
+    return "do_run__wuffs_base__image_decoder: invalid bits_per_pixel";
+  } else if ((bits_per_pixel % 8) != 0) {
+    return "do_run__wuffs_base__image_decoder: cannot bench fractional bytes";
+  }
+  uint64_t bytes_per_pixel = bits_per_pixel / 8;
+
+  CHECK_STATUS("decode_image_config",
+               wuffs_base__image_decoder__decode_image_config(b, &ic, src));
+  wuffs_base__pixel_config__set(&ic.pixcfg, pixfmt.repr,
+                                WUFFS_BASE__PIXEL_SUBSAMPLING__NONE,
+                                wuffs_base__pixel_config__width(&ic.pixcfg),
+                                wuffs_base__pixel_config__height(&ic.pixcfg));
+  CHECK_STATUS("set_from_slice", wuffs_base__pixel_buffer__set_from_slice(
+                                     &pb, &ic.pixcfg, g_pixel_slice_u8));
+
+  while (true) {
+    wuffs_base__status status =
+        wuffs_base__image_decoder__decode_frame_config(b, &fc, src);
+    if (status.repr == wuffs_base__note__end_of_data) {
+      break;
+    } else {
+      CHECK_STATUS("decode_frame_config", status);
+    }
+    wuffs_base__pixel_blend blend =
+        ((wuffs_base__frame_config__index(&fc) == 0) ||
+         wuffs_base__frame_config__overwrite_instead_of_blend(&fc) ||
+         wuffs_base__pixel_format__is_indexed(&pixfmt))
+            ? WUFFS_BASE__PIXEL_BLEND__SRC
+            : WUFFS_BASE__PIXEL_BLEND__SRC_OVER;
+
+    CHECK_STATUS("decode_frame",
+                 wuffs_base__image_decoder__decode_frame(
+                     b, &pb, src, blend, g_work_slice_u8, NULL));
+
+    if (n_bytes_out) {
+      uint64_t frame_width = wuffs_base__frame_config__width(&fc);
+      uint64_t frame_height = wuffs_base__frame_config__height(&fc);
+      *n_bytes_out += frame_width * frame_height * bytes_per_pixel;
+    }
+    if (dst) {
+      CHECK_STRING(copy_to_io_buffer_from_pixel_buffer(
+          dst, &pb, wuffs_base__frame_config__bounds(&fc)));
+    }
+  }
+  return NULL;
+}
+
+const char*  //
+do_bench_image_decode(
+    const char* (*decode_func)(uint64_t* n_bytes_out,
+                               wuffs_base__io_buffer* dst,
+                               uint32_t wuffs_initialize_flags,
+                               wuffs_base__pixel_format pixfmt,
+                               wuffs_base__io_buffer* src),
+    uint32_t wuffs_initialize_flags,
+    wuffs_base__pixel_format pixfmt,
+    const char* src_filename,
+    size_t src_ri,
+    size_t src_wi,
+    uint64_t iters_unscaled) {
+  wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
+      .data = g_src_slice_u8,
+  });
+  CHECK_STRING(read_file_fragment(&src, src_filename, src_ri, src_wi));
+
+  bench_start();
+  uint64_t n_bytes = 0;
+  uint64_t i;
+  uint64_t iters = iters_unscaled * g_flags.iterscale;
+  for (i = 0; i < iters; i++) {
+    src.meta.ri = src_ri;
+    CHECK_STRING(
+        (*decode_func)(&n_bytes, NULL, wuffs_initialize_flags, pixfmt, &src));
+  }
+  bench_finish(iters, n_bytes);
+  return NULL;
+}
+
+// --------
+
+const char*  //
 do_test__wuffs_base__hasher_u32(wuffs_base__hasher_u32* b,
                                 const char* src_filename,
                                 size_t src_ri,