Merge pull request #68 from MichaelMcDonnell/fix-rust-gif-bench

Fix Rust GIF benchmark
diff --git a/doc/changelog.md b/doc/changelog.md
index 56747a3..54fab21 100644
--- a/doc/changelog.md
+++ b/doc/changelog.md
@@ -27,6 +27,7 @@
 - Added `std/json`.
 - Added `std/nie`.
 - Added `std/png`.
+- Added `std/tga`.
 - Added `std/wbmp`.
 - Added `tell_me_more?` mechanism.
 - Added SIMD.
diff --git a/doc/spec/nie-spec.md b/doc/spec/nie-spec.md
index ffce34b..b6b0978 100644
--- a/doc/spec/nie-spec.md
+++ b/doc/spec/nie-spec.md
@@ -353,7 +353,7 @@
 
 The recommended filename extensions are `.nie`, `.nii` and `.nia`.
 
-The recommended MIME types are `image/nie`, `image/nii` and `image/nia`.
+The recommended MIME types are `image/x-nie`, `image/x-nii` and `image/x-nia`.
 
 
 ## Why NIE and not NIF (for Naïve Image Format)?
@@ -370,4 +370,4 @@
 
 ---
 
-Updated on November 2021.
+Updated on January 2022.
diff --git a/example/convert-to-nia/convert-to-nia.c b/example/convert-to-nia/convert-to-nia.c
index 76eeb3c..da9ddd9 100644
--- a/example/convert-to-nia/convert-to-nia.c
+++ b/example/convert-to-nia/convert-to-nia.c
@@ -66,6 +66,7 @@
 #define WUFFS_CONFIG__MODULE__LZW
 #define WUFFS_CONFIG__MODULE__NIE
 #define WUFFS_CONFIG__MODULE__PNG
+#define WUFFS_CONFIG__MODULE__TGA
 #define WUFFS_CONFIG__MODULE__WBMP
 #define WUFFS_CONFIG__MODULE__ZLIB
 
@@ -138,6 +139,7 @@
   wuffs_gif__decoder gif;
   wuffs_nie__decoder nie;
   wuffs_png__decoder png;
+  wuffs_tga__decoder tga;
   wuffs_wbmp__decoder wbmp;
 } g_potential_decoders;
 
@@ -246,7 +248,7 @@
   g_fourcc = 0;
   while (true) {
     g_fourcc = wuffs_base__magic_number_guess_fourcc(
-        wuffs_base__io_buffer__reader_slice(&g_src));
+        wuffs_base__io_buffer__reader_slice(&g_src), g_src.meta.closed);
     if ((g_fourcc >= 0) ||
         (wuffs_base__io_buffer__reader_length(&g_src) == g_src.data.len)) {
       break;
@@ -300,6 +302,16 @@
               &g_potential_decoders.png);
       return NULL;
 
