example/std-imagedumper: add background-color flag
diff --git a/example/stb-imagedumper/stb-imagedumper.c b/example/stb-imagedumper/stb-imagedumper.c
index 440df43..0e0b381 100644
--- a/example/stb-imagedumper/stb-imagedumper.c
+++ b/example/stb-imagedumper/stb-imagedumper.c
@@ -156,11 +156,12 @@
 // ----
 //
 // This program uses the STB API, not the wuffs_aux C++ API. We only ever pass
-// 1 (STBI_grey) or 3 (STBI_rgb), not 0, 2 or 4, as the desired_channels
-// argument to stbi_load_etc functions, so we only need ALLOW_Y and ALLOW_RGB.
+// 1 (STBI_grey) or 4 (STBI_rgb_alpha), not 0, 2 or 3, as the desired_channels
+// argument to stbi_load_etc functions, so we only need ALLOW_Y and
+// ALLOW_RGBA_NONPREMUL.
 #define WUFFS_CONFIG__DST_PIXEL_FORMAT__ENABLE_ALLOWLIST
 #define WUFFS_CONFIG__DST_PIXEL_FORMAT__ALLOW_Y
-#define WUFFS_CONFIG__DST_PIXEL_FORMAT__ALLOW_RGB
+#define WUFFS_CONFIG__DST_PIXEL_FORMAT__ALLOW_RGBA_NONPREMUL
 
 // Defining this enables Wuffs' reimplementation of the STB library's API.
 #define WUFFS_CONFIG__ENABLE_DROP_IN_REPLACEMENT__STB
@@ -170,6 +171,10 @@
 // program to generate a stand-alone C file.
 #include "../../release/c/wuffs-unsupported-snapshot.c"
 
+#define PARSE_COLOR_CONFIG__STATIC_FUNCTIONS
+#define PARSE_COLOR_IMPLEMENTATION
+#include "../../snippet/parse-color.c"
+
 #endif  // defined(USE_THE_REAL_STBI)
 
 // ----------------
@@ -625,6 +630,10 @@
   bool demo;
   bool natural_size;
   int resize;
+
+  // background_color is in 0xAABBGGRR order, the same as STB (assuming
+  // little-endian).
+  uint32_t background_color;
 } g_flags = {0};
 
 static const char* g_usage =
@@ -634,6 +643,7 @@
     "    -a or -ascii-art (use ASCII art instead of ANSI color codes)\n"
     "    -B or -braille-art-dark-mode (use white-on-black Braille art)\n"
     "    -b or -braille-art-light-mode (use black-on-white Braille art)\n"
+    "    -c=orange or -background-color=ff9800 (set a background color)\n"
     "    -demo (dump the built-in demonstration images)\n"
     "    -n or -natural-size (use each image's natural size); this\n"
     "        overrides -resize=N\n"
@@ -684,6 +694,18 @@
       g_flags.natural_size = true;
       continue;
     }
+    if (!strncmp(arg, "c=", 2) || !strncmp(arg, "background-color=", 17)) {
+      while (*arg++ != '=') {
+      }
+      int64_t c = parse_color(arg, strlen(arg));
+      if (c < 0) {
+        return g_usage;
+      }
+      g_flags.background_color = wuffs_base__color_swap_u32_argb_abgr(
+          wuffs_base__color_u32_argb_nonpremul__as__color_u32_argb_premul(
+              (uint32_t)(c)));
+      continue;
+    }
     if (!strncmp(arg, "resize=", 7)) {
       while (*arg++ != '=') {
       }
@@ -753,7 +775,7 @@
   if (!inout_w || !inout_h || (dst_wh <= 0) || (0x4000 < dst_wh) ||  //
       (*inout_w <= 0) || (0xffffff < *inout_w) ||                    //
       (*inout_h <= 0) || (0xffffff < *inout_h) ||                    //
-      ((bytes_per_pixel != 1) && (bytes_per_pixel != 3))) {
+      ((bytes_per_pixel != 1) && (bytes_per_pixel != 4))) {
     printf("main: resize failed: invalid argument\n");
     return NULL;
   }
@@ -791,6 +813,7 @@
       uint64_t tot0 = 0;
       uint64_t tot1 = 0;
       uint64_t tot2 = 0;
+      uint64_t tot3 = 0;
       uint64_t totw = 0;
 
       uint64_t ty = dy * sh;
@@ -811,10 +834,11 @@
             size_t i = (((size_t)sy * (size_t)sw) + (size_t)sx) * 1;
             tot0 += weight * src_pixels[i + 0];
           } else {
-            size_t i = (((size_t)sy * (size_t)sw) + (size_t)sx) * 3;
+            size_t i = (((size_t)sy * (size_t)sw) + (size_t)sx) * 4;
             tot0 += weight * src_pixels[i + 0];
             tot1 += weight * src_pixels[i + 1];
             tot2 += weight * src_pixels[i + 2];
+            tot3 += weight * src_pixels[i + 3];
           }
         }
       }
@@ -825,6 +849,7 @@
         *dst++ = (unsigned char)(((tot0 * 2) + totw) / (totw * 2));
         *dst++ = (unsigned char)(((tot1 * 2) + totw) / (totw * 2));
         *dst++ = (unsigned char)(((tot2 * 2) + totw) / (totw * 2));
+        *dst++ = (unsigned char)(((tot3 * 2) + totw) / (totw * 2));
       }
     }
   }
@@ -835,6 +860,55 @@
   return dst_pixels;
 }
 
+static inline uint32_t  //
+composite_premul_nonpremul_u32_axxx(uint32_t dst_premul,
+                                    uint32_t src_nonpremul) {
+  // Extract 16-bit color components.
+  uint32_t da = 0x101 * (0xFF & (dst_premul >> 24));
+  uint32_t dr = 0x101 * (0xFF & (dst_premul >> 16));
+  uint32_t dg = 0x101 * (0xFF & (dst_premul >> 8));
+  uint32_t db = 0x101 * (0xFF & (dst_premul >> 0));
+  uint32_t sa = 0x101 * (0xFF & (src_nonpremul >> 24));
+  uint32_t sr = 0x101 * (0xFF & (src_nonpremul >> 16));
+  uint32_t sg = 0x101 * (0xFF & (src_nonpremul >> 8));
+  uint32_t sb = 0x101 * (0xFF & (src_nonpremul >> 0));
+
+  // Calculate the inverse of the src-alpha: how much of the dst to keep.
+  uint32_t ia = 0xFFFF - sa;
+
+  // Composite src (nonpremul) over dst (premul).
+  da = sa + ((da * ia) / 0xFFFF);
+  dr = ((sr * sa) + (dr * ia)) / 0xFFFF;
+  dg = ((sg * sa) + (dg * ia)) / 0xFFFF;
+  db = ((sb * sa) + (db * ia)) / 0xFFFF;
+
+  // Convert from 16-bit color to 8-bit color.
+  da >>= 8;
+  dr >>= 8;
+  dg >>= 8;
+  db >>= 8;
+
+  // Combine components.
+  return (db << 0) | (dg << 8) | (dr << 16) | (da << 24);
+}
+
+static void  //
+apply_background_color(uint32_t bg_color_premul,
+                       unsigned char* pixels,
+                       int w,
+                       int h) {
+  for (int64_t n = ((int64_t)(w)) * ((int64_t)(h)); n--;) {
+    uint32_t fg_color_nonpremul =
+        wuffs_base__peek_u32le__no_bounds_check(pixels);
+    if ((fg_color_nonpremul >> 24) != 0xFFu) {
+      wuffs_base__poke_u32le__no_bounds_check(
+          pixels, composite_premul_nonpremul_u32_axxx(bg_color_premul,
+                                                      fg_color_nonpremul));
+    }
+    pixels += 4;
+  }
+}
+
 // ----------------
 
 #define MAX_INCL_DIMENSION 10000
@@ -855,8 +929,9 @@
   int channels_in_file = 0;
   int bytes_per_pixel = (g_flags.ascii_art || g_flags.braille_art_dark_mode ||
                          g_flags.braille_art_light_mode)
-                            ? STBI_grey  // 1.
-                            : STBI_rgb;  // 3.
+                            ? STBI_grey        // 1.
+                            : STBI_rgb_alpha;  // 4.
+
   unsigned char* src_pixels = NULL;
 
 #if defined(WUFFS_EXAMPLE_USE_TIMERS)