+    case WUFFS_BASE__FOURCC__TGA:
+      status = wuffs_tga__decoder__initialize(
+          &g_potential_decoders.tga, sizeof g_potential_decoders.tga,
+          WUFFS_VERSION, WUFFS_INITIALIZE__DEFAULT_OPTIONS);
+      TRY(wuffs_base__status__message(&status));
+      g_image_decoder =
+          wuffs_tga__decoder__upcast_as__wuffs_base__image_decoder(
+              &g_potential_decoders.tga);
+      return NULL;
+
     case WUFFS_BASE__FOURCC__WBMP:
       status = wuffs_wbmp__decoder__initialize(
           &g_potential_decoders.wbmp, sizeof g_potential_decoders.wbmp,
diff --git a/example/imageviewer/imageviewer.cc b/example/imageviewer/imageviewer.cc
index 9693845..9e33474 100644
--- a/example/imageviewer/imageviewer.cc
+++ b/example/imageviewer/imageviewer.cc
@@ -80,6 +80,7 @@
 #define WUFFS_CONFIG__MODULE__LZW
 #define WUFFS_CONFIG__MODULE__NIE
 #define WUFFS_CONFIG__MODULE__PNG
+#define WUFFS_CONFIG__MODULE__TGA
 #define WUFFS_CONFIG__MODULE__WBMP
 #define WUFFS_CONFIG__MODULE__ZLIB
 
diff --git a/fuzz/c/std/seed_corpora.txt b/fuzz/c/std/seed_corpora.txt
index 01e1767..01fa62f 100644
--- a/fuzz/c/std/seed_corpora.txt
+++ b/fuzz/c/std/seed_corpora.txt
@@ -20,6 +20,7 @@
 gzip:   test/data/*.gz
 json:   test/data/*.json  ../rapidjson_corpus/*  ../simdjson_corpus/*  ../JSONTestSuite/test_*/*.json
 png:    test/data/*.png   test/data/artificial-png/*.png  ../pngsuite_corpus/*.png
+tga:    test/data/*.tga
 wbmp:   test/data/*.wbmp
 zlib:   test/data/*.zlib
 
diff --git a/fuzz/c/std/tga_fuzzer.c b/fuzz/c/std/tga_fuzzer.c
new file mode 100644
index 0000000..b2803c5
--- /dev/null
+++ b/fuzz/c/std/tga_fuzzer.c
@@ -0,0 +1,88 @@
+// Copyright 2020 The Wuffs Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// ----------------
+
+// Silence the nested slash-star warning for the next comment's command line.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wcomment"
+
+/*
+This fuzzer (the fuzz function) is typically run indirectly, by a framework
+such as https://github.com/google/oss-fuzz calling LLVMFuzzerTestOneInput.
+
+When working on the fuzz implementation, or as a coherence check, defining
+WUFFS_CONFIG__FUZZLIB_MAIN will let you manually run fuzz over a set of files:
+
+gcc -DWUFFS_CONFIG__FUZZLIB_MAIN tga_fuzzer.c
+./a.out ../../../test/data/*.tga
+rm -f ./a.out
+
+It should print "PASS", amongst other information, and exit(0).
+*/
+
+#pragma clang diagnostic pop
+
+// 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
+
+#if defined(WUFFS_CONFIG__FUZZLIB_MAIN)
+// Defining the WUFFS_CONFIG__STATIC_FUNCTIONS macro is optional, but when
+// combined with WUFFS_IMPLEMENTATION, it demonstrates making all of Wuffs'
+// functions have static storage.
+//
+// This can help the compiler ignore or discard unused code, which can produce
+// faster compiles and smaller binaries. Other motivations are discussed in the
+// "ALLOW STATIC IMPLEMENTATION" section of
+// https://raw.githubusercontent.com/nothings/stb/master/docs/stb_howto.txt
+#define WUFFS_CONFIG__STATIC_FUNCTIONS
+#endif  // defined(WUFFS_CONFIG__FUZZLIB_MAIN)
+
+// 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__TGA
+
+// 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 "../fuzzlib/fuzzlib.c"
+#include "../fuzzlib/fuzzlib_image_decoder.c"
+
+const char*  //
+fuzz(wuffs_base__io_buffer* src, uint64_t hash) {
+  wuffs_tga__decoder dec;
+  wuffs_base__status status = wuffs_tga__decoder__initialize(
+      &dec, sizeof dec, WUFFS_VERSION,
+      (hash & 1) ? WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED : 0);
+  hash = wuffs_base__u64__rotate_right(hash, 1);
+  if (!wuffs_base__status__is_ok(&status)) {
+    return wuffs_base__status__message(&status);
+  }
+  return fuzz_image_decoder(
+      src, hash,
+      wuffs_tga__decoder__upcast_as__wuffs_base__image_decoder(&dec));
+}
diff --git a/internal/cgen/auxiliary/image.cc b/internal/cgen/auxiliary/image.cc
index 312ec81..1c58016 100644
--- a/internal/cgen/auxiliary/image.cc
+++ b/internal/cgen/auxiliary/image.cc
@@ -58,7 +58,8 @@
 
 wuffs_base__image_decoder::unique_ptr  //
 DecodeImageCallbacks::SelectDecoder(uint32_t fourcc,
-                                    wuffs_base__slice_u8 prefix) {
+                                    wuffs_base__slice_u8 prefix_data,
+                                    bool prefix_closed) {
   switch (fourcc) {
 #if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BMP)
     case WUFFS_BASE__FOURCC__BMP:
@@ -84,6 +85,11 @@
     }
 #endif
 
+#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__TGA)
+    case WUFFS_BASE__FOURCC__TGA:
+      return wuffs_tga__decoder::alloc_as__wuffs_base__image_decoder();
+#endif
+
 #if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__WBMP)
     case WUFFS_BASE__FOURCC__WBMP:
       return wuffs_wbmp__decoder::alloc_as__wuffs_base__image_decoder();
@@ -311,10 +317,17 @@
     // Determine the image format.
     if (!redirected) {
       while (true) {
-        fourcc = wuffs_base__magic_number_guess_fourcc(io_buf.reader_slice());
+        fourcc = wuffs_base__magic_number_guess_fourcc(io_buf.reader_slice(),
+                                                       io_buf.meta.closed);
         if (fourcc > 0) {
           break;
         } else if ((fourcc == 0) && (io_buf.reader_length() >= 64)) {
+          // Having (fourcc == 0) means that Wuffs' built in MIME sniffer
+          // didn't recognize the image format. Nonetheless, custom callbacks
+          // may still be able to do their own MIME sniffing, for exotic image
+          // types. We try to give them at least 64 bytes of prefix data when
+          // one-shot-calling callbacks.SelectDecoder. There is no mechanism
+          // for the callbacks to request a longer prefix.
           break;
         } else if (io_buf.meta.closed || (io_buf.writer_length() == 0)) {
           fourcc = 0;
@@ -355,8 +368,7 @@
 
     // Select the image decoder.
     image_decoder = callbacks.SelectDecoder(
-        (uint32_t)fourcc,
-        fourcc ? wuffs_base__empty_slice_u8() : io_buf.reader_slice());
+        (uint32_t)fourcc, io_buf.reader_slice(), io_buf.meta.closed);
     if (!image_decoder) {
       return DecodeImageResult(DecodeImage_UnsupportedImageFormat);
     }
diff --git a/internal/cgen/auxiliary/image.hh b/internal/cgen/auxiliary/image.hh
index 96919a5..ff1812e 100644
--- a/internal/cgen/auxiliary/image.hh
+++ b/internal/cgen/auxiliary/image.hh
@@ -70,9 +70,13 @@
   // Returning a nullptr means failure (DecodeImage_UnsupportedImageFormat).
   //
   // Common formats will have a FourCC value in the range [1 ..= 0x7FFF_FFFF],
-  // such as WUFFS_BASE__FOURCC__JPEG. A zero FourCC value means that the
-  // caller is responsible for examining the opening bytes (a prefix) of the
-  // input data. SelectDecoder implementations should not modify those bytes.
+  // such as WUFFS_BASE__FOURCC__JPEG. A zero FourCC value means that Wuffs'
+  // standard library did not recognize the image format but if SelectDecoder
+  // was overridden, it may examine the input data's starting bytes and still
+  // provide its own image decoder, e.g. for an exotic image file format that's
+  // not in Wuffs' standard library. The prefix_etc fields have the same
+  // meaning as wuffs_base__magic_number_guess_fourcc arguments. SelectDecoder
+  // implementations should not modify prefix_data's contents.
   //
   // SelectDecoder might be called more than once, since some image file
   // formats can wrap others. For example, a nominal BMP file can actually
@@ -87,9 +91,12 @@
   //  - WUFFS_BASE__FOURCC__GIF
   //  - WUFFS_BASE__FOURCC__NIE
   //  - WUFFS_BASE__FOURCC__PNG
+  //  - WUFFS_BASE__FOURCC__TGA
   //  - WUFFS_BASE__FOURCC__WBMP
   virtual wuffs_base__image_decoder::unique_ptr  //
-  SelectDecoder(uint32_t fourcc, wuffs_base__slice_u8 prefix);
+  SelectDecoder(uint32_t fourcc,
+                wuffs_base__slice_u8 prefix_data,
+                bool prefix_closed);
 
   // HandleMetadata acknowledges image metadata. minfo.flavor will be one of:
   //  - WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_RAW_PASSTHROUGH
diff --git a/internal/cgen/base/fundamental-public.h b/internal/cgen/base/fundamental-public.h
index 43ead3f..b4af4de 100644
--- a/internal/cgen/base/fundamental-public.h
+++ b/internal/cgen/base/fundamental-public.h
@@ -1418,13 +1418,19 @@
 // ---------------- Magic Numbers
 
 // wuffs_base__magic_number_guess_fourcc guesses the file format of some data,
-// given its opening bytes. It returns a positive FourCC value on success.
+// given its starting bytes (the prefix_data argument) and whether or not there
+// may be further bytes (the prefix_closed argument; true means that
+// prefix_data is the entire data).
+//
+// It returns a positive FourCC value on success.
 //
 // It returns zero if nothing matches its hard-coded list of 'magic numbers'.
 //
-// It returns a negative value if a longer prefix is required for a conclusive
-// result. For example, seeing a single 'B' byte is not enough to discriminate
-// the BMP and BPG image file formats.
+// It returns a negative value if prefix_closed is false and a longer prefix is
+// required for a conclusive result. For example, a single 'B' byte (without
+// further data) is not enough to discriminate the BMP and BPG image file
+// formats. Similarly, a single '\xFF' byte might be the start of JPEG data or
+// it might be the start of some other binary data.
 //
 // It does not do a full validity check. Like any guess made from a short
 // prefix of the data, it may return false positives. Data that starts with 99
@@ -1439,4 +1445,5 @@
 // function requires the WUFFS_CONFIG__MODULE__BASE__MAGIC sub-module, not just
 // WUFFS_CONFIG__MODULE__BASE__CORE.
 WUFFS_BASE__MAYBE_STATIC int32_t  //
-wuffs_base__magic_number_guess_fourcc(wuffs_base__slice_u8 prefix);
+wuffs_base__magic_number_guess_fourcc(wuffs_base__slice_u8 prefix_data,
+                                      bool prefix_closed);
diff --git a/internal/cgen/base/magic-submodule.c b/internal/cgen/base/magic-submodule.c
index 4ca558e..f70ab93 100644
--- a/internal/cgen/base/magic-submodule.c
+++ b/internal/cgen/base/magic-submodule.c
@@ -14,8 +14,127 @@
 
 // ---------------- Magic Numbers
 
+// ICO doesn't start with a magic identifier. Instead, see if the opening bytes
+// are plausibly ICO.
+//
+// Callers should have already verified that (prefix_data.len >= 2) and the
+// first two bytes are 0x00.
+//
+// See:
+//  - https://docs.fileformat.com/image/ico/
+static int32_t  //
+wuffs_base__magic_number_guess_fourcc__maybe_ico(
+    wuffs_base__slice_u8 prefix_data,
+    bool prefix_closed) {
+  // Allow-list for the Image Type field.
+  if (prefix_data.len < 4) {
+    return prefix_closed ? 0 : -1;
+  } else if (prefix_data.ptr[3] != 0) {
+    return 0;
+  }
+  switch (prefix_data.ptr[2]) {
+    case 0x01:  // ICO
+    case 0x02:  // CUR
+      break;
+    default:
+      return 0;
+  }
+
+  // The Number Of Images should be positive.
+  if (prefix_data.len < 6) {
+    return prefix_closed ? 0 : -1;
+  } else if ((prefix_data.ptr[4] == 0) && (prefix_data.ptr[5] == 0)) {
+    return 0;
+  }
+
+  // The first ICONDIRENTRY's fourth byte should be zero.
+  if (prefix_data.len < 10) {
+    return prefix_closed ? 0 : -1;
+  } else if (prefix_data.ptr[9] != 0) {
+    return 0;
+  }
+
+  // TODO: have a separate FourCC for CUR?
+  return 0x49434F20;  // 'ICO 'be
+}
+
+// TGA doesn't start with a magic identifier. Instead, see if the opening bytes
+// are plausibly TGA.
+//
+// Callers should have already verified that (prefix_data.len >= 2) and the
+// second byte (prefix_data.ptr[1], the Color Map Type byte), is either 0x00 or
+// 0x01.
+//
+// See:
+//  - https://docs.fileformat.com/image/tga/
+//  - https://www.dca.fee.unicamp.br/~martino/disciplinas/ea978/tgaffs.pdf
+static int32_t  //
+wuffs_base__magic_number_guess_fourcc__maybe_tga(
+    wuffs_base__slice_u8 prefix_data,
+    bool prefix_closed) {
+  // Allow-list for the Image Type field.
+  if (prefix_data.len < 3) {
+    return prefix_closed ? 0 : -1;
+  }
+  switch (prefix_data.ptr[2]) {
+    case 0x01:
+    case 0x02:
+    case 0x03:
+    case 0x09:
+    case 0x0A:
+    case 0x0B:
+      break;
+    default:
+      // TODO: 0x20 and 0x21 are invalid, according to the spec, but are
+      // apparently unofficial extensions.
+      return 0;
+  }
+
+  // Allow-list for the Color Map Entry Size field (if the Color Map Type field
+  // is non-zero) or else all the Color Map fields should be zero.
+  if (prefix_data.len < 8) {
+    return prefix_closed ? 0 : -1;
+  } else if (prefix_data.ptr[1] != 0x00) {
+    switch (prefix_data.ptr[7]) {
+      case 0x0F:
+      case 0x10:
+      case 0x18:
+      case 0x20:
+        break;
+      default:
+        return 0;
+    }
+  } else if ((prefix_data.ptr[3] | prefix_data.ptr[4] | prefix_data.ptr[5] |
+              prefix_data.ptr[6] | prefix_data.ptr[7]) != 0x00) {
+    return 0;
+  }
+
+  // Allow-list for the Pixel Depth field.
+  if (prefix_data.len < 17) {
+    return prefix_closed ? 0 : -1;
+  }
+  switch (prefix_data.ptr[16]) {
+    case 0x01:
+    case 0x08:
+    case 0x0F:
+    case 0x10:
+    case 0x18:
+    case 0x20:
+      break;
+    default:
+      return 0;
+  }
+
+  return 0x54474120;  // 'TGA 'be
+}
+
 WUFFS_BASE__MAYBE_STATIC int32_t  //
-wuffs_base__magic_number_guess_fourcc(wuffs_base__slice_u8 prefix) {
+wuffs_base__magic_number_guess_fourcc(wuffs_base__slice_u8 prefix_data,
+                                      bool prefix_closed) {
+  // This is similar to (but different from):
+  //  - the magic/Magdir tables under https://github.com/file/file
+  //  - the MIME Sniffing algorithm at https://mimesniff.spec.whatwg.org/
+
   // table holds the 'magic numbers' (which are actually variable length
   // strings). The strings may contain NUL bytes, so the "const char* magic"
   // value starts with the length-minus-1 of the 'magic number'.
@@ -42,10 +161,10 @@
   };
   static const size_t table_len = sizeof(table) / sizeof(table[0]);
 
-  if (prefix.len == 0) {
-    return -1;
+  if (prefix_data.len == 0) {
+    return prefix_closed ? 0 : -1;
   }
-  uint8_t pre_first_byte = prefix.ptr[0];
+  uint8_t pre_first_byte = prefix_data.ptr[0];
 
   int32_t fourcc = 0;
   size_t i;
@@ -64,11 +183,11 @@
     }
 
     const char* mag_remaining_ptr = table[i].magic + 2;
-    uint8_t* pre_remaining_ptr = prefix.ptr + 1;
-    size_t pre_remaining_len = prefix.len - 1;
+    uint8_t* pre_remaining_ptr = prefix_data.ptr + 1;
+    size_t pre_remaining_len = prefix_data.len - 1;
     if (pre_remaining_len < mag_remaining_len) {
       if (!memcmp(pre_remaining_ptr, mag_remaining_ptr, pre_remaining_len)) {
-        return -1;
+        return prefix_closed ? 0 : -1;
       }
     } else {
       if (!memcmp(pre_remaining_ptr, mag_remaining_ptr, mag_remaining_len)) {
@@ -76,6 +195,14 @@
       }
     }
   }
+
+  if (prefix_data.len < 2) {
+    return prefix_closed ? 0 : -1;
+  } else if ((prefix_data.ptr[1] == 0x00) || (prefix_data.ptr[1] == 0x01)) {
+    return wuffs_base__magic_number_guess_fourcc__maybe_tga(prefix_data,
+                                                            prefix_closed);
+  }
+
   return 0;
 
 match:
@@ -84,26 +211,38 @@
     fourcc = -fourcc;
 
     if (fourcc == 0x52494646) {  // 'RIFF'be
-      if (prefix.len < 12) {
-        return -1;
+      if (prefix_data.len < 12) {
+        return prefix_closed ? 0 : -1;
       }
-      uint32_t x = wuffs_base__peek_u32be__no_bounds_check(prefix.ptr + 8);
+      uint32_t x = wuffs_base__peek_u32be__no_bounds_check(prefix_data.ptr + 8);
       if (x == 0x57454250) {  // 'WEBP'be
         return 0x57454250;    // 'WEBP'be
       }
 
     } else if (fourcc == 0x30302020) {  // '00  'be
       // Binary data starting with multiple 0x00 NUL bytes is quite common.
-      if (prefix.len < 4) {
-        return -1;
-      } else if ((prefix.ptr[2] != 0x00) &&
-                 ((prefix.ptr[2] >= 0x80) || (prefix.ptr[3] != 0x00))) {
+      // Unfortunately, some file formats also don't start with a magic
+      // identifier, so we have to use heuristics (where the order matters, the
+      // same as /usr/bin/file's magic/Magdir tables) as best we can. Maybe
+      // it's TGA, ICO/CUR, etc. Maybe it's something else.
+      int32_t tga = wuffs_base__magic_number_guess_fourcc__maybe_tga(
+          prefix_data, prefix_closed);
+      if (tga != 0) {
+        return tga;
+      }
+      int32_t ico = wuffs_base__magic_number_guess_fourcc__maybe_ico(
+          prefix_data, prefix_closed);
+      if (ico != 0) {
+        return ico;
+      }
+      if (prefix_data.len < 4) {
+        return prefix_closed ? 0 : -1;
+      } else if ((prefix_data.ptr[2] != 0x00) &&
+                 ((prefix_data.ptr[2] >= 0x80) ||
+                  (prefix_data.ptr[3] != 0x00))) {
         // Roughly speaking, this could be a non-degenerate (non-0-width and
         // non-0-height) WBMP image.
         return 0x57424D50;  // 'WBMP'be
-      } else if (((prefix.ptr[2] == 0x01) || (prefix.ptr[2] == 0x02)) &&
-                 (prefix.ptr[3] == 0x00)) {
-        return 0x49434F20;  // 'ICO 'be
       }
       return 0;
     }
diff --git a/lang/builtin/builtin.go b/lang/builtin/builtin.go
index 19f6eb5..e86124a 100644
--- a/lang/builtin/builtin.go
+++ b/lang/builtin/builtin.go
@@ -76,6 +76,7 @@
 	{"SVG ", "Scalable Vector Graphics"},
 	{"TAR ", "Tape Archive"},
 	{"TEXT", "Text"},
+	{"TGA ", "Truevision Advanced Raster Graphics Adapter"},
 	{"TIFF", "Tagged Image File Format"},
 	{"TOML", "Tom's Obvious Minimal Language"},
 	{"WAVE", "Waveform"},
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index 0875822..b2dccf2 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -716,6 +716,9 @@
 // Text.
 #define WUFFS_BASE__FOURCC__TEXT 0x54455854
 
+// Truevision Advanced Raster Graphics Adapter.
+#define WUFFS_BASE__FOURCC__TGA 0x54474120
+
 // Tagged Image File Format.
 #define WUFFS_BASE__FOURCC__TIFF 0x54494646
 
@@ -1682,13 +1685,19 @@
 // ---------------- Magic Numbers
 
 // wuffs_base__magic_number_guess_fourcc guesses the file format of some data,
-// given its opening bytes. It returns a positive FourCC value on success.
+// given its starting bytes (the prefix_data argument) and whether or not there
+// may be further bytes (the prefix_closed argument; true means that
+// prefix_data is the entire data).
+//
+// It returns a positive FourCC value on success.
 //
 // It returns zero if nothing matches its hard-coded list of 'magic numbers'.
 //
-// It returns a negative value if a longer prefix is required for a conclusive
-// result. For example, seeing a single 'B' byte is not enough to discriminate
-// the BMP and BPG image file formats.
+// It returns a negative value if prefix_closed is false and a longer prefix is
+// required for a conclusive result. For example, a single 'B' byte (without
+// further data) is not enough to discriminate the BMP and BPG image file
+// formats. Similarly, a single '\xFF' byte might be the start of JPEG data or
+// it might be the start of some other binary data.
 //
 // It does not do a full validity check. Like any guess made from a short
 // prefix of the data, it may return false positives. Data that starts with 99
@@ -1703,7 +1712,8 @@
 // function requires the WUFFS_CONFIG__MODULE__BASE__MAGIC sub-module, not just
 // WUFFS_CONFIG__MODULE__BASE__CORE.
 WUFFS_BASE__MAYBE_STATIC int32_t  //
-wuffs_base__magic_number_guess_fourcc(wuffs_base__slice_u8 prefix);
+wuffs_base__magic_number_guess_fourcc(wuffs_base__slice_u8 prefix_data,
+                                      bool prefix_closed);
 
 // ---------------- Ranges and Rects
 
@@ -9630,6 +9640,351 @@
 
 // ---------------- Status Codes
 
+extern const char wuffs_tga__error__bad_header[];
+extern const char wuffs_tga__error__bad_run_length_encoding[];
+extern const char wuffs_tga__error__unsupported_tga_file[];
+
+// ---------------- Public Consts
+
+#define WUFFS_TGA__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 0
+
+// ---------------- Struct Declarations
+
+typedef struct wuffs_tga__decoder__struct wuffs_tga__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_tga__decoder__initialize(
+    wuffs_tga__decoder* self,
+    size_t sizeof_star_self,
+    uint64_t wuffs_version,
+    uint32_t options);
+
+size_t
+sizeof__wuffs_tga__decoder();
+
+// ---------------- 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, decltype(&free)>.
+
+wuffs_tga__decoder*
+wuffs_tga__decoder__alloc();
+
+static inline wuffs_base__image_decoder*
+wuffs_tga__decoder__alloc_as__wuffs_base__image_decoder() {
+  return (wuffs_base__image_decoder*)(wuffs_tga__decoder__alloc());
+}
+
+// ---------------- Upcasts
+
+static inline wuffs_base__image_decoder*
+wuffs_tga__decoder__upcast_as__wuffs_base__image_decoder(
+    wuffs_tga__decoder* p) {
+  return (wuffs_base__image_decoder*)p;
+}
+
+// ---------------- Public Function Prototypes
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct
+wuffs_tga__decoder__set_quirk_enabled(
+    wuffs_tga__decoder* self,
+    uint32_t a_quirk,
+    bool a_enabled);
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_tga__decoder__decode_image_config(
+    wuffs_tga__decoder* self,
+    wuffs_base__image_config* a_dst,
+    wuffs_base__io_buffer* a_src);
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_tga__decoder__decode_frame_config(
+    wuffs_tga__decoder* self,
+    wuffs_base__frame_config* a_dst,
+    wuffs_base__io_buffer* a_src);
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_tga__decoder__decode_frame(
+    wuffs_tga__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__MAYBE_STATIC wuffs_base__rect_ie_u32
+wuffs_tga__decoder__frame_dirty_rect(
+    const wuffs_tga__decoder* self);
+
+WUFFS_BASE__MAYBE_STATIC uint32_t
+wuffs_tga__decoder__num_animation_loops(
+    const wuffs_tga__decoder* self);
+
+WUFFS_BASE__MAYBE_STATIC uint64_t
+wuffs_tga__decoder__num_decoded_frame_configs(
+    const wuffs_tga__decoder* self);
+
+WUFFS_BASE__MAYBE_STATIC uint64_t
+wuffs_tga__decoder__num_decoded_frames(
+    const wuffs_tga__decoder* self);
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_tga__decoder__restart_frame(
+    wuffs_tga__decoder* self,
+    uint64_t a_index,
+    uint64_t a_io_position);
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct
+wuffs_tga__decoder__set_report_metadata(
+    wuffs_tga__decoder* self,
+    uint32_t a_fourcc,
+    bool a_report);
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_tga__decoder__tell_me_more(
+    wuffs_tga__decoder* self,
+    wuffs_base__io_buffer* a_dst,
+    wuffs_base__more_information* a_minfo,
+    wuffs_base__io_buffer* a_src);
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64
+wuffs_tga__decoder__workbuf_len(
+    const wuffs_tga__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_tga__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_width;
+    uint32_t f_height;
+    uint8_t f_call_sequence;
+    uint8_t f_header_id_length;
+    uint8_t f_header_color_map_type;
+    uint8_t f_header_image_type;
+    uint16_t f_header_color_map_first_entry_index;
+    uint16_t f_header_color_map_length;
+    uint8_t f_header_color_map_entry_size;
+    uint8_t f_header_pixel_depth;
+    uint8_t f_header_image_descriptor;
+    bool f_opaque;
+    uint32_t f_scratch_bytes_per_pixel;
+    uint32_t f_src_bytes_per_pixel;
+    uint32_t f_src_pixfmt;
+    uint64_t f_frame_config_io_position;
+    wuffs_base__pixel_swizzler f_swizzler;
+
+    uint32_t p_decode_image_config[1];
+    uint32_t p_decode_frame_config[1];
+    uint32_t p_decode_frame[1];
+  } private_impl;
+
+  struct {
+    uint8_t f_dst_palette[1024];
+    uint8_t f_src_palette[1024];
+    uint8_t f_scratch[4];
+
+    struct {
+      uint32_t v_i;
+      uint64_t scratch;
+    } s_decode_image_config[1];
+    struct {
+      uint64_t v_dst_bytes_per_pixel;
+      uint32_t v_dst_x;
+      uint32_t v_dst_y;
+      uint64_t v_mark;
+      uint32_t v_num_pixels32;
+      uint32_t v_lit_length;
+      uint32_t v_run_length;
+      uint64_t v_num_dst_bytes;
+      uint64_t scratch;
+    } s_decode_frame[1];
+  } private_data;
+
+#ifdef __cplusplus
+#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR)
+  using unique_ptr = std::unique_ptr<wuffs_tga__decoder, decltype(&free)>;
+
+  // On failure, the alloc_etc functions return nullptr. They don't throw.
+
+  static inline unique_ptr
+  alloc() {
+    return unique_ptr(wuffs_tga__decoder__alloc(), &free);
+  }
+
+  static inline wuffs_base__image_decoder::unique_ptr
+  alloc_as__wuffs_base__image_decoder() {
+    return wuffs_base__image_decoder::unique_ptr(
+        wuffs_tga__decoder__alloc_as__wuffs_base__image_decoder(), &free);
+  }
+#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_tga__decoder__struct() = delete;
+  wuffs_tga__decoder__struct(const wuffs_tga__decoder__struct&) = delete;
+  wuffs_tga__decoder__struct& operator=(
+      const wuffs_tga__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_tga__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 wuffs_base__empty_struct
+  set_quirk_enabled(
+      uint32_t a_quirk,
+      bool a_enabled) {
+    return wuffs_tga__decoder__set_quirk_enabled(this, a_quirk, a_enabled);
+  }
+
+  inline wuffs_base__status
+  decode_image_config(
+      wuffs_base__image_config* a_dst,
+      wuffs_base__io_buffer* a_src) {
+    return wuffs_tga__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_tga__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_tga__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_tga__decoder__frame_dirty_rect(this);
+  }
+
+  inline uint32_t
+  num_animation_loops() const {
+    return wuffs_tga__decoder__num_animation_loops(this);
+  }
+
+  inline uint64_t
+  num_decoded_frame_configs() const {
+    return wuffs_tga__decoder__num_decoded_frame_configs(this);
+  }
+
+  inline uint64_t
+  num_decoded_frames() const {
+    return wuffs_tga__decoder__num_decoded_frames(this);
+  }
+
+  inline wuffs_base__status
+  restart_frame(
+      uint64_t a_index,
+      uint64_t a_io_position) {
+    return wuffs_tga__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_tga__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_tga__decoder__tell_me_more(this, a_dst, a_minfo, a_src);
+  }
+
+  inline wuffs_base__range_ii_u64
+  workbuf_len() const {
+    return wuffs_tga__decoder__workbuf_len(this);
+  }
+
+#endif  // __cplusplus
+};  // struct wuffs_tga__decoder__struct
+
+#endif  // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION)
+
+// ---------------- Status Codes
+
 extern const char wuffs_wbmp__error__bad_header[];
 
 // ---------------- Public Consts
@@ -10235,9 +10590,13 @@
   // Returning a nullptr means failure (DecodeImage_UnsupportedImageFormat).
   //
   // Common formats will have a FourCC value in the range [1 ..= 0x7FFF_FFFF],
-  // such as WUFFS_BASE__FOURCC__JPEG. A zero FourCC value means that the
-  // caller is responsible for examining the opening bytes (a prefix) of the
-  // input data. SelectDecoder implementations should not modify those bytes.
+  // such as WUFFS_BASE__FOURCC__JPEG. A zero FourCC value means that Wuffs'
+  // standard library did not recognize the image format but if SelectDecoder
+  // was overridden, it may examine the input data's starting bytes and still
+  // provide its own image decoder, e.g. for an exotic image file format that's
+  // not in Wuffs' standard library. The prefix_etc fields have the same
+  // meaning as wuffs_base__magic_number_guess_fourcc arguments. SelectDecoder
+  // implementations should not modify prefix_data's contents.
   //
   // SelectDecoder might be called more than once, since some image file
   // formats can wrap others. For example, a nominal BMP file can actually
@@ -10252,9 +10611,12 @@
   //  - WUFFS_BASE__FOURCC__GIF
   //  - WUFFS_BASE__FOURCC__NIE
   //  - WUFFS_BASE__FOURCC__PNG
+  //  - WUFFS_BASE__FOURCC__TGA
   //  - WUFFS_BASE__FOURCC__WBMP
   virtual wuffs_base__image_decoder::unique_ptr  //
-  SelectDecoder(uint32_t fourcc, wuffs_base__slice_u8 prefix);
+  SelectDecoder(uint32_t fourcc,
+                wuffs_base__slice_u8 prefix_data,
+                bool prefix_closed);
 
   // HandleMetadata acknowledges image metadata. minfo.flavor will be one of:
   //  - WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_RAW_PASSTHROUGH
@@ -15572,8 +15934,127 @@
 
 // ---------------- Magic Numbers
 
+// ICO doesn't start with a magic identifier. Instead, see if the opening bytes
+// are plausibly ICO.
+//
+// Callers should have already verified that (prefix_data.len >= 2) and the
+// first two bytes are 0x00.
+//
+// See:
+//  - https://docs.fileformat.com/image/ico/
+static int32_t  //
+wuffs_base__magic_number_guess_fourcc__maybe_ico(
+    wuffs_base__slice_u8 prefix_data,
+    bool prefix_closed) {
+  // Allow-list for the Image Type field.
+  if (prefix_data.len < 4) {
+    return prefix_closed ? 0 : -1;
+  } else if (prefix_data.ptr[3] != 0) {
+    return 0;
+  }
+  switch (prefix_data.ptr[2]) {
+    case 0x01:  // ICO
+    case 0x02:  // CUR
+      break;
+    default:
+      return 0;
+  }
+
+  // The Number Of Images should be positive.
+  if (prefix_data.len < 6) {
+    return prefix_closed ? 0 : -1;
+  } else if ((prefix_data.ptr[4] == 0) && (prefix_data.ptr[5] == 0)) {
+    return 0;
+  }
+
+  // The first ICONDIRENTRY's fourth byte should be zero.
+  if (prefix_data.len < 10) {
+    return prefix_closed ? 0 : -1;
+  } else if (prefix_data.ptr[9] != 0) {
+    return 0;
+  }
+
+  // TODO: have a separate FourCC for CUR?
+  return 0x49434F20;  // 'ICO 'be
+}
+
+// TGA doesn't start with a magic identifier. Instead, see if the opening bytes
+// are plausibly TGA.
+//
+// Callers should have already verified that (prefix_data.len >= 2) and the
+// second byte (prefix_data.ptr[1], the Color Map Type byte), is either 0x00 or
+// 0x01.
+//
+// See:
+//  - https://docs.fileformat.com/image/tga/
+//  - https://www.dca.fee.unicamp.br/~martino/disciplinas/ea978/tgaffs.pdf
+static int32_t  //
+wuffs_base__magic_number_guess_fourcc__maybe_tga(
+    wuffs_base__slice_u8 prefix_data,
+    bool prefix_closed) {
+  // Allow-list for the Image Type field.
+  if (prefix_data.len < 3) {
+    return prefix_closed ? 0 : -1;
+  }
+  switch (prefix_data.ptr[2]) {
+    case 0x01:
+    case 0x02:
+    case 0x03:
+    case 0x09:
+    case 0x0A:
+    case 0x0B:
+      break;
+    default:
+      // TODO: 0x20 and 0x21 are invalid, according to the spec, but are
+      // apparently unofficial extensions.
+      return 0;
+  }
+
+  // Allow-list for the Color Map Entry Size field (if the Color Map Type field
+  // is non-zero) or else all the Color Map fields should be zero.
+  if (prefix_data.len < 8) {
+    return prefix_closed ? 0 : -1;
+  } else if (prefix_data.ptr[1] != 0x00) {
+    switch (prefix_data.ptr[7]) {
+      case 0x0F:
+      case 0x10:
+      case 0x18:
+      case 0x20:
+        break;
+      default:
+        return 0;
+    }
+  } else if ((prefix_data.ptr[3] | prefix_data.ptr[4] | prefix_data.ptr[5] |
+              prefix_data.ptr[6] | prefix_data.ptr[7]) != 0x00) {
+    return 0;
+  }
+
+  // Allow-list for the Pixel Depth field.
+  if (prefix_data.len < 17) {
+    return prefix_closed ? 0 : -1;
+  }
+  switch (prefix_data.ptr[16]) {
+    case 0x01:
+    case 0x08:
+    case 0x0F:
+    case 0x10:
+    case 0x18:
+    case 0x20:
+      break;
+    default:
+      return 0;
+  }
+
+  return 0x54474120;  // 'TGA 'be
+}
+
 WUFFS_BASE__MAYBE_STATIC int32_t  //
-wuffs_base__magic_number_guess_fourcc(wuffs_base__slice_u8 prefix) {
+wuffs_base__magic_number_guess_fourcc(wuffs_base__slice_u8 prefix_data,
+                                      bool prefix_closed) {
+  // This is similar to (but different from):
+  //  - the magic/Magdir tables under https://github.com/file/file
+  //  - the MIME Sniffing algorithm at https://mimesniff.spec.whatwg.org/
+
   // table holds the 'magic numbers' (which are actually variable length
   // strings). The strings may contain NUL bytes, so the "const char* magic"
   // value starts with the length-minus-1 of the 'magic number'.
@@ -15600,10 +16081,10 @@
   };
   static const size_t table_len = sizeof(table) / sizeof(table[0]);
 
-  if (prefix.len == 0) {
-    return -1;
+  if (prefix_data.len == 0) {
+    return prefix_closed ? 0 : -1;
   }
-  uint8_t pre_first_byte = prefix.ptr[0];
+  uint8_t pre_first_byte = prefix_data.ptr[0];
 
   int32_t fourcc = 0;
   size_t i;
@@ -15622,11 +16103,11 @@
     }
 
     const char* mag_remaining_ptr = table[i].magic + 2;
-    uint8_t* pre_remaining_ptr = prefix.ptr + 1;
-    size_t pre_remaining_len = prefix.len - 1;
+    uint8_t* pre_remaining_ptr = prefix_data.ptr + 1;
+    size_t pre_remaining_len = prefix_data.len - 1;
     if (pre_remaining_len < mag_remaining_len) {
       if (!memcmp(pre_remaining_ptr, mag_remaining_ptr, pre_remaining_len)) {
-        return -1;
+        return prefix_closed ? 0 : -1;
       }
     } else {
       if (!memcmp(pre_remaining_ptr, mag_remaining_ptr, mag_remaining_len)) {
@@ -15634,6 +16115,14 @@
       }
     }
   }
+
+  if (prefix_data.len < 2) {
+    return prefix_closed ? 0 : -1;
+  } else if ((prefix_data.ptr[1] == 0x00) || (prefix_data.ptr[1] == 0x01)) {
+    return wuffs_base__magic_number_guess_fourcc__maybe_tga(prefix_data,
+                                                            prefix_closed);
+  }
+
   return 0;
 
 match:
@@ -15642,26 +16131,38 @@
     fourcc = -fourcc;
 
     if (fourcc == 0x52494646) {  // 'RIFF'be
-      if (prefix.len < 12) {
-        return -1;
+      if (prefix_data.len < 12) {
+        return prefix_closed ? 0 : -1;
       }
-      uint32_t x = wuffs_base__peek_u32be__no_bounds_check(prefix.ptr + 8);
+      uint32_t x = wuffs_base__peek_u32be__no_bounds_check(prefix_data.ptr + 8);
       if (x == 0x57454250) {  // 'WEBP'be
         return 0x57454250;    // 'WEBP'be
       }
 
     } else if (fourcc == 0x30302020) {  // '00  'be
       // Binary data starting with multiple 0x00 NUL bytes is quite common.
-      if (prefix.len < 4) {
-        return -1;
-      } else if ((prefix.ptr[2] != 0x00) &&
-                 ((prefix.ptr[2] >= 0x80) || (prefix.ptr[3] != 0x00))) {
+      // Unfortunately, some file formats also don't start with a magic
+      // identifier, so we have to use heuristics (where the order matters, the
+      // same as /usr/bin/file's magic/Magdir tables) as best we can. Maybe
+      // it's TGA, ICO/CUR, etc. Maybe it's something else.
+      int32_t tga = wuffs_base__magic_number_guess_fourcc__maybe_tga(
+          prefix_data, prefix_closed);
+      if (tga != 0) {
+        return tga;
+      }
+      int32_t ico = wuffs_base__magic_number_guess_fourcc__maybe_ico(
+          prefix_data, prefix_closed);
+      if (ico != 0) {
+        return ico;
+      }
+      if (prefix_data.len < 4) {
+        return prefix_closed ? 0 : -1;
+      } else if ((prefix_data.ptr[2] != 0x00) &&
+                 ((prefix_data.ptr[2] >= 0x80) ||
+                  (prefix_data.ptr[3] != 0x00))) {
         // Roughly speaking, this could be a non-degenerate (non-0-width and
         // non-0-height) WBMP image.
         return 0x57424D50;  // 'WBMP'be
-      } else if (((prefix.ptr[2] == 0x01) || (prefix.ptr[2] == 0x02)) &&
-                 (prefix.ptr[3] == 0x00)) {
-        return 0x49434F20;  // 'ICO 'be
       }
       return 0;
     }
@@ -41481,6 +41982,1240 @@
 
 #endif  // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__PNG)
 
+#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__TGA)
+
+// ---------------- Status Codes Implementations
+
+const char wuffs_tga__error__bad_header[] = "#tga: bad header";
+const char wuffs_tga__error__bad_run_length_encoding[] = "#tga: bad run length encoding";
+const char wuffs_tga__error__unsupported_tga_file[] = "#tga: unsupported TGA file";
+
+// ---------------- Private Consts
+
+// ---------------- Private Initializer Prototypes
+
+// ---------------- Private Function Prototypes
+
+// ---------------- VTables
+
+const wuffs_base__image_decoder__func_ptrs
+wuffs_tga__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_tga__decoder__decode_frame),
+  (wuffs_base__status(*)(void*,
+      wuffs_base__frame_config*,
+      wuffs_base__io_buffer*))(&wuffs_tga__decoder__decode_frame_config),
+  (wuffs_base__status(*)(void*,
+      wuffs_base__image_config*,
+      wuffs_base__io_buffer*))(&wuffs_tga__decoder__decode_image_config),
+  (wuffs_base__rect_ie_u32(*)(const void*))(&wuffs_tga__decoder__frame_dirty_rect),
+  (uint32_t(*)(const void*))(&wuffs_tga__decoder__num_animation_loops),
+  (uint64_t(*)(const void*))(&wuffs_tga__decoder__num_decoded_frame_configs),
+  (uint64_t(*)(const void*))(&wuffs_tga__decoder__num_decoded_frames),
+  (wuffs_base__status(*)(void*,
+      uint64_t,
+      uint64_t))(&wuffs_tga__decoder__restart_frame),
+  (wuffs_base__empty_struct(*)(void*,
+      uint32_t,
+      bool))(&wuffs_tga__decoder__set_quirk_enabled),
+  (wuffs_base__empty_struct(*)(void*,
+      uint32_t,
+      bool))(&wuffs_tga__decoder__set_report_metadata),
+  (wuffs_base__status(*)(void*,
+      wuffs_base__io_buffer*,
+      wuffs_base__more_information*,
+      wuffs_base__io_buffer*))(&wuffs_tga__decoder__tell_me_more),
+  (wuffs_base__range_ii_u64(*)(const void*))(&wuffs_tga__decoder__workbuf_len),
+};
+
+// ---------------- Initializer Implementations
+
+wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT
+wuffs_tga__decoder__initialize(
+    wuffs_tga__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_tga__decoder__func_ptrs_for__wuffs_base__image_decoder);
+  return wuffs_base__make_status(NULL);
+}
+
+wuffs_tga__decoder*
+wuffs_tga__decoder__alloc() {
+  wuffs_tga__decoder* x =
+      (wuffs_tga__decoder*)(calloc(sizeof(wuffs_tga__decoder), 1));
+  if (!x) {
+    return NULL;
+  }
+  if (wuffs_tga__decoder__initialize(
+      x, sizeof(wuffs_tga__decoder), WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED).repr) {
+    free(x);
+    return NULL;
+  }
+  return x;
+}
+
+size_t
+sizeof__wuffs_tga__decoder() {
+  return sizeof(wuffs_tga__decoder);
+}
+
+// ---------------- Function Implementations
+
+// -------- func tga.decoder.set_quirk_enabled
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct
+wuffs_tga__decoder__set_quirk_enabled(
+    wuffs_tga__decoder* self,
+    uint32_t a_quirk,
+    bool a_enabled) {
+  return wuffs_base__make_empty_struct();
+}
+
+// -------- func tga.decoder.decode_image_config
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_tga__decoder__decode_image_config(
+    wuffs_tga__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);
+
+  uint32_t v_c = 0;
+  uint32_t v_c5 = 0;
+  uint32_t v_i = 0;
+
+  const uint8_t* iop_a_src = NULL;
+  const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_src) {
+    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_decode_image_config[0];
+  if (coro_susp_point) {
+    v_i = self->private_data.s_decode_image_config[0].v_i;
+  }
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    if (self->private_impl.f_call_sequence != 0) {
+      status = wuffs_base__make_status(wuffs_base__error__bad_call_sequence);
+      goto exit;
+    }
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+        status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+        goto suspend;
+      }
+      uint8_t t_0 = *iop_a_src++;
+      self->private_impl.f_header_id_length = t_0;
+    }
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+        status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+        goto suspend;
+      }
+      uint8_t t_1 = *iop_a_src++;
+      self->private_impl.f_header_color_map_type = t_1;
+    }
+    if (self->private_impl.f_header_color_map_type > 1) {
+      status = wuffs_base__make_status(wuffs_tga__error__bad_header);
+      goto exit;
+    }
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+        status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+        goto suspend;
+      }
+      uint8_t t_2 = *iop_a_src++;
+      self->private_impl.f_header_image_type = t_2;
+    }
+    if ((self->private_impl.f_header_image_type == 1) ||
+        (self->private_impl.f_header_image_type == 2) ||
+        (self->private_impl.f_header_image_type == 3) ||
+        (self->private_impl.f_header_image_type == 9) ||
+        (self->private_impl.f_header_image_type == 10) ||
+        (self->private_impl.f_header_image_type == 11)) {
+    } else {
+      status = wuffs_base__make_status(wuffs_tga__error__bad_header);
+      goto exit;
+    }
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
+      uint16_t t_3;
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
+        t_3 = wuffs_base__peek_u16le__no_bounds_check(iop_a_src);
+        iop_a_src += 2;
+      } else {
+        self->private_data.s_decode_image_config[0].scratch = 0;
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
+        while (true) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+            status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+            goto suspend;
+          }
+          uint64_t* scratch = &self->private_data.s_decode_image_config[0].scratch;
+          uint32_t num_bits_3 = ((uint32_t)(*scratch >> 56));
+          *scratch <<= 8;
+          *scratch >>= 8;
+          *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_3;
+          if (num_bits_3 == 8) {
+            t_3 = ((uint16_t)(*scratch));
+            break;
+          }
+          num_bits_3 += 8;
+          *scratch |= ((uint64_t)(num_bits_3)) << 56;
+        }
+      }
+      self->private_impl.f_header_color_map_first_entry_index = t_3;
+    }
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
+      uint16_t t_4;
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
+        t_4 = wuffs_base__peek_u16le__no_bounds_check(iop_a_src);
+        iop_a_src += 2;
+      } else {
+        self->private_data.s_decode_image_config[0].scratch = 0;
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
+        while (true) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+            status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+            goto suspend;
+          }
+          uint64_t* scratch = &self->private_data.s_decode_image_config[0].scratch;
+          uint32_t num_bits_4 = ((uint32_t)(*scratch >> 56));
+          *scratch <<= 8;
+          *scratch >>= 8;
+          *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_4;
+          if (num_bits_4 == 8) {
+            t_4 = ((uint16_t)(*scratch));
+            break;
+          }
+          num_bits_4 += 8;
+          *scratch |= ((uint64_t)(num_bits_4)) << 56;
+        }
+      }
+      self->private_impl.f_header_color_map_length = t_4;
+    }
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+        status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+        goto suspend;
+      }
+      uint8_t t_5 = *iop_a_src++;
+      self->private_impl.f_header_color_map_entry_size = t_5;
+    }
+    if (self->private_impl.f_header_color_map_type != 0) {
+      if ((self->private_impl.f_header_color_map_first_entry_index != 0) || (self->private_impl.f_header_color_map_length > 256)) {
+        status = wuffs_base__make_status(wuffs_tga__error__unsupported_tga_file);
+        goto exit;
+      } else if ((self->private_impl.f_header_color_map_entry_size != 15) &&
+          (self->private_impl.f_header_color_map_entry_size != 16) &&
+          (self->private_impl.f_header_color_map_entry_size != 24) &&
+          (self->private_impl.f_header_color_map_entry_size != 32)) {
+        status = wuffs_base__make_status(wuffs_tga__error__bad_header);
+        goto exit;
+      }
+    } else {
+      if ((self->private_impl.f_header_color_map_first_entry_index != 0) || (self->private_impl.f_header_color_map_length != 0) || (self->private_impl.f_header_color_map_entry_size != 0)) {
+        status = wuffs_base__make_status(wuffs_tga__error__bad_header);
+        goto exit;
+      }
+    }
+    self->private_data.s_decode_image_config[0].scratch = 4;
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9);
+    if (self->private_data.s_decode_image_config[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) {
+      self->private_data.s_decode_image_config[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src));
+      iop_a_src = io2_a_src;
+      status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+      goto suspend;
+    }
+    iop_a_src += self->private_data.s_decode_image_config[0].scratch;
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(10);
+      uint32_t t_6;
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
+        t_6 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src)));
+        iop_a_src += 2;
+      } else {
+        self->private_data.s_decode_image_config[0].scratch = 0;
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(11);
+        while (true) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+            status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+            goto suspend;
+          }
+          uint64_t* scratch = &self->private_data.s_decode_image_config[0].scratch;
+          uint32_t num_bits_6 = ((uint32_t)(*scratch >> 56));
+          *scratch <<= 8;
+          *scratch >>= 8;
+          *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_6;
+          if (num_bits_6 == 8) {
+            t_6 = ((uint32_t)(*scratch));
+            break;
+          }
+          num_bits_6 += 8;
+          *scratch |= ((uint64_t)(num_bits_6)) << 56;
+        }
+      }
+      self->private_impl.f_width = t_6;
+    }
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(12);
+      uint32_t t_7;
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
+        t_7 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src)));
+        iop_a_src += 2;
+      } else {
+        self->private_data.s_decode_image_config[0].scratch = 0;
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(13);
+        while (true) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+            status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+            goto suspend;
+          }
+          uint64_t* scratch = &self->private_data.s_decode_image_config[0].scratch;
+          uint32_t num_bits_7 = ((uint32_t)(*scratch >> 56));
+          *scratch <<= 8;
+          *scratch >>= 8;
+          *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_7;
+          if (num_bits_7 == 8) {
+            t_7 = ((uint32_t)(*scratch));
+            break;
+          }
+          num_bits_7 += 8;
+          *scratch |= ((uint64_t)(num_bits_7)) << 56;
+        }
+      }
+      self->private_impl.f_height = t_7;
+    }
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(14);
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+        status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+        goto suspend;
+      }
+      uint8_t t_8 = *iop_a_src++;
+      self->private_impl.f_header_pixel_depth = t_8;
+    }
+    if ((self->private_impl.f_header_pixel_depth != 1) &&
+        (self->private_impl.f_header_pixel_depth != 8) &&
+        (self->private_impl.f_header_pixel_depth != 15) &&
+        (self->private_impl.f_header_pixel_depth != 16) &&
+        (self->private_impl.f_header_pixel_depth != 24) &&
+        (self->private_impl.f_header_pixel_depth != 32)) {
+      status = wuffs_base__make_status(wuffs_tga__error__bad_header);
+      goto exit;
+    }
+    if ((self->private_impl.f_header_image_type | 8) == 9) {
+      self->private_impl.f_scratch_bytes_per_pixel = 1;
+      self->private_impl.f_src_bytes_per_pixel = 1;
+      self->private_impl.f_src_pixfmt = 2164523016;
+      self->private_impl.f_opaque = ((self->private_impl.f_header_color_map_entry_size == 15) || (self->private_impl.f_header_color_map_entry_size == 24));
+    } else if ((self->private_impl.f_header_image_type | 8) == 10) {
+      if ((self->private_impl.f_header_pixel_depth == 15) || (self->private_impl.f_header_pixel_depth == 16)) {
+        self->private_impl.f_scratch_bytes_per_pixel = 4;
+        self->private_impl.f_src_bytes_per_pixel = 0;
+        self->private_impl.f_src_pixfmt = 2164295816;
+      } else if (self->private_impl.f_header_pixel_depth == 24) {
+        self->private_impl.f_scratch_bytes_per_pixel = 3;
+        self->private_impl.f_src_bytes_per_pixel = 3;
+        self->private_impl.f_src_pixfmt = 2147485832;
+        self->private_impl.f_opaque = true;
+      } else if (self->private_impl.f_header_pixel_depth == 32) {
+        self->private_impl.f_scratch_bytes_per_pixel = 4;
+        self->private_impl.f_src_bytes_per_pixel = 4;
+        self->private_impl.f_src_pixfmt = 2164295816;
+      } else {
+        status = wuffs_base__make_status(wuffs_tga__error__unsupported_tga_file);
+        goto exit;
+      }
+    } else {
+      if (self->private_impl.f_header_pixel_depth == 8) {
+        self->private_impl.f_scratch_bytes_per_pixel = 1;
+        self->private_impl.f_src_bytes_per_pixel = 1;
+        self->private_impl.f_src_pixfmt = 536870920;
+        self->private_impl.f_opaque = true;
+      } else {
+        status = wuffs_base__make_status(wuffs_tga__error__unsupported_tga_file);
+        goto exit;
+      }
+    }
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(15);
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+        status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+        goto suspend;
+      }
+      uint8_t t_9 = *iop_a_src++;
+      self->private_impl.f_header_image_descriptor = t_9;
+    }
+    if ((self->private_impl.f_header_image_descriptor & 16) != 0) {
+      status = wuffs_base__make_status(wuffs_tga__error__unsupported_tga_file);
+      goto exit;
+    }
+    self->private_data.s_decode_image_config[0].scratch = ((uint32_t)(self->private_impl.f_header_id_length));
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(16);
+    if (self->private_data.s_decode_image_config[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) {
+      self->private_data.s_decode_image_config[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src));
+      iop_a_src = io2_a_src;
+      status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+      goto suspend;
+    }
+    iop_a_src += self->private_data.s_decode_image_config[0].scratch;
+    if (self->private_impl.f_header_color_map_type != 0) {
+      while (v_i < ((uint32_t)(self->private_impl.f_header_color_map_length))) {
+        if (self->private_impl.f_header_color_map_entry_size == 24) {
+          {
+            WUFFS_BASE__COROUTINE_SUSPENSION_POINT(17);
+            uint32_t t_10;
+            if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 3)) {
+              t_10 = ((uint32_t)(wuffs_base__peek_u24le__no_bounds_check(iop_a_src)));
+              iop_a_src += 3;
+            } else {
+              self->private_data.s_decode_image_config[0].scratch = 0;
+              WUFFS_BASE__COROUTINE_SUSPENSION_POINT(18);
+              while (true) {
+                if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+                  status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+                  goto suspend;
+                }
+                uint64_t* scratch = &self->private_data.s_decode_image_config[0].scratch;
+                uint32_t num_bits_10 = ((uint32_t)(*scratch >> 56));
+                *scratch <<= 8;
+                *scratch >>= 8;
+                *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_10;
+                if (num_bits_10 == 16) {
+                  t_10 = ((uint32_t)(*scratch));
+                  break;
+                }
+                num_bits_10 += 8;
+                *scratch |= ((uint64_t)(num_bits_10)) << 56;
+              }
+            }
+            v_c = t_10;
+          }
+          self->private_data.f_src_palette[(((v_i & 255) * 4) + 0)] = ((uint8_t)(((v_c >> 0) & 255)));
+          self->private_data.f_src_palette[(((v_i & 255) * 4) + 1)] = ((uint8_t)(((v_c >> 8) & 255)));
+          self->private_data.f_src_palette[(((v_i & 255) * 4) + 2)] = ((uint8_t)(((v_c >> 16) & 255)));
+          self->private_data.f_src_palette[(((v_i & 255) * 4) + 3)] = 255;
+        } else if (self->private_impl.f_header_color_map_entry_size == 32) {
+          {
+            WUFFS_BASE__COROUTINE_SUSPENSION_POINT(19);
+            uint32_t t_11;
+            if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) {
+              t_11 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src);
+              iop_a_src += 4;
+            } else {
+              self->private_data.s_decode_image_config[0].scratch = 0;
+              WUFFS_BASE__COROUTINE_SUSPENSION_POINT(20);
+              while (true) {
+                if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+                  status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+                  goto suspend;
+                }
+                uint64_t* scratch = &self->private_data.s_decode_image_config[0].scratch;
+                uint32_t num_bits_11 = ((uint32_t)(*scratch >> 56));
+                *scratch <<= 8;
+                *scratch >>= 8;
+                *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_11;
+                if (num_bits_11 == 24) {
+                  t_11 = ((uint32_t)(*scratch));
+                  break;
+                }
+                num_bits_11 += 8;
+                *scratch |= ((uint64_t)(num_bits_11)) << 56;
+              }
+            }
+            v_c = t_11;
+          }
+          self->private_data.f_src_palette[(((v_i & 255) * 4) + 0)] = ((uint8_t)(((v_c >> 0) & 255)));
+          self->private_data.f_src_palette[(((v_i & 255) * 4) + 1)] = ((uint8_t)(((v_c >> 8) & 255)));
+          self->private_data.f_src_palette[(((v_i & 255) * 4) + 2)] = ((uint8_t)(((v_c >> 16) & 255)));
+          self->private_data.f_src_palette[(((v_i & 255) * 4) + 3)] = ((uint8_t)(((v_c >> 24) & 255)));
+        } else {
+          {
+            WUFFS_BASE__COROUTINE_SUSPENSION_POINT(21);
+            uint32_t t_12;
+            if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
+              t_12 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src)));
+              iop_a_src += 2;
+            } else {
+              self->private_data.s_decode_image_config[0].scratch = 0;
+              WUFFS_BASE__COROUTINE_SUSPENSION_POINT(22);
+              while (true) {
+                if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+                  status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+                  goto suspend;
+                }
+                uint64_t* scratch = &self->private_data.s_decode_image_config[0].scratch;
+                uint32_t num_bits_12 = ((uint32_t)(*scratch >> 56));
+                *scratch <<= 8;
+                *scratch >>= 8;
+                *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_12;
+                if (num_bits_12 == 8) {
+                  t_12 = ((uint32_t)(*scratch));
+                  break;
+                }
+                num_bits_12 += 8;
+                *scratch |= ((uint64_t)(num_bits_12)) << 56;
+              }
+            }
+            v_c = t_12;
+          }
+          v_c5 = (31 & (v_c >> 0));
+          self->private_data.f_src_palette[(((v_i & 255) * 4) + 0)] = ((uint8_t)(((v_c5 << 3) | (v_c5 >> 2))));
+          v_c5 = (31 & (v_c >> 5));
+          self->private_data.f_src_palette[(((v_i & 255) * 4) + 1)] = ((uint8_t)(((v_c5 << 3) | (v_c5 >> 2))));
+          v_c5 = (31 & (v_c >> 10));
+          self->private_data.f_src_palette[(((v_i & 255) * 4) + 2)] = ((uint8_t)(((v_c5 << 3) | (v_c5 >> 2))));
+          self->private_data.f_src_palette[(((v_i & 255) * 4) + 3)] = 255;
+        }
+        v_i += 1;
+      }
+      while (v_i < 256) {
+        self->private_data.f_src_palette[((v_i * 4) + 0)] = 0;
+        self->private_data.f_src_palette[((v_i * 4) + 1)] = 0;
+        self->private_data.f_src_palette[((v_i * 4) + 2)] = 0;
+        self->private_data.f_src_palette[((v_i * 4) + 3)] = 255;
+        v_i += 1;
+      }
+    }
+    self->private_impl.f_frame_config_io_position = wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src)));
+    if (a_dst != NULL) {
+      wuffs_base__image_config__set(
+          a_dst,
+          self->private_impl.f_src_pixfmt,
+          0,
+          self->private_impl.f_width,
+          self->private_impl.f_height,
+          self->private_impl.f_frame_config_io_position,
+          self->private_impl.f_opaque);
+    }
+    self->private_impl.f_call_sequence = 3;
+
+    goto ok;
+    ok:
+    self->private_impl.p_decode_image_config[0] = 0;
+    goto exit;
+  }
+
+  goto suspend;
+  suspend:
+  self->private_impl.p_decode_image_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+  self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0;
+  self->private_data.s_decode_image_config[0].v_i = v_i;
+
+  goto exit;
+  exit:
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+  }
+
+  if (wuffs_base__status__is_error(&status)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+  }
+  return status;
+}
+
+// -------- func tga.decoder.decode_frame_config
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_tga__decoder__decode_frame_config(
+    wuffs_tga__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);
+
+  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) {
+    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_decode_frame_config[0];
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    if (self->private_impl.f_call_sequence < 3) {
+      if (a_src) {
+        a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+      }
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
+      status = wuffs_tga__decoder__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 == 3) {
+      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 == 4) {
+      self->private_impl.f_call_sequence = 255;
+      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(
+          0,
+          0,
+          self->private_impl.f_width,
+          self->private_impl.f_height),
+          ((wuffs_base__flicks)(0)),
+          0,
+          self->private_impl.f_frame_config_io_position,
+          0,
+          self->private_impl.f_opaque,
+          false,
+          4278190080);
+    }
+    self->private_impl.f_call_sequence = 4;
+
+    ok:
+    self->private_impl.p_decode_frame_config[0] = 0;
+    goto exit;
+  }
+
+  goto suspend;
+  suspend:
+  self->private_impl.p_decode_frame_config[0] = 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 (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+  }
+
+  if (wuffs_base__status__is_error(&status)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+  }
+  return status;
+}
+
+// -------- func tga.decoder.decode_frame
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_tga__decoder__decode_frame(
+    wuffs_tga__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);
+  wuffs_base__pixel_format v_dst_pixfmt = {0};
+  uint32_t v_dst_bits_per_pixel = 0;
+  uint64_t v_dst_bytes_per_pixel = 0;
+  uint32_t v_dst_x = 0;
+  uint32_t v_dst_y = 0;
+  wuffs_base__table_u8 v_tab = {0};
+  wuffs_base__slice_u8 v_dst_palette = {0};
+  wuffs_base__slice_u8 v_dst = {0};
+  uint64_t v_dst_start = 0;
+  wuffs_base__slice_u8 v_src_palette = {0};
+  uint64_t v_mark = 0;
+  uint64_t v_num_pixels64 = 0;
+  uint32_t v_num_pixels32 = 0;
+  uint32_t v_lit_length = 0;
+  uint32_t v_run_length = 0;
+  uint64_t v_num_dst_bytes = 0;
+  uint32_t v_num_src_bytes = 0;
+  uint32_t v_c = 0;
+  uint32_t v_c5 = 0;
+
+  const uint8_t* iop_a_src = NULL;
+  const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_src) {
+    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_decode_frame[0];
+  if (coro_susp_point) {
+    v_dst_bytes_per_pixel = self->private_data.s_decode_frame[0].v_dst_bytes_per_pixel;
+    v_dst_x = self->private_data.s_decode_frame[0].v_dst_x;
+    v_dst_y = self->private_data.s_decode_frame[0].v_dst_y;
+    v_mark = self->private_data.s_decode_frame[0].v_mark;
+    v_num_pixels32 = self->private_data.s_decode_frame[0].v_num_pixels32;
+    v_lit_length = self->private_data.s_decode_frame[0].v_lit_length;
+    v_run_length = self->private_data.s_decode_frame[0].v_run_length;
+    v_num_dst_bytes = self->private_data.s_decode_frame[0].v_num_dst_bytes;
+  }
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    if (self->private_impl.f_call_sequence < 4) {
+      if (a_src) {
+        a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+      }
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
+      status = wuffs_tga__decoder__decode_frame_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 == 4) {
+    } else {
+      status = wuffs_base__make_status(wuffs_base__note__end_of_data);
+      goto ok;
+    }
+    if (self->private_impl.f_header_color_map_type != 0) {
+      v_src_palette = wuffs_base__make_slice_u8(self->private_data.f_src_palette, 1024);
+    }
+    v_status = wuffs_base__pixel_swizzler__prepare(&self->private_impl.f_swizzler,
+        wuffs_base__pixel_buffer__pixel_format(a_dst),
+        wuffs_base__pixel_buffer__palette_or_else(a_dst, wuffs_base__make_slice_u8(self->private_data.f_dst_palette, 1024)),
+        wuffs_base__utility__make_pixel_format(self->private_impl.f_src_pixfmt),
+        v_src_palette,
+        a_blend);
+    if ( ! wuffs_base__status__is_ok(&v_status)) {
+      status = v_status;
+      if (wuffs_base__status__is_error(&status)) {
+        goto exit;
+      } else if (wuffs_base__status__is_suspension(&status)) {
+        status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension);
+        goto exit;
+      }
+      goto ok;
+    }
+    v_dst_pixfmt = wuffs_base__pixel_buffer__pixel_format(a_dst);
+    v_dst_bits_per_pixel = wuffs_base__pixel_format__bits_per_pixel(&v_dst_pixfmt);
+    if ((v_dst_bits_per_pixel & 7) != 0) {
+      status = wuffs_base__make_status(wuffs_base__error__unsupported_option);
+      goto exit;
+    }
+    v_dst_bytes_per_pixel = ((uint64_t)((v_dst_bits_per_pixel / 8)));
+    if ((self->private_impl.f_header_image_descriptor & 32) == 0) {
+      v_dst_y = ((uint32_t)(self->private_impl.f_height - 1));
+    }
+    if ((self->private_impl.f_header_image_type & 8) == 0) {
+      v_lit_length = self->private_impl.f_width;
+    }
+    label__resume__continue:;
+    while (true) {
+      v_tab = wuffs_base__pixel_buffer__plane(a_dst, 0);
+      v_dst_palette = wuffs_base__pixel_buffer__palette_or_else(a_dst, wuffs_base__make_slice_u8(self->private_data.f_dst_palette, 1024));
+      while (v_dst_y < self->private_impl.f_height) {
+        v_dst = wuffs_base__table_u8__row_u32(v_tab, v_dst_y);
+        v_dst_start = (((uint64_t)(v_dst_x)) * v_dst_bytes_per_pixel);
+        if (v_dst_start <= ((uint64_t)(v_dst.len))) {
+          v_dst = wuffs_base__slice_u8__subslice_i(v_dst, v_dst_start);
+        } else {
+          v_dst = wuffs_base__utility__empty_slice_u8();
+        }
+        while (v_dst_x < self->private_impl.f_width) {
+          if (self->private_impl.f_src_bytes_per_pixel > 0) {
+            if (v_lit_length > 0) {
+              v_mark = ((uint64_t)(iop_a_src - io0_a_src));
+              v_num_pixels64 = (((uint64_t)(io2_a_src - iop_a_src)) / ((uint64_t)(self->private_impl.f_src_bytes_per_pixel)));
+              v_num_pixels32 = ((uint32_t)(wuffs_base__u64__min(v_num_pixels64, ((uint64_t)(v_lit_length)))));
+              v_num_dst_bytes = (((uint64_t)(v_num_pixels32)) * v_dst_bytes_per_pixel);
+              v_num_src_bytes = (v_num_pixels32 * self->private_impl.f_src_bytes_per_pixel);
+              self->private_data.s_decode_frame[0].scratch = v_num_src_bytes;
+              WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
+              if (self->private_data.s_decode_frame[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) {
+                self->private_data.s_decode_frame[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src));
+                iop_a_src = io2_a_src;
+                status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+                goto suspend;
+              }
+              iop_a_src += self->private_data.s_decode_frame[0].scratch;
+              wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, v_dst, v_dst_palette, wuffs_base__io__since(v_mark, ((uint64_t)(iop_a_src - io0_a_src)), io0_a_src));
+              if (v_num_dst_bytes <= ((uint64_t)(v_dst.len))) {
+                v_dst = wuffs_base__slice_u8__subslice_i(v_dst, v_num_dst_bytes);
+              } else {
+                v_dst = wuffs_base__utility__empty_slice_u8();
+              }
+              v_dst_x += v_num_pixels32;
+              v_lit_length = (((uint32_t)(v_lit_length - v_num_pixels32)) & 65535);
+              if (v_lit_length > 0) {
+                status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+                WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(3);
+                goto label__resume__continue;
+              }
+            } else if (v_run_length > 0) {
+              v_run_length -= 1;
+              wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, v_dst, v_dst_palette, wuffs_base__slice_u8__subslice_j(wuffs_base__make_slice_u8(self->private_data.f_scratch, 4), self->private_impl.f_scratch_bytes_per_pixel));
+              if (v_dst_bytes_per_pixel <= ((uint64_t)(v_dst.len))) {
+                v_dst = wuffs_base__slice_u8__subslice_i(v_dst, v_dst_bytes_per_pixel);
+              }
+              v_dst_x += 1;
+            } else {
+              if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) {
+                status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+                WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(4);
+                goto label__resume__continue;
+              }
+              if (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) < 128) {
+                v_lit_length = (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) + 1);
+                iop_a_src += 1;
+                if ((v_lit_length + v_dst_x) > self->private_impl.f_width) {
+                  status = wuffs_base__make_status(wuffs_tga__error__bad_run_length_encoding);
+                  goto exit;
+                }
+              } else {
+                if (self->private_impl.f_src_bytes_per_pixel == 1) {
+                  if (((uint64_t)(io2_a_src - iop_a_src)) < 2) {
+                    status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+                    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(5);
+                    goto label__resume__continue;
+                  }
+                  v_run_length = ((((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) & 127) + 1);
+                  iop_a_src += 1;
+                  self->private_data.f_scratch[0] = wuffs_base__peek_u8be__no_bounds_check(iop_a_src);
+                  iop_a_src += 1;
+                } else if (self->private_impl.f_src_bytes_per_pixel == 3) {
+                  if (((uint64_t)(io2_a_src - iop_a_src)) < 4) {
+                    status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+                    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(6);
+                    goto label__resume__continue;
+                  }
+                  v_run_length = ((((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) & 127) + 1);
+                  iop_a_src += 1;
+                  self->private_data.f_scratch[0] = wuffs_base__peek_u8be__no_bounds_check(iop_a_src);
+                  iop_a_src += 1;
+                  self->private_data.f_scratch[1] = wuffs_base__peek_u8be__no_bounds_check(iop_a_src);
+                  iop_a_src += 1;
+                  self->private_data.f_scratch[2] = wuffs_base__peek_u8be__no_bounds_check(iop_a_src);
+                  iop_a_src += 1;
+                } else {
+                  if (((uint64_t)(io2_a_src - iop_a_src)) < 5) {
+                    status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+                    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(7);
+                    goto label__resume__continue;
+                  }
+                  v_run_length = ((((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) & 127) + 1);
+                  iop_a_src += 1;
+                  self->private_data.f_scratch[0] = wuffs_base__peek_u8be__no_bounds_check(iop_a_src);
+                  iop_a_src += 1;
+                  self->private_data.f_scratch[1] = wuffs_base__peek_u8be__no_bounds_check(iop_a_src);
+                  iop_a_src += 1;
+                  self->private_data.f_scratch[2] = wuffs_base__peek_u8be__no_bounds_check(iop_a_src);
+                  iop_a_src += 1;
+                  self->private_data.f_scratch[3] = wuffs_base__peek_u8be__no_bounds_check(iop_a_src);
+                  iop_a_src += 1;
+                }
+                if ((v_run_length + v_dst_x) > self->private_impl.f_width) {
+                  status = wuffs_base__make_status(wuffs_tga__error__bad_run_length_encoding);
+                  goto exit;
+                }
+              }
+            }
+          } else {
+            if (v_lit_length > 0) {
+              if (((uint64_t)(io2_a_src - iop_a_src)) < 2) {
+                status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+                WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(8);
+                goto label__resume__continue;
+              }
+              v_c = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src)));
+              iop_a_src += 2;
+              v_c5 = (31 & (v_c >> 0));
+              self->private_data.f_scratch[0] = ((uint8_t)(((v_c5 << 3) | (v_c5 >> 2))));
+              v_c5 = (31 & (v_c >> 5));
+              self->private_data.f_scratch[1] = ((uint8_t)(((v_c5 << 3) | (v_c5 >> 2))));
+              v_c5 = (31 & (v_c >> 10));
+              self->private_data.f_scratch[2] = ((uint8_t)(((v_c5 << 3) | (v_c5 >> 2))));
+              self->private_data.f_scratch[3] = 255;
+              wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, v_dst, v_dst_palette, wuffs_base__make_slice_u8(self->private_data.f_scratch, 4));
+              if (v_dst_bytes_per_pixel <= ((uint64_t)(v_dst.len))) {
+                v_dst = wuffs_base__slice_u8__subslice_i(v_dst, v_dst_bytes_per_pixel);
+              }
+              v_dst_x += 1;
+              v_lit_length -= 1;
+            } else if (v_run_length > 0) {
+              v_run_length -= 1;
+              wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, v_dst, v_dst_palette, wuffs_base__slice_u8__subslice_j(wuffs_base__make_slice_u8(self->private_data.f_scratch, 4), self->private_impl.f_scratch_bytes_per_pixel));
+              if (v_dst_bytes_per_pixel <= ((uint64_t)(v_dst.len))) {
+                v_dst = wuffs_base__slice_u8__subslice_i(v_dst, v_dst_bytes_per_pixel);
+              }
+              v_dst_x += 1;
+            } else {
+              if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) {
+                status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+                WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(9);
+                goto label__resume__continue;
+              }
+              if (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) < 128) {
+                v_lit_length = (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) + 1);
+                iop_a_src += 1;
+                if ((v_lit_length + v_dst_x) > self->private_impl.f_width) {
+                  status = wuffs_base__make_status(wuffs_tga__error__bad_run_length_encoding);
+                  goto exit;
+                }
+              } else {
+                if (((uint64_t)(io2_a_src - iop_a_src)) < 3) {
+                  status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+                  WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(10);
+                  goto label__resume__continue;
+                }
+                v_run_length = ((((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) & 127) + 1);
+                iop_a_src += 1;
+                v_c = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src)));
+                iop_a_src += 2;
+                v_c5 = (31 & (v_c >> 0));
+                self->private_data.f_scratch[0] = ((uint8_t)(((v_c5 << 3) | (v_c5 >> 2))));
+                v_c5 = (31 & (v_c >> 5));
+                self->private_data.f_scratch[1] = ((uint8_t)(((v_c5 << 3) | (v_c5 >> 2))));
+                v_c5 = (31 & (v_c >> 10));
+                self->private_data.f_scratch[2] = ((uint8_t)(((v_c5 << 3) | (v_c5 >> 2))));
+                self->private_data.f_scratch[3] = 255;
+                if ((v_run_length + v_dst_x) > self->private_impl.f_width) {
+                  status = wuffs_base__make_status(wuffs_tga__error__bad_run_length_encoding);
+                  goto exit;
+                }
+              }
+            }
+          }
+        }
+        v_dst_x = 0;
+        if ((self->private_impl.f_header_image_descriptor & 32) == 0) {
+          v_dst_y -= 1;
+        } else {
+          v_dst_y += 1;
+        }
+        if ((self->private_impl.f_header_image_type & 8) == 0) {
+          v_lit_length = self->private_impl.f_width;
+        }
+      }
+      goto label__resume__break;
+    }
+    label__resume__break:;
+    self->private_impl.f_call_sequence = 255;
+
+    ok:
+    self->private_impl.p_decode_frame[0] = 0;
+    goto exit;
+  }
+
+  goto suspend;
+  suspend:
+  self->private_impl.p_decode_frame[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+  self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 3 : 0;
+  self->private_data.s_decode_frame[0].v_dst_bytes_per_pixel = v_dst_bytes_per_pixel;
+  self->private_data.s_decode_frame[0].v_dst_x = v_dst_x;
+  self->private_data.s_decode_frame[0].v_dst_y = v_dst_y;
+  self->private_data.s_decode_frame[0].v_mark = v_mark;
+  self->private_data.s_decode_frame[0].v_num_pixels32 = v_num_pixels32;
+  self->private_data.s_decode_frame[0].v_lit_length = v_lit_length;
+  self->private_data.s_decode_frame[0].v_run_length = v_run_length;
+  self->private_data.s_decode_frame[0].v_num_dst_bytes = v_num_dst_bytes;
+
+  goto exit;
+  exit:
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+  }
+
+  if (wuffs_base__status__is_error(&status)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+  }
+  return status;
+}
+
+// -------- func tga.decoder.frame_dirty_rect
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__rect_ie_u32
+wuffs_tga__decoder__frame_dirty_rect(
+    const wuffs_tga__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(
+      0,
+      0,
+      self->private_impl.f_width,
+      self->private_impl.f_height);
+}
+
+// -------- func tga.decoder.num_animation_loops
+
+WUFFS_BASE__MAYBE_STATIC uint32_t
+wuffs_tga__decoder__num_animation_loops(
+    const wuffs_tga__decoder* self) {
+  if (!self) {
+    return 0;
+  }
+  if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&
+      (self->private_impl.magic != WUFFS_BASE__DISABLED)) {
+    return 0;
+  }
+
+  return 0;
+}
+
+// -------- func tga.decoder.num_decoded_frame_configs
+
+WUFFS_BASE__MAYBE_STATIC uint64_t
+wuffs_tga__decoder__num_decoded_frame_configs(
+    const wuffs_tga__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 > 3) {
+    return 1;
+  }
+  return 0;
+}
+
+// -------- func tga.decoder.num_decoded_frames
+
+WUFFS_BASE__MAYBE_STATIC uint64_t
+wuffs_tga__decoder__num_decoded_frames(
+    const wuffs_tga__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 > 4) {
+    return 1;
+  }
+  return 0;
+}
+
+// -------- func tga.decoder.restart_frame
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_tga__decoder__restart_frame(
+    wuffs_tga__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 < 3) {
+    return wuffs_base__make_status(wuffs_base__error__bad_call_sequence);
+  }
+  if (a_index != 0) {
+    return wuffs_base__make_status(wuffs_base__error__bad_argument);
+  }
+  self->private_impl.f_call_sequence = 3;
+  self->private_impl.f_frame_config_io_position = a_io_position;
+  return wuffs_base__make_status(NULL);
+}
+
+// -------- func tga.decoder.set_report_metadata
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct
+wuffs_tga__decoder__set_report_metadata(
+    wuffs_tga__decoder* self,
+    uint32_t a_fourcc,
+    bool a_report) {
+  return wuffs_base__make_empty_struct();
+}
+
+// -------- func tga.decoder.tell_me_more
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_tga__decoder__tell_me_more(
+    wuffs_tga__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 tga.decoder.workbuf_len
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64
+wuffs_tga__decoder__workbuf_len(
+    const wuffs_tga__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(0, 0);
+}
+
+#endif  // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__TGA)
+
 #if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__WBMP)
 
 // ---------------- Status Codes Implementations
@@ -42927,7 +44662,8 @@
 
 wuffs_base__image_decoder::unique_ptr  //
 DecodeImageCallbacks::SelectDecoder(uint32_t fourcc,
-                                    wuffs_base__slice_u8 prefix) {
+                                    wuffs_base__slice_u8 prefix_data,
+                                    bool prefix_closed) {
   switch (fourcc) {
 #if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BMP)
     case WUFFS_BASE__FOURCC__BMP:
@@ -42953,6 +44689,11 @@
     }
 #endif
 
+#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__TGA)
+    case WUFFS_BASE__FOURCC__TGA:
+      return wuffs_tga__decoder::alloc_as__wuffs_base__image_decoder();
+#endif
+
 #if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__WBMP)
     case WUFFS_BASE__FOURCC__WBMP:
       return wuffs_wbmp__decoder::alloc_as__wuffs_base__image_decoder();
@@ -43180,10 +44921,17 @@
     // Determine the image format.
     if (!redirected) {
       while (true) {
-        fourcc = wuffs_base__magic_number_guess_fourcc(io_buf.reader_slice());
+        fourcc = wuffs_base__magic_number_guess_fourcc(io_buf.reader_slice(),
+                                                       io_buf.meta.closed);
         if (fourcc > 0) {
           break;
         } else if ((fourcc == 0) && (io_buf.reader_length() >= 64)) {
+          // Having (fourcc == 0) means that Wuffs' built in MIME sniffer
+          // didn't recognize the image format. Nonetheless, custom callbacks
+          // may still be able to do their own MIME sniffing, for exotic image
+          // types. We try to give them at least 64 bytes of prefix data when
+          // one-shot-calling callbacks.SelectDecoder. There is no mechanism
+          // for the callbacks to request a longer prefix.
           break;
         } else if (io_buf.meta.closed || (io_buf.writer_length() == 0)) {
           fourcc = 0;
@@ -43224,8 +44972,7 @@
 
     // Select the image decoder.
     image_decoder = callbacks.SelectDecoder(
-        (uint32_t)fourcc,
-        fourcc ? wuffs_base__empty_slice_u8() : io_buf.reader_slice());
+        (uint32_t)fourcc, io_buf.reader_slice(), io_buf.meta.closed);
     if (!image_decoder) {
       return DecodeImageResult(DecodeImage_UnsupportedImageFormat);
     }
diff --git a/std/tga/decode_tga.wuffs b/std/tga/decode_tga.wuffs
new file mode 100644
index 0000000..e052e63
--- /dev/null
+++ b/std/tga/decode_tga.wuffs
@@ -0,0 +1,599 @@
+// Copyright 2022 The Wuffs Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+pub status "#bad header"
+pub status "#bad run length encoding"
+pub status "#unsupported TGA file"
+
+pub const DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE : base.u64 = 0
+
+pub struct decoder? implements base.image_decoder(
+	width  : base.u32[..= 0xFFFF],
+	height : base.u32[..= 0xFFFF],
+
+	// Call sequence states:
+	//  - 0x00: initial state.
+	//  - 0x03: image config decoded.
+	//  - 0x04: frame config decoded.
+	//  - 0xFF: end-of-data, usually after (the non-animated) frame decoded.
+	//
+	// State transitions:
+	//
+	//  - 0x00 -> 0x03: via DIC
+	//  - 0x00 -> 0x04: via DFC with implicit DIC
+	//  - 0x00 -> 0xFF: via DF  with implicit DIC and DFC
+	//
+	//  - 0x03 -> 0x04: via DFC
+	//  - 0x03 -> 0xFF: via DF  with implicit DFC
+	//
+	//  - 0x04 -> 0xFF: via DFC
+	//  - 0x04 -> 0xFF: via DF
+	//
+	//  - ???? -> 0x03: via RF  for ???? > 0x00
+	//
+	// Where:
+	//  - DF  is decode_frame
+	//  - DFC is decode_frame_config, implicit means nullptr args.dst
+	//  - DIC is decode_image_config, implicit means nullptr args.dst
+	//  - RF  is restart_frame
+	call_sequence : base.u8,
+
+	header_id_length                   : base.u8,
+	header_color_map_type              : base.u8,
+	header_image_type                  : base.u8,
+	header_color_map_first_entry_index : base.u16,
+	header_color_map_length            : base.u16,
+	header_color_map_entry_size        : base.u8,
+	header_pixel_depth                 : base.u8,
+	header_image_descriptor            : base.u8,
+
+	opaque : base.bool,
+
+	scratch_bytes_per_pixel : base.u32[..= 4],
+	src_bytes_per_pixel     : base.u32[..= 4],
+	src_pixfmt              : base.u32,
+
+	frame_config_io_position : base.u64,
+
+	swizzler : base.pixel_swizzler,
+	util     : base.utility,
+)(
+	dst_palette : array[4 * 256] base.u8,
+	src_palette : array[4 * 256] base.u8,
+	scratch     : array[4] base.u8,
+)
+
+pub func decoder.set_quirk_enabled!(quirk: base.u32, enabled: base.bool) {
+}
+
+pub func decoder.decode_image_config?(dst: nptr base.image_config, src: base.io_reader) {
+	var c  : base.u32
+	var c5 : base.u32[..= 0x1F]
+	var i  : base.u32
+
+	if this.call_sequence <> 0 {
+		return base."#bad call sequence"
+	}
+
+	this.header_id_length = args.src.read_u8?()
+	this.header_color_map_type = args.src.read_u8?()
+	if this.header_color_map_type > 1 {
+		return "#bad header"
+	}
+
+	this.header_image_type = args.src.read_u8?()
+	if (this.header_image_type == 0x01) or
+		(this.header_image_type == 0x02) or
+		(this.header_image_type == 0x03) or
+		(this.header_image_type == 0x09) or
+		(this.header_image_type == 0x0A) or
+		(this.header_image_type == 0x0B) {
+		// No-op.
+	} else {
+		// TODO: 0x20 and 0x21 are invalid, according to the spec, but are
+		// apparently unofficial extensions.
+		return "#bad header"
+	}
+
+	this.header_color_map_first_entry_index = args.src.read_u16le?()
+	this.header_color_map_length = args.src.read_u16le?()
+	this.header_color_map_entry_size = args.src.read_u8?()
+	if this.header_color_map_type <> 0 {
+		// We have a color-mapped image (in Wuffs, an indexed pixel format).
+		if (this.header_color_map_first_entry_index <> 0) or
+			(this.header_color_map_length > 0x100) {
+			return "#unsupported TGA file"
+		} else if (this.header_color_map_entry_size <> 0x0F) and
+			(this.header_color_map_entry_size <> 0x10) and
+			(this.header_color_map_entry_size <> 0x18) and
+			(this.header_color_map_entry_size <> 0x20) {
+			return "#bad header"
+		}
+	} else {
+		// The color-map fields must be zero.
+		if (this.header_color_map_first_entry_index <> 0) or
+			(this.header_color_map_length <> 0) or
+			(this.header_color_map_entry_size <> 0) {
+			return "#bad header"
+		}
+	}
+
+	// Ignore the X-Origin and Y-Origin fields.
+	args.src.skip_u32?(n: 4)
+
+	this.width = args.src.read_u16le_as_u32?()
+	this.height = args.src.read_u16le_as_u32?()
+
+	this.header_pixel_depth = args.src.read_u8?()
+	if (this.header_pixel_depth <> 0x01) and
+		(this.header_pixel_depth <> 0x08) and
+		(this.header_pixel_depth <> 0x0F) and
+		(this.header_pixel_depth <> 0x10) and
+		(this.header_pixel_depth <> 0x18) and
+		(this.header_pixel_depth <> 0x20) {
+		return "#bad header"
+	}
+
+	if (this.header_image_type | 8) == 0x09 {
+		this.scratch_bytes_per_pixel = 1
+		this.src_bytes_per_pixel = 1
+		this.src_pixfmt = base.PIXEL_FORMAT__INDEXED__BGRA_NONPREMUL
+		this.opaque =
+			(this.header_color_map_entry_size == 0x0F) or
+			(this.header_color_map_entry_size == 0x18)
+
+	} else if (this.header_image_type | 8) == 0x0A {
+		if (this.header_pixel_depth == 0x0F) or
+			(this.header_pixel_depth == 0x10) {
+			// Wuffs' base.pixel_swizzler doesn't support BGRX5551, so
+			// scratch_bytes_per_pixel and src_bytes_per_pixel are different.
+			this.scratch_bytes_per_pixel = 4
+			this.src_bytes_per_pixel = 0
+			this.src_pixfmt = base.PIXEL_FORMAT__BGRA_NONPREMUL
+		} else if this.header_pixel_depth == 0x18 {
+			this.scratch_bytes_per_pixel = 3
+			this.src_bytes_per_pixel = 3
+			this.src_pixfmt = base.PIXEL_FORMAT__BGR
+			this.opaque = true
+		} else if this.header_pixel_depth == 0x20 {
+			this.scratch_bytes_per_pixel = 4
+			this.src_bytes_per_pixel = 4
+			this.src_pixfmt = base.PIXEL_FORMAT__BGRA_NONPREMUL
+		} else {
+			return "#unsupported TGA file"
+		}
+
+	} else {
+		if this.header_pixel_depth == 0x08 {
+			this.scratch_bytes_per_pixel = 1
+			this.src_bytes_per_pixel = 1
+			this.src_pixfmt = base.PIXEL_FORMAT__Y
+			this.opaque = true
+		} else {
+			return "#unsupported TGA file"
+		}
+	}
+
+	this.header_image_descriptor = args.src.read_u8?()
+	if (this.header_image_descriptor & 0x10) <> 0 {
+		// We don't support right-to-left, only left-to-right pixel order.
+		return "#unsupported TGA file"
+	}
+
+	// Skip the Image ID.
+	args.src.skip_u32?(n: this.header_id_length as base.u32)
+
+	// Read the color map.
+	if this.header_color_map_type <> 0 {
+		while i < (this.header_color_map_length as base.u32) {
+			assert i <= 0xFFFF via "a <= b: a <= c; c <= b"(c: this.header_color_map_length as base.u32)
+			if this.header_color_map_entry_size == 0x18 {
+				c = args.src.read_u24le_as_u32?()
+				this.src_palette[((i & 0xFF) * 4) + 0] = ((c >> 0x00) & 0xFF) as base.u8
+				this.src_palette[((i & 0xFF) * 4) + 1] = ((c >> 0x08) & 0xFF) as base.u8
+				this.src_palette[((i & 0xFF) * 4) + 2] = ((c >> 0x10) & 0xFF) as base.u8
+				this.src_palette[((i & 0xFF) * 4) + 3] = 0xFF
+			} else if this.header_color_map_entry_size == 0x20 {
+				c = args.src.read_u32le?()
+				this.src_palette[((i & 0xFF) * 4) + 0] = ((c >> 0x00) & 0xFF) as base.u8
+				this.src_palette[((i & 0xFF) * 4) + 1] = ((c >> 0x08) & 0xFF) as base.u8
+				this.src_palette[((i & 0xFF) * 4) + 2] = ((c >> 0x10) & 0xFF) as base.u8
+				this.src_palette[((i & 0xFF) * 4) + 3] = ((c >> 0x18) & 0xFF) as base.u8
+			} else {
+				// Expand 15-bit or 16-bit color map entries.
+				c = args.src.read_u16le_as_u32?()
+				c5 = 0x1F & (c >> 0)
+				this.src_palette[((i & 0xFF) * 4) + 0] = ((c5 << 3) | (c5 >> 2)) as base.u8
+				c5 = 0x1F & (c >> 5)
+				this.src_palette[((i & 0xFF) * 4) + 1] = ((c5 << 3) | (c5 >> 2)) as base.u8
+				c5 = 0x1F & (c >> 10)
+				this.src_palette[((i & 0xFF) * 4) + 2] = ((c5 << 3) | (c5 >> 2)) as base.u8
+				// TODO: can the alpha value be zero (BGRA5551 not BGRX5551)?
+				this.src_palette[((i & 0xFF) * 4) + 3] = 0xFF
+			}
+			i += 1
+		} endwhile
+		while i < 0x100 {
+			this.src_palette[(i * 4) + 0] = 0x00
+			this.src_palette[(i * 4) + 1] = 0x00
+			this.src_palette[(i * 4) + 2] = 0x00
+			this.src_palette[(i * 4) + 3] = 0xFF
+			i += 1
+		} endwhile
+	}
+
+	this.frame_config_io_position = args.src.position()
+
+	if args.dst <> nullptr {
+		args.dst.set!(
+			pixfmt: this.src_pixfmt,
+			pixsub: 0,
+			width: this.width,
+			height: this.height,
+			first_frame_io_position: this.frame_config_io_position,
+			first_frame_is_opaque: this.opaque)
+	}
+
+	this.call_sequence = 3
+}
+
+pub func decoder.decode_frame_config?(dst: nptr base.frame_config, src: base.io_reader) {
+	if this.call_sequence < 3 {
+		this.decode_image_config?(dst: nullptr, src: args.src)
+	} else if this.call_sequence == 3 {
+		if this.frame_config_io_position <> args.src.position() {
+			return base."#bad restart"
+		}
+	} else if this.call_sequence == 4 {
+		this.call_sequence = 0xFF
+		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: this.opaque,
+			overwrite_instead_of_blend: false,
+			background_color: 0xFF00_0000)
+	}
+
+	this.call_sequence = 4
+}
+
+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
+	var dst_pixfmt          : base.pixel_format
+	var dst_bits_per_pixel  : base.u32[..= 256]
+	var dst_bytes_per_pixel : base.u64[..= 32]
+	var dst_x               : base.u32
+	var dst_y               : base.u32
+	var tab                 : table base.u8
+	var dst_palette         : slice base.u8
+	var dst                 : slice base.u8
+	var dst_start           : base.u64
+	var src_palette         : slice base.u8
+	var mark                : base.u64
+	var num_pixels64        : base.u64
+	var num_pixels32        : base.u32[..= 0xFFFF]
+	var lit_length          : base.u32[..= 0xFFFF]
+	var run_length          : base.u32[..= 0xFFFF]
+	var num_dst_bytes       : base.u64[..= 0x1F_FFE0]
+	var num_src_bytes       : base.u32[..= 0x3_FFFC]
+	var c                   : base.u32
+	var c5                  : base.u32[..= 0x1F]
+
+	if this.call_sequence < 4 {
+		this.decode_frame_config?(dst: nullptr, src: args.src)
+	} else if this.call_sequence == 4 {
+		// No-op.
+	} else {
+		return base."@end of data"
+	}
+
+	if this.header_color_map_type <> 0 {
+		src_palette = this.src_palette[..]
+	}
+	status = this.swizzler.prepare!(
+		dst_pixfmt: args.dst.pixel_format(),
+		dst_palette: args.dst.palette_or_else(fallback: this.dst_palette[..]),
+		src_pixfmt: this.util.make_pixel_format(repr: this.src_pixfmt),
+		src_palette: src_palette,
+		blend: args.blend)
+	if not status.is_ok() {
+		return status
+	}
+
+	// TODO: the dst_pixfmt variable shouldn't be necessary. We should be able
+	// to chain the two calls: "args.dst.pixel_format().bits_per_pixel()".
+	dst_pixfmt = args.dst.pixel_format()
+	dst_bits_per_pixel = dst_pixfmt.bits_per_pixel()
+	if (dst_bits_per_pixel & 7) <> 0 {
+		return base."#unsupported option"
+	}
+	dst_bytes_per_pixel = (dst_bits_per_pixel / 8) as base.u64
+
+	if (this.header_image_descriptor & 0x20) == 0 {  // Bottom-to-top.
+		dst_y = this.height ~mod- 1
+	}
+	if (this.header_image_type & 8) == 0 {
+		// No RLE (run length encoding) means that the entire row is
+		// effectively literals.
+		lit_length = this.width
+	}
+
+	while.resume true {
+		tab = args.dst.plane(p: 0)
+		dst_palette = args.dst.palette_or_else(fallback: this.dst_palette[..])
+
+		while dst_y < this.height {
+			dst = tab.row_u32(y: dst_y)
+			dst_start = (dst_x as base.u64) * dst_bytes_per_pixel
+			if dst_start <= dst.length() {
+				dst = dst[dst_start ..]
+			} else {
+				dst = this.util.empty_slice_u8()
+			}
+
+			while dst_x < this.width {
+				assert dst_x <= 0xFFFF via "a <= b: a <= c; c <= b"(c: this.width)
+
+				if this.src_bytes_per_pixel > 0 {
+					if lit_length > 0 {
+						mark = args.src.mark()
+						num_pixels64 = args.src.length() / (this.src_bytes_per_pixel as base.u64)
+						num_pixels32 = num_pixels64.min(a: lit_length as base.u64) as base.u32
+						num_dst_bytes = (num_pixels32 as base.u64) * dst_bytes_per_pixel
+						num_src_bytes = num_pixels32 * this.src_bytes_per_pixel
+						args.src.skip_u32?(n: num_src_bytes)
+						this.swizzler.swizzle_interleaved_from_slice!(
+							dst: dst,
+							dst_palette: dst_palette,
+							src: args.src.since(mark: mark))
+						if num_dst_bytes <= dst.length() {
+							dst = dst[num_dst_bytes ..]
+						} else {
+							dst = this.util.empty_slice_u8()
+						}
+						dst_x += num_pixels32
+						lit_length = (lit_length ~mod- num_pixels32) & 0xFFFF
+						if lit_length > 0 {
+							yield? base."$short read"
+							continue.resume
+						}
+
+					} else if run_length > 0 {
+						run_length -= 1
+						this.swizzler.swizzle_interleaved_from_slice!(
+							dst: dst,
+							dst_palette: dst_palette,
+							src: this.scratch[.. this.scratch_bytes_per_pixel])
+						if dst_bytes_per_pixel <= dst.length() {
+							dst = dst[dst_bytes_per_pixel ..]
+						}
+						dst_x += 1
+
+					} else {
+						// Handle Raw vs RLE packets.
+						if args.src.length() <= 0 {
+							yield? base."$short read"
+							continue.resume
+						}
+						if args.src.peek_u8_as_u32() < 0x80 {
+							lit_length = args.src.peek_u8_as_u32() + 1
+							args.src.skip_u32_fast!(actual: 1, worst_case: 1)
+							if (lit_length + dst_x) > this.width {
+								return "#bad run length encoding"
+							}
+
+						} else {
+							if this.src_bytes_per_pixel == 1 {
+								if args.src.length() < 2 {
+									yield? base."$short read"
+									continue.resume
+								}
+								run_length = (args.src.peek_u8_as_u32() & 0x7F) + 1
+								args.src.skip_u32_fast!(actual: 1, worst_case: 1)
+								this.scratch[0] = args.src.peek_u8()
+								args.src.skip_u32_fast!(actual: 1, worst_case: 1)
+							} else if this.src_bytes_per_pixel == 3 {
+								if args.src.length() < 4 {
+									yield? base."$short read"
+									continue.resume
+								}
+								run_length = (args.src.peek_u8_as_u32() & 0x7F) + 1
+								args.src.skip_u32_fast!(actual: 1, worst_case: 1)
+								this.scratch[0] = args.src.peek_u8()
+								args.src.skip_u32_fast!(actual: 1, worst_case: 1)
+								this.scratch[1] = args.src.peek_u8()
+								args.src.skip_u32_fast!(actual: 1, worst_case: 1)
+								this.scratch[2] = args.src.peek_u8()
+								args.src.skip_u32_fast!(actual: 1, worst_case: 1)
+							} else {  // this.src_bytes_per_pixel == 4
+								if args.src.length() < 5 {
+									yield? base."$short read"
+									continue.resume
+								}
+								run_length = (args.src.peek_u8_as_u32() & 0x7F) + 1
+								args.src.skip_u32_fast!(actual: 1, worst_case: 1)
+								this.scratch[0] = args.src.peek_u8()
+								args.src.skip_u32_fast!(actual: 1, worst_case: 1)
+								this.scratch[1] = args.src.peek_u8()
+								args.src.skip_u32_fast!(actual: 1, worst_case: 1)
+								this.scratch[2] = args.src.peek_u8()
+								args.src.skip_u32_fast!(actual: 1, worst_case: 1)
+								this.scratch[3] = args.src.peek_u8()
+								args.src.skip_u32_fast!(actual: 1, worst_case: 1)
+							}
+
+							if (run_length + dst_x) > this.width {
+								return "#bad run length encoding"
+							}
+						}
+					}
+
+				} else {
+					// Wuffs' base.pixel_swizzler doesn't support BGRX5551, so
+					// we manually convert to BGRX8888, one pixel at a time.
+
+					if lit_length > 0 {
+						if args.src.length() < 2 {
+							yield? base."$short read"
+							continue.resume
+						}
+						c = args.src.peek_u16le_as_u32()
+						args.src.skip_u32_fast!(actual: 2, worst_case: 2)
+						c5 = 0x1F & (c >> 0)
+						this.scratch[0] = ((c5 << 3) | (c5 >> 2)) as base.u8
+						c5 = 0x1F & (c >> 5)
+						this.scratch[1] = ((c5 << 3) | (c5 >> 2)) as base.u8
+						c5 = 0x1F & (c >> 10)
+						this.scratch[2] = ((c5 << 3) | (c5 >> 2)) as base.u8
+						// TODO: can the alpha value be zero (BGRA5551 not BGRX5551)?
+						this.scratch[3] = 0xFF
+						this.swizzler.swizzle_interleaved_from_slice!(
+							dst: dst,
+							dst_palette: dst_palette,
+							src: this.scratch[.. 4])
+						if dst_bytes_per_pixel <= dst.length() {
+							dst = dst[dst_bytes_per_pixel ..]
+						}
+						dst_x += 1
+						lit_length -= 1
+
+					} else if run_length > 0 {
+						run_length -= 1
+						this.swizzler.swizzle_interleaved_from_slice!(
+							dst: dst,
+							dst_palette: dst_palette,
+							src: this.scratch[.. this.scratch_bytes_per_pixel])
+						if dst_bytes_per_pixel <= dst.length() {
+							dst = dst[dst_bytes_per_pixel ..]
+						}
+						dst_x += 1
+
+					} else {
+						// Handle Raw vs RLE packets.
+						if args.src.length() <= 0 {
+							yield? base."$short read"
+							continue.resume
+						}
+						if args.src.peek_u8_as_u32() < 0x80 {
+							lit_length = args.src.peek_u8_as_u32() + 1
+							args.src.skip_u32_fast!(actual: 1, worst_case: 1)
+							if (lit_length + dst_x) > this.width {
+								return "#bad run length encoding"
+							}
+
+						} else {
+							if args.src.length() < 3 {
+								yield? base."$short read"
+								continue.resume
+							}
+							run_length = (args.src.peek_u8_as_u32() & 0x7F) + 1
+							args.src.skip_u32_fast!(actual: 1, worst_case: 1)
+							c = args.src.peek_u16le_as_u32()
+							args.src.skip_u32_fast!(actual: 2, worst_case: 2)
+							c5 = 0x1F & (c >> 0)
+							this.scratch[0] = ((c5 << 3) | (c5 >> 2)) as base.u8
+							c5 = 0x1F & (c >> 5)
+							this.scratch[1] = ((c5 << 3) | (c5 >> 2)) as base.u8
+							c5 = 0x1F & (c >> 10)
+							this.scratch[2] = ((c5 << 3) | (c5 >> 2)) as base.u8
+							// TODO: can the alpha value be zero (BGRA5551 not BGRX5551)?
+							this.scratch[3] = 0xFF
+
+							if (run_length + dst_x) > this.width {
+								return "#bad run length encoding"
+							}
+						}
+					}
+				}
+			} endwhile
+			dst_x = 0
+
+			if (this.header_image_descriptor & 0x20) == 0 {  // Bottom-to-top.
+				dst_y ~mod-= 1
+			} else {  // Top-to-bottom.
+				dst_y ~mod+= 1
+			}
+			if (this.header_image_type & 8) == 0 {
+				// No RLE (run length encoding) means that the entire row is
+				// effectively literals.
+				lit_length = this.width
+			}
+		} endwhile
+		break.resume
+	} endwhile.resume
+
+	this.call_sequence = 0xFF
+}
+
+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 > 3 {
+		return 1
+	}
+	return 0
+}
+
+pub func decoder.num_decoded_frames() base.u64 {
+	if this.call_sequence > 4 {
+		return 1
+	}
+	return 0
+}
+
+pub func decoder.restart_frame!(index: base.u64, io_position: base.u64) base.status {
+	if this.call_sequence < 3 {
+		return base."#bad call sequence"
+	}
+	if args.index <> 0 {
+		return base."#bad argument"
+	}
+	this.call_sequence = 3
+	this.frame_config_io_position = args.io_position
+	return ok
+}
+
+pub func decoder.set_report_metadata!(fourcc: base.u32, report: base.bool) {
+	// TODO: implement.
+}
+
+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/fetch-tgasuite.sh b/test/3pdata/fetch-tgasuite.sh
new file mode 100755
index 0000000..a615e9f
--- /dev/null
+++ b/test/3pdata/fetch-tgasuite.sh
@@ -0,0 +1,35 @@
+#!/bin/bash -eu
+# Copyright 2022 The Wuffs Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# ----------------
+
+# This script fetches the TrueVision TGA test suite.
+
+if [ ! -e ../../wuffs-root-directory.txt ]; then
+  echo "$0 should be run from the Wuffs test/3pdata directory."
+  exit 1
+fi
+
+# Check out a specific commit (from January 2022).
+git clone --quiet https://github.com/image-rs/image.git
+cd image
+git reset --quiet --hard 2e1d2e5928077eae7a92f8b5cac83ca1ae0db0cd
+cd ..
+
+# Copy out the *.tga files.
+mkdir -p tgasuite
+cp image/tests/images/tga/testsuite/*.tga  tgasuite
+cp image/tests/regression/tga/*.tga        tgasuite
+rm -rf image
diff --git a/test/3pdata/nia-checksums-of-tgasuite.txt b/test/3pdata/nia-checksums-of-tgasuite.txt
new file mode 100644
index 0000000..2083243
--- /dev/null
+++ b/test/3pdata/nia-checksums-of-tgasuite.txt
@@ -0,0 +1,9 @@
+# Generated by script/print-nia-checksums.sh
+9fbe866a test/3pdata/tgasuite/cbw8.tga
+5d75ef41 test/3pdata/tgasuite/ccm8.tga
+5d75ef41 test/3pdata/tgasuite/ctc24.tga
+9fbe866a test/3pdata/tgasuite/ubw8.tga
+5d75ef41 test/3pdata/tgasuite/ucm8.tga
+5d75ef41 test/3pdata/tgasuite/utc16.tga
+5d75ef41 test/3pdata/tgasuite/utc24.tga
+79781cfb test/3pdata/tgasuite/utc32.tga
diff --git a/test/c/std/tga.c b/test/c/std/tga.c
new file mode 100644
index 0000000..0e0f6fb
--- /dev/null
+++ b/test/c/std/tga.c
@@ -0,0 +1,137 @@
+// Copyright 2022 The Wuffs Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// ----------------
+
+/*
+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 tga.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__TGA
+
+// 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
+
+// ---------------- TGA Tests
+
+const char*  //
+test_wuffs_tga_decode_interface() {
+  CHECK_FOCUS(__func__);
+  wuffs_tga__decoder dec;
+  CHECK_STATUS("initialize",
+               wuffs_tga__decoder__initialize(
+                   &dec, sizeof dec, WUFFS_VERSION,
+                   WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED));
+  return do_test__wuffs_base__image_decoder(
+      wuffs_tga__decoder__upcast_as__wuffs_base__image_decoder(&dec),
+      "test/data/bricks-color.tga", 0, SIZE_MAX, 160, 120, 0xFF022460);
+}
+
+// ---------------- Mimic Tests
+
+#ifdef WUFFS_MIMIC
+
+// No mimic tests.
+
+#endif  // WUFFS_MIMIC
+
+// ---------------- TGA Benches
+
+// No TGA benches.
+
+// ---------------- Mimic Benches
+
+#ifdef WUFFS_MIMIC
+
+// No mimic benches.
+
+#endif  // WUFFS_MIMIC
+
+// ---------------- Manifest
+
+proc g_tests[] = {
+
+    test_wuffs_tga_decode_interface,
+
+#ifdef WUFFS_MIMIC
+
+// No mimic tests.
+
+#endif  // WUFFS_MIMIC
+
+    NULL,
+};
+
+proc g_benches[] = {
+
+// No TGA benches.
+
+#ifdef WUFFS_MIMIC
+
+// No mimic benches.
+
+#endif  // WUFFS_MIMIC
+
+    NULL,
+};
+
+int  //
+main(int argc, char** argv) {
+  g_proc_package_name = "std/tga";
+  return test_main(argc, argv, g_tests, g_benches);
+}
diff --git a/test/data/bricks-color.tga b/test/data/bricks-color.tga
new file mode 100644
index 0000000..aa87427
--- /dev/null
+++ b/test/data/bricks-color.tga
Binary files differ
diff --git a/test/data/bricks-gray.tga b/test/data/bricks-gray.tga
new file mode 100644
index 0000000..a72dd47
--- /dev/null
+++ b/test/data/bricks-gray.tga
Binary files differ
diff --git a/test/data/bricks-nodither.tga b/test/data/bricks-nodither.tga
new file mode 100644
index 0000000..898d255
--- /dev/null
+++ b/test/data/bricks-nodither.tga
Binary files differ
diff --git a/test/nia-checksums-of-data.txt b/test/nia-checksums-of-data.txt
index 4626713..16ed6c3 100644
--- a/test/nia-checksums-of-data.txt
+++ b/test/nia-checksums-of-data.txt
@@ -26,6 +26,7 @@
 e08a7cc8 test/data/artificial-png/key-value-pairs.png
 076cb375 test/data/bricks-color.bmp
 076cb375 test/data/bricks-color.png
+076cb375 test/data/bricks-color.tga
 f36c2e80 test/data/bricks-dither.bmp
 f36c2e80 test/data/bricks-dither.gif
 f36c2e80 test/data/bricks-dither.no-ancillary.png
@@ -34,9 +35,11 @@
 c2bce675 test/data/bricks-gray.gif
 c2bce675 test/data/bricks-gray.no-ancillary.png
 c2bce675 test/data/bricks-gray.png
+c2bce675 test/data/bricks-gray.tga
 7aebfe6c test/data/bricks-nodither.bmp
 7aebfe6c test/data/bricks-nodither.gif
 7aebfe6c test/data/bricks-nodither.png
+7aebfe6c test/data/bricks-nodither.tga
 ae2e547d test/data/bricks-nodither.wbmp
 b47730a2 test/data/checkerboard.gamma1dot0.png
 b47730a2 test/data/checkerboard.gamma2dot2.png