@@ -975,7 +1050,8 @@
       fwrite(buffer, sizeof(char), dst - buffer, stdout);
     }
 
-  } else {
+  } else if (bytes_per_pixel == 4) {
+    apply_background_color(g_flags.background_color, pixels, w, h);
     unsigned char* src = pixels;
     for (int y = 0; y < h; y += 2) {
       char* dst = buffer;
@@ -990,21 +1066,21 @@
           dst += sprintf(dst, "\x1B[38;2;%d;%d;%dm\xE2\x96\x80",  //
                          upper0, upper1, upper2);
         } else {
-          uint8_t lower0 = (uint8_t)src[(w * 3) + 0];
-          uint8_t lower1 = (uint8_t)src[(w * 3) + 1];
-          uint8_t lower2 = (uint8_t)src[(w * 3) + 2];
+          uint8_t lower0 = (uint8_t)src[(w * bytes_per_pixel) + 0];
+          uint8_t lower1 = (uint8_t)src[(w * bytes_per_pixel) + 1];
+          uint8_t lower2 = (uint8_t)src[(w * bytes_per_pixel) + 2];
           dst +=
               sprintf(dst,
                       "\x1B[38;2;%d;%d;%dm\x1B[48;2;%d;%d;%dm\xE2\x96\x80",  //
                       upper0, upper1, upper2, lower0, lower1, lower2);
         }
-        src += 3;
+        src += bytes_per_pixel;
       }
       memcpy(dst, "\x1B[0m\n", 5);
       dst += 5;
       fwrite(buffer, sizeof(char), dst - buffer, stdout);
       if ((y + 1) < h) {
-        src += w * 3;
+        src += w * bytes_per_pixel;
       }
     }
   }
diff --git a/snippet/parse-color.c b/snippet/parse-color.c
new file mode 100644
index 0000000..4e9d9d1
--- /dev/null
+++ b/snippet/parse-color.c
@@ -0,0 +1,195 @@
+// Copyright 2026 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 is a small, self-contained, single-file C library to parse an RGB or
+// ARGB color as hex (like "b0279c") or a well-known name (like "purple").
+//
+// To use this file as a "foo.c"-like implementation, instead of a "foo.h"-like
+// header, #define PARSE_COLOR_IMPLEMENTATION before #include'ing or compiling
+// it.
+//
+// As an option, you may also #define PARSE_COLOR_CONFIG__STATIC_FUNCTIONS to
+// make these functions have static storage. This can help the compiler ignore
+// or discard unused code, which can produce faster compiles and smaller
+// binaries.
+
+#ifndef PARSE_COLOR_INCLUDE_GUARD
+#define PARSE_COLOR_INCLUDE_GUARD
+
+#if defined(PARSE_COLOR_CONFIG__STATIC_FUNCTIONS)
+#define PARSE_COLOR__MAYBE_STATIC static
+#else
+#define PARSE_COLOR__MAYBE_STATIC
+#endif
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+// parse_color parses a string that's a hex color (like "#ff8" or "ffb0279c")
+// or a well-known name (like "black", "purple" or "darkred"). Well-known names
+// are an ad-hoc set based on the Material Design color names, optionally
+// prefixed by "dark" or "light".
+//
+// On success, it returns a non-negative integer (that fits into a uint32_t,
+// but cast as an int64_t) that is 0xAARRGGBB, using non-premultiplied alpha.
+//
+// On failure, it returns a negative int64_t.
+//
+// Parsing is case-insensitive. This function is thread-safe.
+PARSE_COLOR__MAYBE_STATIC int64_t  //
+parse_color(const char* s_ptr, size_t s_len);
+
+// --------
+
+#ifdef PARSE_COLOR_IMPLEMENTATION
+
+PARSE_COLOR__MAYBE_STATIC int64_t  //
+parse_color(const char* s_ptr, size_t s_len) {
+  if ((s_len <= 0) || (16 <= s_len)) {
+    return -1;
+  }
+
+  bool starts_with_hash = (*s_ptr == '#');
+  if (starts_with_hash) {
+    s_ptr++;
+    s_len--;
+    if (s_len <= 0) {
+      return -1;
+    }
+  }
+
+  // Canonicalize ASCII to lower case.
+  char buf[16];
+  for (size_t i = 0; i < s_len; i++) {
+    if (((int8_t)(s_ptr[i])) < 0) {
+      return -1;
+    }
+    buf[i] = s_ptr[i] | 0x20;
+  }
+
+  // Map well known names to Material Design "500 shade" colors.
+  if (!starts_with_hash) {
+    const char* b_ptr = &buf[0];
+    size_t b_len = s_len;
+    int adjustment = 0;
+    if ((b_len > 4) && !memcmp(buf, "dark", 4)) {
+      adjustment = 1;
+      b_ptr += 4;
+      b_len -= 4;
+    } else if ((b_len > 5) && !memcmp(buf, "light", 5)) {
+      adjustment = 2;
+      b_ptr += 5;
+      b_len -= 5;
+    }
+
+    static const int num_offsets = 18;
+    static const uint32_t offsets[18] = {
+        0x00000000u,  //
+        0x05000000u,  // black
+        0x0AFFFFFFu,  // white
+        0x0DF44336u,  // red
+        0x11E91E63u,  // pink
+        0x179C27B0u,  // purple
+        0x1D3F51B5u,  // indigo
+        0x212196F3u,  // blue
+        0x2500BCD4u,  // cyan
+        0x29009688u,  // teal
+        0x2E4CAF50u,  // green
+        0x32CDDC39u,  // lime
+        0x38FFEB3Bu,  // yellow
+        0x3DFFC107u,  // amber
+        0x43FF9800u,  // orange
+        0x48795548u,  // brown
+        0x4C9E9E9Eu,  // gray
+        0x509E9E9Eu,  // grey
+    };
+
+    static const char names[] =
+        "blackwhiteredpinkpurpleindigobluecyantealgreenlimeyellowamberorangebro"
+        "wngraygrey";
+
+    for (int i = 1; i < num_offsets; i++) {
+      size_t name_len = (offsets[i] >> 24) - (offsets[i - 1] >> 24);
+      if (name_len != b_len) {
+        continue;
+      }
+      const char* p = b_ptr;
+      const char* p_end = &buf[s_len];
+      const char* q = &names[offsets[i - 1] >> 24];
+
+      while (1) {
+        if (p == p_end) {
+          uint32_t r = 0xFFu & (offsets[i] >> 16);
+          uint32_t g = 0xFFu & (offsets[i] >> 8);
+          uint32_t b = 0xFFu & (offsets[i] >> 0);
+          if (adjustment == 1) {
+            r /= 2;
+            g /= 2;
+            b /= 2;
+          } else if (adjustment == 2) {
+            r = 0xFFu - ((0xFFu - r) / 2);
+            g = 0xFFu - ((0xFFu - g) / 2);
+            b = 0xFFu - ((0xFFu - b) / 2);
+          }
+          return ((int64_t)(0xFF000000u | (r << 16) | (g << 8) | b));
+
+        } else if (*p++ != *q++) {
+          break;
+        }
+      }
+    }
+  }
+
+  uint32_t ret = 0u;
+  uint32_t multiplier = 0u;
+  int shift = 0;
+
+  if (s_len == 3) {
+    ret = 0xFFu;
+    multiplier = 0x11u;
+    shift = 8;
+  } else if (s_len == 4) {
+    ret = 0u;
+    multiplier = 0x11u;
+    shift = 8;
+  } else if (s_len == 6) {
+    ret = 0xFFu;
+    multiplier = 0x01u;
+    shift = 4;
+  } else if (s_len == 8) {
+    ret = 0u;
+    multiplier = 0x01u;
+    shift = 4;
+  } else {
+    return -1;
+  }
+
+  for (size_t i = 0; i < s_len; i++) {
+    char c = buf[i];
+    if (('0' <= c) && (c <= '9')) {
+      c -= '0';
+    } else if (('a' <= c) && (c <= 'f')) {
+      c -= 'a' - 10;
+    } else {
+      return -1;
+    }
+    ret <<= shift;
+    ret |= multiplier * ((uint32_t)(c));
+  }
+
+  return ((int64_t)(ret));
+}
+
+#endif  // PARSE_COLOR_IMPLEMENTATION
+#endif  // PARSE_COLOR_INCLUDE_GUARD