Update wuffs to match the version used by Skia

Change-Id: I4c98412464785f1175af68d6d7ae9f8b1038ace2
diff --git a/cmd/ractool/main.go b/cmd/ractool/main.go
new file mode 100644
index 0000000..c552ba3
--- /dev/null
+++ b/cmd/ractool/main.go
@@ -0,0 +1,148 @@
+// Copyright 2019 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.
+
+// ----------------
+
+// ractool manipulates Random Access Compression (RAC) files.
+package main
+
+import (
+	"bytes"
+	"errors"
+	"flag"
+	"fmt"
+	"io"
+	"os"
+
+	"github.com/google/wuffs/lib/raczlib"
+)
+
+// TODO: a flag to use a disk-backed (not memory-backed) TempFile.
+
+var (
+	decodeFlag = flag.Bool("decode", false, "whether to decode the input")
+	encodeFlag = flag.Bool("encode", false, "whether to encode the input")
+
+	codecFlag         = flag.String("codec", "zlib", "when encoding, the compression codec")
+	cpagesizeFlag     = flag.Uint64("cpagesize", 0, "when encoding, the page size (in CSpace)")
+	cchunksizeFlag    = flag.Uint64("cchunksize", 0, "when encoding, the chunk size (in CSpace)")
+	dchunksizeFlag    = flag.Uint64("dchunksize", 0, "when encoding, the chunk size (in DSpace)")
+	indexlocationFlag = flag.String("indexlocation", "start",
+		"when encoding, the index location, \"start\" or \"end\"")
+)
+
+const usageStr = `usage: ractool [flags] [input_filename]
+
+If no input_filename is given, stdin is used. Either way, output is written to
+stdout.
+
+The flags should include exactly one of -decode or -encode.
+
+Decoding is not yet implemented.
+
+When encoding, the input is partitioned into chunks and each chunk is
+compressed independently. You can specify the target chunk size in terms of
+either its compressed size or decompressed size. By default (if both
+-cchunksize and -dchunksize are zero), a 64KiB -cchunksize is used.
+
+You can also specify a -cpagesize, which is similar to but not exactly the same
+concept as alignment. If non-zero, padding is inserted into the output to
+minimize the number of pages that each chunk occupies. Look for "CPageSize" in
+the "package rac" documentation for more details.
+
+A RAC file consists of an index and the chunks. The index may be either at the
+start or at the end of the file. See the RAC specification for more details:
+
+https://github.com/google/wuffs/blob/master/doc/spec/rac-spec.md
+
+`
+
+func usage() {
+	fmt.Fprintf(os.Stderr, usageStr)
+	flag.PrintDefaults()
+}
+
+func main() {
+	if err := main1(); err != nil {
+		os.Stderr.WriteString(err.Error() + "\n")
+		os.Exit(1)
+	}
+}
+
+func main1() error {
+	flag.Usage = usage
+	flag.Parse()
+
+	r := io.Reader(os.Stdin)
+	switch flag.NArg() {
+	case 0:
+		// No-op.
+	case 1:
+		f, err := os.Open(flag.Arg(0))
+		if err != nil {
+			return err
+		}
+		defer f.Close()
+		r = f
+	default:
+		return errors.New("too many filenames; the maximum is one")
+	}
+
+	if *decodeFlag && !*encodeFlag {
+		return decode(r)
+	}
+	if *encodeFlag && !*decodeFlag {
+		return encode(r)
+	}
+	return errors.New("must specify exactly one of -decode or -encode")
+}
+
+func decode(r io.Reader) error {
+	return errors.New("TODO: implement a decoder")
+}
+
+func encode(r io.Reader) error {
+	indexLocation := raczlib.IndexLocation(0)
+	switch *indexlocationFlag {
+	case "start":
+		indexLocation = raczlib.IndexLocationAtStart
+	case "end":
+		indexLocation = raczlib.IndexLocationAtEnd
+	default:
+		return errors.New("invalid -indexlocation")
+	}
+
+	if (*cchunksizeFlag != 0) && (*dchunksizeFlag != 0) {
+		return errors.New("must specify none or one of -cchunksize or -dchunksize")
+	} else if (*cchunksizeFlag == 0) && (*dchunksizeFlag == 0) {
+		*cchunksizeFlag = 65536 // 64 KiB.
+	}
+
+	switch *codecFlag {
+	case "zlib":
+		w := &raczlib.Writer{
+			Writer:        os.Stdout,
+			IndexLocation: indexLocation,
+			TempFile:      &bytes.Buffer{},
+			CPageSize:     *cpagesizeFlag,
+			CChunkSize:    *cchunksizeFlag,
+			DChunkSize:    *dchunksizeFlag,
+		}
+		if _, err := io.Copy(w, r); err != nil {
+			return err
+		}
+		return w.Close()
+	}
+	return errors.New("unsupported -codec")
+}
diff --git a/doc/changelog.md b/doc/changelog.md
index 205ad84..3d9501c 100644
--- a/doc/changelog.md
+++ b/doc/changelog.md
@@ -37,6 +37,7 @@
 - Added a `reset` method.
 - Added `peek_uxx`, `skip_fast` and `write_fast_uxx` methods.
 - Renamed some `read_uxx` methods as `read_uxx_as_uyy`.
+- Report image metadata such as ICCP and XMP.
 - Added I/O positions.
 - Added extra fields (uninitialized internal buffers) to structs.
 - Tweaked how marks and limits work.
@@ -68,4 +69,4 @@
 
 ---
 
-Updated on March 2019.
+Updated on April 2019.
diff --git a/doc/note/README.md b/doc/note/README.md
new file mode 100644
index 0000000..d1ec6fd
--- /dev/null
+++ b/doc/note/README.md
@@ -0,0 +1,2 @@
+This directory contains notes: short articles about various Wuffs concepts that
+aren't specific to any one file format or package.
diff --git a/doc/note/base38-and-fourcc.md b/doc/note/base38-and-fourcc.md
new file mode 100644
index 0000000..dc3c83f
--- /dev/null
+++ b/doc/note/base38-and-fourcc.md
@@ -0,0 +1,53 @@
+# Base38 and FourCC Codes
+
+Both of these encode a four-character string such as `"JPEG"` as a `uint32_t`
+value. Computers can compare two integer values faster than they can compare
+two arbitrary strings.
+
+Both schemes maintain ordering: if two four-character strings `s` and `t`
+satisfy `(s < t)`, and those strings have valid numerical encodings, then the
+numerical values also satisfy `(encoding(s) < encoding(t))`.
+
+
+## FourCC
+
+FourCC codes are not specific to Wuffs. For example, the AVI multimedia
+container format can hold various sub-formats, such as "H264" or "YV12",
+distinguished in the overall file format by their FourCC code.
+
+The FourCC encoding is the straightforward sequence of each character's ASCII
+encoding. The FourCC code for `"JPEG"` is `0x4A504547`, since `'J'` is `0x4A`,
+`'P'` is `0x50`, etc. This is essentially 8 bits for each character, 32 bits
+overall. The big-endian representation of this number is exactly the ASCII (and
+UTF-8) string `"JPEG"`.
+
+Other FourCC documentation sometimes use a little-endian convention, so that
+the `{0x4A, 0x50, 0x45, 0x47}` bytes on the wire for `"JPEG"` corresponds to
+the number `0x4745504A` (little-endian) instead of `0x4A504547` (big-endian).
+Wuffs uses the big-endian interpretation, as it maintains ordering.
+
+
+## Base38
+
+Base38 is a tighter encoding than FourCC, fitting four characters into 21 bits
+instead of 32 bits. This is achieved by using a smaller alphabet of 38 possible
+values (space, 0-9, ? or a-z), so that it cannot distinguish between e.g. an
+upper case 'X' and a lower case 'x'. There's also the happy coincidence that
+`38 ** 4` is slightly smaller than `2 ** 21`.
+
+The base38 encoding of `"JPEG"` is `0x122FF6`, which is `1191926`, which is
+`((21 * (38 ** 3)) + (27 * (38 ** 2)) + (16 * (38 ** 1)) + (18 * (38 ** 0)))`.
+
+Using only 21 bits means that we can use base38 values to partition the set of
+possible `uint32_t` values into file-format specific enumerations. Each package
+(i.e. Wuffs implementation of a specific file format) can define up to 1024
+different values in their own namespace, without conflicting with other
+packages (assuming that there aren't e.g. two `"JPEG"` Wuffs packages in the
+same library). The conventional `uint32_t` packing is:
+
+- Bit        31 is reserved (zero).
+- Bits 30 .. 10 are the base38 value, shifted by 10.
+- Bits  9 ..  0 are the enumeration value.
+
+For example, [quirk values](/doc/note/quirks.md) use this `((base38 << 10) |
+enumeration)` scheme.
diff --git a/doc/note/io-input-output.md b/doc/note/io-input-output.md
new file mode 100644
index 0000000..056ae43
--- /dev/null
+++ b/doc/note/io-input-output.md
@@ -0,0 +1,212 @@
+# I/O (Input / Output)
+
+Wuffs per se doesn't have the ability to read or write to files or network
+connections. Recall that Wuffs is a programming language for writing libraries,
+not applications, and that having fewer capabilities means that it's trivial to
+prove that you can't misuse a capability, even when given malicious input.
+
+Instead, the code that calls into Wuffs libraries is responsible for
+interfacing with e.g. the file system or the network system. An `io_buffer` is
+the mechanism for transferring data into and out of Wuffs libraries. For
+example, when decompressing gzip, there are two `io_buffer`'s: the caller fills
+a source buffer with e.g. the compressed file's contents and the callee (the
+Wuffs library) reads compressed bytes from that source buffer and writes
+decompressed bytes to a destination buffer.
+
+
+## I/O Buffers
+
+An `io_buffer` is a [slice](/doc/note/slices-and-tables.md) of bytes (the data,
+a `ptr` and `len`) with additional fields (the metadata): a read index (`ri`),
+a write index (`wi`), a position (`pos`) and whether or not it is `closed`.
+
+
+## Read Index and Write Index
+
+Writing to an `io_buffer`, e.g. copying from a file to a buffer, increments
+`wi`. The buffer is full for writing (no more can be written) when `wi` equals
+`len`. Writing does not have to fill a buffer before further processing.
+
+Reading from an `io_buffer`, e.g. copying from a buffer to a file, increments
+`ri`. The buffer is empty for reading (no more can be read) when `ri` equals
+`wi`. Reading does not have to empty a buffer before further processing.
+
+An invariant condition is that `((0 <= ri) and (ri <= wi) and (wi <= len))`.
+
+Having separate read and write indexes simplifies connecting a sequence of
+filters or processors with `io_buffer`'s, similar to connecting Unix processes
+with pipes. Each filter reads from the previous buffer and writes to the next
+buffer. Each buffer is written to by the previous filter and is read from by
+the next filter. There's no need to flip a buffer between reading and writing
+modes. Nonetheless, `io_buffer`'s are generally not thread-safe.
+
+Continuing the "decompressing gzip" example, the application would write to the
+source buffer by copying from e.g. `/dev/stdin`. The Wuffs library would read
+from the source buffer and write to the destination buffer. The application
+would read from the destination buffer by copying to e.g. `stdout`. Buffer
+space can be re-used, via compaction (see below), so that neither the source
+(`/dev/stdin`) or destination (`/dev/stdout`) data needs to be entirely in
+memory at any point.
+
+For example, an `io_buffer` of length 8 could have 4 bytes available to read
+and 1 byte available to write. If 1 byte was written, there would then be 5
+bytes available to read. Visually:
+
+```
+[.. .. .. .. .. .. .. ..]
+ |<- ri ->|           |  |
+ |<------- wi ------->|  |
+ |<-------- len -------->|
+```
+
+
+## Position
+
+An `io_buffer` is a sliding window into a stream of bytes. Its position (`pos`)
+is the number of bytes in the stream prior to the first element of the slice.
+The total number of bytes read from and written to the stream are therefore
+`(pos + ri)` and `(pos + wi)`.
+
+While every slice element is in-memory, the stream's prior bytes do not
+necessarily have to be in-memory now, or have been in-memory in the past. It is
+valid to open a file, seek to the 1000'th byte and start copying from there to
+an `io_buffer`, provided that `pos` was also initialized to 1000.
+
+
+## Closed-ness
+
+The `closed` field indicates that no further writes are expected to the
+`io_buffer`. When copying from a file to a buffer, `closed` means that we have
+reached EOF (End Of File).
+
+For example, decoding a particular file format might, at some point, expect at
+least another 4 bytes of data, but only 3 are available to read. If `closed` is
+false, this isn't necessarily an error, since an `io_buffer` holds only a
+partial view of the underlying data stream, and more data might be forthcoming
+but not yet buffered. If `closed` is true, it is definitely an error.
+
+
+## Undoing Reads and Writes
+
+It is possible to decrement `ri` or `wi`, undoing previous reads or writes,
+provided that the invariant `((0 <= ri) and (ri <= wi) and (wi <= len))` holds.
+For example, it can be faster on 64 bit (8 byte) systems, if buffer space is
+available, to write 8 bytes and then undo 1 byte than to write exactly 7 bytes.
+
+The Wuffs compiler enforces that, during a Wuffs function, `ri` and `wi` will
+never be decremented (by an undo operation) to be less than the initial values
+at the time of the call. When considering a function as a 'black box', the two
+indexes can only travel forward, and it is up to the application code (not
+Wuffs library) code to rewind the indexes (e.g. by compaction).
+
+Even though `ri` can not drop below its initial value, Wuffs code can still
+read the contents of the slice before `ri` (in sub-slice notation,
+`data[0:ri]`) and it should still contain  the `(pos + 0)`th, `(pos + 1)`th,
+etc. byte of the stream.
+
+The contents of the slice after `wi` (in sub-slice notation, `data[wi:len]`)
+are undefined, and code should not rely on its values. When passing an
+`io_buffer` into a function, that function is free to modify anything in
+`data[wi:len]`, for either value of `wi` before or after the function returns.
+
+
+## Compaction
+
+Compacting an `io_buffer` moves any written but unread bytes (those in
+`data[ri:wi]`) to the start of the buffer, and updates the metadata fields
+`ri`, `wi` and `pos`. Equivalently, it moves the sliding window that is the
+`io_buffer` as far forward as possible along the stream.
+
+This generally increases `(len - wi)`, the number of bytes available for
+writing, allowing for re-using the allocated buffer memory (the data slice).
+
+Suppose that the underlying data stream's `i`th byte has value `i`, and that we
+start with `ri`, `wi` and `pos` were `3`, `7` and `20`. Compaction will
+subtract 3 from the first two and add 3 to the last, so that the new `ri`, `wi`
+and `pos` are `0`, `4` and `23`. Note that `len`, `(pos + ri)` and `(pos + wi)`
+are all unchanged.
+
+Here are two equivalent visualizations of before and after compaction. The `xx`
+means a byte whose value is undefined (as it is at or past `wi`).
+
+The first visualization is where the slice is fixed and its contents (its view
+of the stream) moves relative to the slice:
+
+```
+Before:
+[20 21 22 23 24 25 26 xx]
+ |<- ri ->|           |  |
+ |<------- wi ------->|  |
+ |<-------- len -------->|
+
+After:
+[23 24 25 26 xx xx xx xx]
+ ||          |           |
+ |<-- wi --->|           |
+ |<-------- len -------->|
+```
+
+The second visualization is where the stream (and its contents) is fixed and
+the slice (the sliding window) moves relative to the stream:
+
+```
+                           pos+ri      pos+wi
+                           |           |
+Before:          [20 21 22 23 24 25 26 xx]
+Stream: ... 18 19 20 21 22 23 24 25 26 27 27 28 29 30 31 ...
+After:                    [23 24 25 26 xx xx xx xx]
+                           |           |
+                           pos+ri      pos+wi
+```
+
+
+## Seeking and I/O Positions
+
+Recall that Wuffs code has limited capabilities, and cannot seek in the
+underlying I/O data streams per se. When it needs to seek (e.g. when jumping
+between video frames), it will typically provide an "I/O position", a
+`uint64_t` value, via some package-specific API. The application (the caller of
+the Wuffs code) is then responsible for configuring an `io_buffer` whose
+`(pos + ri)` or `(pos + wi)` value, depending on whether we're reading or
+writing, is at that "I/O position".
+
+If the underlying file (or equivalent) isn't seekable, e.g. it's `/dev/stdin`
+instead of a regular file, then the request cannot be satisfied. The
+application should then decide whether that error is recoverable or fatal. This
+is the application's responsibility, not the library's, as the application
+usually has more context to make that decision.
+
+If that "I/O position" is already within the sliding window, it might not be
+necessary to seek in the underlying file, as it may be possible to e.g. simply
+decrement `ri` to reach a target `(pos + ri)`, for the reading case. Otherwise,
+the typical process is:
+
+1. Set `ri`, `wi` and `pos` to `0`, `0` and that "I/O position". This discards
+   any buffered data (but does not free the buffer's memory).
+2. Seek in the underlying file to that same "I/O position".
+3. Copy from the underlying file to the `io_buffer`, incrementing `wi`.
+
+Whether or not it was necessary to seek and copy from the underlying file, when
+calling back into the Wuffs library, it typically checks that the `io_buffer`'s
+`(pos + ri)` is now at the expected "I/O position".
+
+
+## I/O Reader and I/O Writer
+
+An `io_buffer` is the mechanism for transferring data between the application
+and the Wuffs library. Application code can manipulate an `io_buffer`'s fields
+as it wishes (but is responsible for maintaining the invariant condition).
+Wuffs library code places a further restriction that `io_buffer`s are used
+exclusively either for reading or for writing, as optimizing incremental access
+to an `io_buffer`'s data, while enforcing invariants, is simpler when only one
+of `ri` and `wi` can vary.
+
+Wuffs code therefore refers to either a `base.io_reader` or `base.io_writer`,
+both of which are essentially the same type (an `io_buffer`) with different
+methods. Wuffs code does not reference an `io_buffer` directly.
+
+
+## Binding
+
+TODO: discuss `io_bind`, which temporarily adapts a slice of bytes into an
+`io_buffer`.
diff --git a/doc/note/quirks.md b/doc/note/quirks.md
new file mode 100644
index 0000000..f919da3
--- /dev/null
+++ b/doc/note/quirks.md
@@ -0,0 +1,34 @@
+# Quirks
+
+Quirks are configuration options that let Wuffs more closely match other codec
+implementations.
+
+Some codecs and file formats are more tightly specified than others. When a
+spec doesn't fully dictate how to treat malformed files, different codec
+implementations have disagreed on whether to accept it (e.g. whether they
+follow Postel's Principle of "be liberal in what you accept") and if so, how to
+interpret it. Some implementations also raise those decisions to the
+application level, not the library level: "provide mechanism, not policy".
+
+For example, in an animated GIF image, the first frame might not fill out the
+entire image, and different implementations choose a different default color
+for the outside pixels: opaque black, transparent black, or something else.
+
+Wuffs, out of the box, makes particular choices (typically mimicking the de
+facto canonical implementation), but enabling various quirks result in
+different choices. In particular, quirks are useful in regression testing that
+Wuffs and another implementation produce the same output for the same input,
+even for malformed input.
+
+
+## Wuffs API
+
+Each quirk is assigned a `uint32_t` value, packed using the [base38 namespace
+convention](/doc/note/base38-and-fourcc.md). Decoders and encoders can have a
+`set_quirk_enabled!(quirk base.u32, enabled base.bool)` method whose first
+argument is this `uint32_t` value.
+
+For example, the base38 encoding of `"gif "` is `0xF8586`, so that the
+GIF-specific quirks have a `uint32_t` value of `((0xF8586 << 10) | n)`, for
+some small integer `n`. The generated `C` language file defines human-readable
+names for those constant values.
diff --git a/example/gifplayer/gifplayer.c b/example/gifplayer/gifplayer.c
index 74ff1be..703b968 100644
--- a/example/gifplayer/gifplayer.c
+++ b/example/gifplayer/gifplayer.c
@@ -130,6 +130,7 @@
 const char* reset_color = "\x1B[0m";
 
 bool color_flag = false;
+bool quirk_honor_background_color_flag = false;
 const int stdout_fd = 1;
 
 static inline uint32_t load_u32le(uint8_t* p) {
@@ -138,7 +139,8 @@
 }
 
 void restore_background(wuffs_base__pixel_buffer* pb,
-                        wuffs_base__rect_ie_u32 bounds) {
+                        wuffs_base__rect_ie_u32 bounds,
+                        wuffs_base__color_u32_argb_premul background_color) {
   size_t width = wuffs_base__pixel_config__width(&pb->pixcfg);
   size_t y;
   for (y = bounds.min_incl_y; y < bounds.max_excl_y; y++) {
@@ -146,7 +148,7 @@
     wuffs_base__color_u32_argb_premul* d =
         curr_dst_buffer + (y * width) + bounds.min_incl_x;
     for (x = bounds.min_incl_x; x < bounds.max_excl_x; x++) {
-      *d++ = 0;
+      *d++ = background_color;
     }
   }
 }
@@ -296,6 +298,11 @@
     return status;
   }
 
+  if (quirk_honor_background_color_flag) {
+    wuffs_gif__decoder__set_quirk_enabled(
+        &dec, wuffs_gif__quirk_honor_background_color, true);
+  }
+
   wuffs_base__io_buffer src;
   src.data.ptr = src_buffer;
   src.data.len = src_len;
@@ -303,11 +310,10 @@
   src.meta.ri = 0;
   src.meta.pos = 0;
   src.meta.closed = true;
-  wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
 
   if (first_play) {
     wuffs_base__image_config ic = {0};
-    status = wuffs_gif__decoder__decode_image_config(&dec, &ic, src_reader);
+    status = wuffs_gif__decoder__decode_image_config(&dec, &ic, &src);
     if (status) {
       return status;
     }
@@ -334,13 +340,12 @@
       return status;
     }
     memset(pixbuf.ptr, 0, pixbuf.len);
-    memset(curr_dst_buffer, 0, dst_len);
   }
 
   while (1) {
     wuffs_base__frame_config fc = {0};
     wuffs_base__status status =
-        wuffs_gif__decoder__decode_frame_config(&dec, &fc, src_reader);
+        wuffs_gif__decoder__decode_frame_config(&dec, &fc, &src);
     if (status) {
       if (status == wuffs_base__warning__end_of_data) {
         break;
@@ -348,6 +353,16 @@
       return status;
     }
 
+    if (wuffs_base__frame_config__index(&fc) == 0) {
+      wuffs_base__color_u32_argb_premul background_color =
+          wuffs_base__frame_config__background_color(&fc);
+      size_t i;
+      size_t n = dst_len / sizeof(wuffs_base__color_u32_argb_premul);
+      for (i = 0; i < n; i++) {
+        curr_dst_buffer[i] = background_color;
+      }
+    }
+
     switch (wuffs_base__frame_config__disposal(&fc)) {
       case WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_PREVIOUS: {
         memcpy(prev_dst_buffer, curr_dst_buffer, dst_len);
@@ -355,8 +370,7 @@
       }
     }
 
-    status =
-        wuffs_gif__decoder__decode_frame(&dec, &pb, src_reader, workbuf, NULL);
+    status = wuffs_gif__decoder__decode_frame(&dec, &pb, &src, workbuf, NULL);
     if (status) {
       if (status == wuffs_base__warning__end_of_data) {
         break;
@@ -370,7 +384,8 @@
 
     switch (wuffs_base__frame_config__disposal(&fc)) {
       case WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_BACKGROUND: {
-        restore_background(&pb, wuffs_base__frame_config__bounds(&fc));
+        restore_background(&pb, wuffs_base__frame_config__bounds(&fc),
+                           wuffs_base__frame_config__background_color(&fc));
         break;
       }
       case WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_PREVIOUS: {
@@ -423,6 +438,9 @@
     if (!strcmp(argv[i], "-color")) {
       color_flag = true;
     }
+    if (!strcmp(argv[i], "-quirk_honor_background_color")) {
+      quirk_honor_background_color_flag = true;
+    }
   }
 
   const char* status = read_stdin();
diff --git a/example/library/library.c b/example/library/library.c
index 8726ca3..f8e258a 100644
--- a/example/library/library.c
+++ b/example/library/library.c
@@ -93,9 +93,6 @@
   src.meta.pos = 0;
   src.meta.closed = true;
 
-  wuffs_base__io_writer dst_writer = wuffs_base__io_buffer__writer(&dst);
-  wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
-
   wuffs_gzip__decoder* dec =
       (wuffs_gzip__decoder*)(calloc(sizeof__wuffs_gzip__decoder(), 1));
   if (!dec) {
@@ -109,7 +106,7 @@
     return status;
   }
   status = wuffs_gzip__decoder__decode_io_writer(
-      dec, dst_writer, src_reader,
+      dec, &dst, &src,
       wuffs_base__make_slice_u8(work_buffer, WORK_BUFFER_SIZE));
   if (status) {
     free(dec);
diff --git a/example/zcat/zcat.c b/example/zcat/zcat.c
index c2bf06a..20d7fe4 100644
--- a/example/zcat/zcat.c
+++ b/example/zcat/zcat.c
@@ -124,8 +124,7 @@
 
     while (true) {
       status = wuffs_gzip__decoder__decode_io_writer(
-          &dec, wuffs_base__io_buffer__writer(&dst),
-          wuffs_base__io_buffer__reader(&src),
+          &dec, &dst, &src,
           wuffs_base__make_slice_u8(work_buffer, WORK_BUFFER_SIZE));
 
       if (dst.meta.wi) {
diff --git a/fuzz/c/fuzzlib/fuzzlib.c b/fuzz/c/fuzzlib/fuzzlib.c
index 03050f9..a91e1e4 100644
--- a/fuzz/c/fuzzlib/fuzzlib.c
+++ b/fuzz/c/fuzzlib/fuzzlib.c
@@ -27,7 +27,7 @@
   *intentional_segfault_ptr = 0;
 }
 
-const char* fuzz(wuffs_base__io_reader src_reader, uint32_t hash);
+const char* fuzz(wuffs_base__io_buffer* src, uint32_t hash);
 
 static const char* llvmFuzzerTestOneInput(const uint8_t* data, size_t size) {
   // Hash input as per https://en.wikipedia.org/wiki/Jenkins_hash_function
@@ -55,7 +55,7 @@
       }),
   });
 
-  const char* msg = fuzz(wuffs_base__io_buffer__reader(&src), hash);
+  const char* msg = fuzz(&src, hash);
   if (msg && strstr(msg, "internal error:")) {
     fprintf(stderr, "internal errors shouldn't occur: \"%s\"\n", msg);
     intentional_segfault();
diff --git a/fuzz/c/std/gif_fuzzer.c b/fuzz/c/std/gif_fuzzer.c
index c3ce2fd..2858c1c 100644
--- a/fuzz/c/std/gif_fuzzer.c
+++ b/fuzz/c/std/gif_fuzzer.c
@@ -48,7 +48,7 @@
 #include "../../../release/c/wuffs-unsupported-snapshot.c"
 #include "../fuzzlib/fuzzlib.c"
 
-const char* fuzz(wuffs_base__io_reader src_reader, uint32_t hash) {
+const char* fuzz(wuffs_base__io_buffer* src, uint32_t hash) {
   const char* ret = NULL;
   wuffs_base__slice_u8 pixbuf = ((wuffs_base__slice_u8){});
   wuffs_base__slice_u8 workbuf = ((wuffs_base__slice_u8){});
@@ -67,7 +67,7 @@
     }
 
     wuffs_base__image_config ic = ((wuffs_base__image_config){});
-    status = wuffs_gif__decoder__decode_image_config(&dec, &ic, src_reader);
+    status = wuffs_gif__decoder__decode_image_config(&dec, &ic, src);
     if (status) {
       ret = status;
       goto exit;
@@ -115,7 +115,7 @@
     bool seen_ok = false;
     while (true) {
       wuffs_base__frame_config fc = ((wuffs_base__frame_config){});
-      status = wuffs_gif__decoder__decode_frame_config(&dec, &fc, src_reader);
+      status = wuffs_gif__decoder__decode_frame_config(&dec, &fc, src);
       if (status) {
         if ((status != wuffs_base__warning__end_of_data) || !seen_ok) {
           ret = status;
@@ -123,8 +123,7 @@
         goto exit;
       }
 
-      status = wuffs_gif__decoder__decode_frame(&dec, &pb, src_reader, workbuf,
-                                                NULL);
+      status = wuffs_gif__decoder__decode_frame(&dec, &pb, src, workbuf, NULL);
 
       wuffs_base__rect_ie_u32 frame_rect =
           wuffs_base__frame_config__bounds(&fc);
diff --git a/fuzz/c/std/zlib_fuzzer.c b/fuzz/c/std/zlib_fuzzer.c
index ffded01..b452582 100644
--- a/fuzz/c/std/zlib_fuzzer.c
+++ b/fuzz/c/std/zlib_fuzzer.c
@@ -60,7 +60,7 @@
 uint8_t work_buffer[1];
 #endif
 
-const char* fuzz(wuffs_base__io_reader src_reader, uint32_t hash) {
+const char* fuzz(wuffs_base__io_buffer* src, uint32_t hash) {
   wuffs_zlib__decoder dec;
   const char* status = wuffs_zlib__decoder__initialize(
       &dec, sizeof dec, WUFFS_VERSION,
@@ -81,11 +81,10 @@
           .len = DST_BUFFER_SIZE,
       }),
   });
-  wuffs_base__io_writer dst_writer = wuffs_base__io_buffer__writer(&dst);
 
   while (true) {
     dst.meta.wi = 0;
-    status = wuffs_zlib__decoder__decode_io_writer(&dec, dst_writer, src_reader,
+    status = wuffs_zlib__decoder__decode_io_writer(&dec, &dst, src,
                                                    ((wuffs_base__slice_u8){
                                                        .ptr = work_buffer,
                                                        .len = WORK_BUFFER_SIZE,
diff --git a/go.mod b/go.mod
index 3279328..4ec9849 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,3 @@
 module github.com/google/wuffs
 
-go 1.13
+go 1.12
diff --git a/internal/cgen/base/core-public.h b/internal/cgen/base/core-public.h
index dc7e7fb..f642fb7 100644
--- a/internal/cgen/base/core-public.h
+++ b/internal/cgen/base/core-public.h
@@ -197,6 +197,12 @@
 
 // --------
 
+// FourCC constants.
+
+// !! INSERT FourCCs.
+
+// --------
+
 // Flicks are a unit of time. One flick (frame-tick) is 1 / 705_600_000 of a
 // second. See https://github.com/OculusVR/Flicks
 typedef int64_t wuffs_base__flicks;
diff --git a/internal/cgen/base/image-impl.c b/internal/cgen/base/image-impl.c
index 6674c4a..7f62677 100644
--- a/internal/cgen/base/image-impl.c
+++ b/internal/cgen/base/image-impl.c
@@ -29,6 +29,61 @@
 }
 
 static uint64_t  //
+wuffs_base__pixel_swizzler__copy_3_1(wuffs_base__slice_u8 dst,
+                                     wuffs_base__slice_u8 dst_palette,
+                                     wuffs_base__slice_u8 src) {
+  if (dst_palette.len != 1024) {
+    return 0;
+  }
+  size_t dst_len3 = dst.len / 3;
+  size_t len = dst_len3 < src.len ? dst_len3 : src.len;
+  uint8_t* d = dst.ptr;
+  uint8_t* s = src.ptr;
+  size_t n = len;
+
+  // N is the loop unroll count.
+  const int N = 4;
+
+  // The comparison in the while condition is ">", not ">=", because with ">=",
+  // the last 4-byte store could write past the end of the dst slice.
+  //
+  // Each 4-byte store writes one too many bytes, but a subsequent store will
+  // overwrite that with the correct byte. There is always another store,
+  // whether a 4-byte store in this loop or a 1-byte store in the next loop.
+  while (n > N) {
+    wuffs_base__store_u32le(
+        d + (0 * 3),
+        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[0]) * 4)));
+    wuffs_base__store_u32le(
+        d + (1 * 3),
+        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[1]) * 4)));
+    wuffs_base__store_u32le(
+        d + (2 * 3),
+        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[2]) * 4)));
+    wuffs_base__store_u32le(
+        d + (3 * 3),
+        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[3]) * 4)));
+
+    s += 1 * N;
+    d += 3 * N;
+    n -= (size_t)(1 * N);
+  }
+
+  while (n >= 1) {
+    uint32_t color =
+        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[0]) * 4));
+    d[0] = (uint8_t)(color >> 0);
+    d[1] = (uint8_t)(color >> 8);
+    d[2] = (uint8_t)(color >> 16);
+
+    s += 1 * 1;
+    d += 3 * 1;
+    n -= (size_t)(1 * 1);
+  }
+
+  return len;
+}
+static uint64_t  //
 wuffs_base__pixel_swizzler__copy_4_1(wuffs_base__slice_u8 dst,
                                      wuffs_base__slice_u8 dst_palette,
                                      wuffs_base__slice_u8 src) {
@@ -39,8 +94,9 @@
   size_t len = dst_len4 < src.len ? dst_len4 : src.len;
   uint8_t* d = dst.ptr;
   uint8_t* s = src.ptr;
-
   size_t n = len;
+
+  // N is the loop unroll count.
   const int N = 4;
 
   while (n >= N) {
@@ -98,14 +154,14 @@
   return len4 * 4;
 }
 
-void  //
+wuffs_base__status  //
 wuffs_base__pixel_swizzler__prepare(wuffs_base__pixel_swizzler* p,
                                     wuffs_base__pixel_format dst_format,
                                     wuffs_base__slice_u8 dst_palette,
                                     wuffs_base__pixel_format src_format,
                                     wuffs_base__slice_u8 src_palette) {
   if (!p) {
-    return;
+    return wuffs_base__error__bad_receiver;
   }
 
   // TODO: support many more formats.
@@ -125,6 +181,13 @@
           }
           func = wuffs_base__pixel_swizzler__copy_1_1;
           break;
+        case WUFFS_BASE__PIXEL_FORMAT__BGR:
+          if (wuffs_base__slice_u8__copy_from_slice(dst_palette, src_palette) !=
+              1024) {
+            break;
+          }
+          func = wuffs_base__pixel_swizzler__copy_3_1;
+          break;
         case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL:
         case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL:
         case WUFFS_BASE__PIXEL_FORMAT__BGRA_BINARY:
@@ -134,6 +197,13 @@
           }
           func = wuffs_base__pixel_swizzler__copy_4_1;
           break;
+        case WUFFS_BASE__PIXEL_FORMAT__RGB:
+          if (wuffs_base__pixel_swizzler__swap_rgbx_bgrx(dst_palette,
+                                                         src_palette) != 1024) {
+            break;
+          }
+          func = wuffs_base__pixel_swizzler__copy_3_1;
+          break;
         case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL:
         case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL:
         case WUFFS_BASE__PIXEL_FORMAT__RGBA_BINARY:
@@ -153,13 +223,15 @@
   }
 
   p->private_impl.func = func;
+  return func ? NULL : wuffs_base__error__unsupported_option;
 }
 
 uint64_t  //
-wuffs_base__pixel_swizzler__swizzle_packed(wuffs_base__pixel_swizzler* p,
-                                           wuffs_base__slice_u8 dst,
-                                           wuffs_base__slice_u8 dst_palette,
-                                           wuffs_base__slice_u8 src) {
+wuffs_base__pixel_swizzler__swizzle_interleaved(
+    const wuffs_base__pixel_swizzler* p,
+    wuffs_base__slice_u8 dst,
+    wuffs_base__slice_u8 dst_palette,
+    wuffs_base__slice_u8 src) {
   if (p && p->private_impl.func) {
     return (*(p->private_impl.func))(dst, dst_palette, src);
   }
diff --git a/internal/cgen/base/image-public.h b/internal/cgen/base/image-public.h
index f452b3b..0a18365 100644
--- a/internal/cgen/base/image-public.h
+++ b/internal/cgen/base/image-public.h
@@ -33,9 +33,9 @@
 //  - bit        20 indicates big-endian/MSB-first (as opposed to little/LSB).
 //  - bit        19 indicates floating point (as opposed to integer).
 //  - bit        18 indicates palette-indexed. The number-of-planes (the next
-//                  field) will be zero, as the format is considered packed,
+//                  field) will be 0, as the format is considered interleaved,
 //                  but the 8-bit N-BGRA color data is stored in plane 3.
-//  - bits 17 .. 16 are the number of planes, minus 1. Zero means packed.
+//  - bits 17 .. 16 are the number of planes, minus 1. Zero means interleaved.
 //  - bits 15 .. 12 encodes the number of bits (depth) in the 3rd channel.
 //  - bits 11 ..  8 encodes the number of bits (depth) in the 2nd channel.
 //  - bits  7 ..  4 encodes the number of bits (depth) in the 1st channel.
@@ -52,14 +52,14 @@
 // channels: cyan, magenta, yellow, black).
 //
 // For direct formats with N > 1 channels, those channels can be laid out in
-// either 1 (packed) or N (planar) planes. For example, RGBA data is usually
-// packed, but YCbCr data is usually planar, due to chroma subsampling (for
-// details, see the wuffs_base__pixel_subsampling type).
+// either 1 (interleaved) or N (planar) planes. For example, RGBA data is
+// usually interleaved, but YCbCr data is usually planar, due to chroma
+// subsampling (for details, see the wuffs_base__pixel_subsampling type).
 //
 // For indexed formats, the palette (always 256 × 4 bytes) holds 8 bits per
 // channel non-alpha-premultiplied BGRA color data. There is only 1 plane (for
-// the index), as the format is considered packed. Plane 0 holds the per-pixel
-// indices. Plane 3 is re-purposed to hold the per-index colors.
+// the index), as the format is considered interleaved. Plane 0 holds the
+// per-pixel indices. Plane 3 is re-purposed to hold the per-index colors.
 //
 // The color field is encoded in 3 bits:
 //  - 0 means                   A (Alpha).
@@ -73,8 +73,8 @@
 //
 // In Wuffs, channels are given in memory order (also known as byte order),
 // regardless of endianness, since the C type for the pixel data is an array of
-// bytes, not an array of uint32_t. For example, packed BGRA with 8 bits per
-// channel means that the bytes in memory are always Blue, Green, Red then
+// bytes, not an array of uint32_t. For example, interleaved BGRA with 8 bits
+// per channel means that the bytes in memory are always Blue, Green, Red then
 // Alpha. On big-endian systems, that is the uint32_t 0xBBGGRRAA. On
 // little-endian, 0xAARRGGBB.
 //
@@ -107,15 +107,15 @@
 //
 // For example, wuffs_base__pixel_format 0x5510BBBB is a natural format for
 // decoding a PNG image - network byte order (also known as big-endian),
-// packed, non-premultiplied alpha - that happens to be 16-bit-depth truecolor
-// with alpha (RGBA). In memory order:
+// interleaved, non-premultiplied alpha - that happens to be 16-bit-depth
+// truecolor with alpha (RGBA). In memory order:
 //
 //  ptr+0  ptr+1  ptr+2  ptr+3  ptr+4  ptr+5  ptr+6  ptr+7
 //  Rhi    Rlo    Ghi    Glo    Bhi    Blo    Ahi    Alo
 //
 // For example, the value wuffs_base__pixel_format 0x40000565 means BGR with no
-// alpha or padding, 5/6/5 bits for blue/green/red, packed 2 bytes per pixel,
-// laid out LSB-first in memory order:
+// alpha or padding, 5/6/5 bits for blue/green/red, interleaved 2 bytes per
+// pixel, laid out LSB-first in memory order:
 //
 //  ptr+0...........  ptr+1...........
 //  MSB          LSB  MSB          LSB
@@ -200,8 +200,8 @@
   return f != 0;
 }
 
-// wuffs_base__pixel_format__bits_per_pixel returns, for packed pixel formats,
-// the number of bits per pixel. It returns 0 for planar pixel formats.
+// wuffs_base__pixel_format__bits_per_pixel returns the number of bits per
+// pixel for interleaved pixel formats, and returns 0 for planar pixel formats.
 static inline uint32_t  //
 wuffs_base__pixel_format__bits_per_pixel(wuffs_base__pixel_format f) {
   if (((f >> 16) & 0x03) != 0) {
@@ -219,7 +219,7 @@
 }
 
 static inline bool  //
-wuffs_base__pixel_format__is_packed(wuffs_base__pixel_format f) {
+wuffs_base__pixel_format__is_interleaved(wuffs_base__pixel_format f) {
   return ((f >> 16) & 0x03) == 0;
 }
 
@@ -245,7 +245,7 @@
 // plane p. For a depth of 8 bits (1 byte), the p'th plane's sample starts at
 // (planes[p].ptr + (j * planes[p].stride) + i).
 //
-// For packed pixel formats, the mapping is trivial: i = x and j = y. For
+// For interleaved pixel formats, the mapping is trivial: i = x and j = y. For
 // planar pixel formats, the mapping can differ due to chroma subsampling. For
 // example, consider a three plane YCbCr pixel format with 4:2:2 subsampling.
 // For the luma (Y) channel, there is one sample for every pixel, but for the
@@ -336,13 +336,13 @@
                   uint32_t width,
                   uint32_t height);
   inline void invalidate();
-  inline bool is_valid();
-  inline wuffs_base__pixel_format pixel_format();
-  inline wuffs_base__pixel_subsampling pixel_subsampling();
-  inline wuffs_base__rect_ie_u32 bounds();
-  inline uint32_t width();
-  inline uint32_t height();
-  inline uint64_t pixbuf_len();
+  inline bool is_valid() const;
+  inline wuffs_base__pixel_format pixel_format() const;
+  inline wuffs_base__pixel_subsampling pixel_subsampling() const;
+  inline wuffs_base__rect_ie_u32 bounds() const;
+  inline uint32_t width() const;
+  inline uint32_t height() const;
+  inline uint64_t pixbuf_len() const;
 #endif  // __cplusplus
 
 } wuffs_base__pixel_config;
@@ -396,22 +396,22 @@
 }
 
 static inline bool  //
-wuffs_base__pixel_config__is_valid(wuffs_base__pixel_config* c) {
+wuffs_base__pixel_config__is_valid(const wuffs_base__pixel_config* c) {
   return c && c->private_impl.pixfmt;
 }
 
 static inline wuffs_base__pixel_format  //
-wuffs_base__pixel_config__pixel_format(wuffs_base__pixel_config* c) {
+wuffs_base__pixel_config__pixel_format(const wuffs_base__pixel_config* c) {
   return c ? c->private_impl.pixfmt : 0;
 }
 
 static inline wuffs_base__pixel_subsampling  //
-wuffs_base__pixel_config__pixel_subsampling(wuffs_base__pixel_config* c) {
+wuffs_base__pixel_config__pixel_subsampling(const wuffs_base__pixel_config* c) {
   return c ? c->private_impl.pixsub : 0;
 }
 
 static inline wuffs_base__rect_ie_u32  //
-wuffs_base__pixel_config__bounds(wuffs_base__pixel_config* c) {
+wuffs_base__pixel_config__bounds(const wuffs_base__pixel_config* c) {
   if (c) {
     wuffs_base__rect_ie_u32 ret;
     ret.min_incl_x = 0;
@@ -430,20 +430,20 @@
 }
 
 static inline uint32_t  //
-wuffs_base__pixel_config__width(wuffs_base__pixel_config* c) {
+wuffs_base__pixel_config__width(const wuffs_base__pixel_config* c) {
   return c ? c->private_impl.width : 0;
 }
 
 static inline uint32_t  //
-wuffs_base__pixel_config__height(wuffs_base__pixel_config* c) {
+wuffs_base__pixel_config__height(const wuffs_base__pixel_config* c) {
   return c ? c->private_impl.height : 0;
 }
 
-// TODO: this is the right API for planar (not packed) pixbufs? Should it allow
-// decoding into a color model different from the format's intrinsic one? For
-// example, decoding a JPEG image straight to RGBA instead of to YCbCr?
+// TODO: this is the right API for planar (not interleaved) pixbufs? Should it
+// allow decoding into a color model different from the format's intrinsic one?
+// For example, decoding a JPEG image straight to RGBA instead of to YCbCr?
 static inline uint64_t  //
-wuffs_base__pixel_config__pixbuf_len(wuffs_base__pixel_config* c) {
+wuffs_base__pixel_config__pixbuf_len(const wuffs_base__pixel_config* c) {
   if (!c) {
     return 0;
   }
@@ -492,37 +492,37 @@
 }
 
 inline bool  //
-wuffs_base__pixel_config::is_valid() {
+wuffs_base__pixel_config::is_valid() const {
   return wuffs_base__pixel_config__is_valid(this);
 }
 
 inline wuffs_base__pixel_format  //
-wuffs_base__pixel_config::pixel_format() {
+wuffs_base__pixel_config::pixel_format() const {
   return wuffs_base__pixel_config__pixel_format(this);
 }
 
 inline wuffs_base__pixel_subsampling  //
-wuffs_base__pixel_config::pixel_subsampling() {
+wuffs_base__pixel_config::pixel_subsampling() const {
   return wuffs_base__pixel_config__pixel_subsampling(this);
 }
 
 inline wuffs_base__rect_ie_u32  //
-wuffs_base__pixel_config::bounds() {
+wuffs_base__pixel_config::bounds() const {
   return wuffs_base__pixel_config__bounds(this);
 }
 
 inline uint32_t  //
-wuffs_base__pixel_config::width() {
+wuffs_base__pixel_config::width() const {
   return wuffs_base__pixel_config__width(this);
 }
 
 inline uint32_t  //
-wuffs_base__pixel_config::height() {
+wuffs_base__pixel_config::height() const {
   return wuffs_base__pixel_config__height(this);
 }
 
 inline uint64_t  //
-wuffs_base__pixel_config::pixbuf_len() {
+wuffs_base__pixel_config::pixbuf_len() const {
   return wuffs_base__pixel_config__pixbuf_len(this);
 }
 
@@ -548,9 +548,9 @@
                   uint64_t first_frame_io_position,
                   bool first_frame_is_opaque);
   inline void invalidate();
-  inline bool is_valid();
-  inline uint64_t first_frame_io_position();
-  inline bool first_frame_is_opaque();
+  inline bool is_valid() const;
+  inline uint64_t first_frame_io_position() const;
+  inline bool first_frame_is_opaque() const;
 #endif  // __cplusplus
 
 } wuffs_base__image_config;
@@ -607,17 +607,19 @@
 }
 
 static inline bool  //
-wuffs_base__image_config__is_valid(wuffs_base__image_config* c) {
+wuffs_base__image_config__is_valid(const wuffs_base__image_config* c) {
   return c && wuffs_base__pixel_config__is_valid(&(c->pixcfg));
 }
 
 static inline uint64_t  //
-wuffs_base__image_config__first_frame_io_position(wuffs_base__image_config* c) {
+wuffs_base__image_config__first_frame_io_position(
+    const wuffs_base__image_config* c) {
   return c ? c->private_impl.first_frame_io_position : 0;
 }
 
 static inline bool  //
-wuffs_base__image_config__first_frame_is_opaque(wuffs_base__image_config* c) {
+wuffs_base__image_config__first_frame_is_opaque(
+    const wuffs_base__image_config* c) {
   return c ? c->private_impl.first_frame_is_opaque : false;
 }
 
@@ -640,17 +642,17 @@
 }
 
 inline bool  //
-wuffs_base__image_config::is_valid() {
+wuffs_base__image_config::is_valid() const {
   return wuffs_base__image_config__is_valid(this);
 }
 
 inline uint64_t  //
-wuffs_base__image_config::first_frame_io_position() {
+wuffs_base__image_config::first_frame_io_position() const {
   return wuffs_base__image_config__first_frame_io_position(this);
 }
 
 inline bool  //
-wuffs_base__image_config::first_frame_is_opaque() {
+wuffs_base__image_config::first_frame_is_opaque() const {
   return wuffs_base__image_config__first_frame_is_opaque(this);
 }
 
@@ -706,6 +708,7 @@
     uint64_t io_position;
     wuffs_base__animation_blend blend;
     wuffs_base__animation_disposal disposal;
+    wuffs_base__color_u32_argb_premul background_color;
   } private_impl;
 
 #ifdef __cplusplus
@@ -714,15 +717,17 @@
                      uint64_t index,
                      uint64_t io_position,
                      wuffs_base__animation_blend blend,
-                     wuffs_base__animation_disposal disposal);
-  inline wuffs_base__rect_ie_u32 bounds();
-  inline uint32_t width();
-  inline uint32_t height();
-  inline wuffs_base__flicks duration();
-  inline uint64_t index();
-  inline uint64_t io_position();
-  inline wuffs_base__animation_blend blend();
-  inline wuffs_base__animation_disposal disposal();
+                     wuffs_base__animation_disposal disposal,
+                     wuffs_base__color_u32_argb_premul background_color);
+  inline wuffs_base__rect_ie_u32 bounds() const;
+  inline uint32_t width() const;
+  inline uint32_t height() const;
+  inline wuffs_base__flicks duration() const;
+  inline uint64_t index() const;
+  inline uint64_t io_position() const;
+  inline wuffs_base__animation_blend blend() const;
+  inline wuffs_base__animation_disposal disposal() const;
+  inline wuffs_base__color_u32_argb_premul background_color() const;
 #endif  // __cplusplus
 
 } wuffs_base__frame_config;
@@ -740,13 +745,15 @@
 }
 
 static inline void  //
-wuffs_base__frame_config__update(wuffs_base__frame_config* c,
-                                 wuffs_base__rect_ie_u32 bounds,
-                                 wuffs_base__flicks duration,
-                                 uint64_t index,
-                                 uint64_t io_position,
-                                 wuffs_base__animation_blend blend,
-                                 wuffs_base__animation_disposal disposal) {
+wuffs_base__frame_config__update(
+    wuffs_base__frame_config* c,
+    wuffs_base__rect_ie_u32 bounds,
+    wuffs_base__flicks duration,
+    uint64_t index,
+    uint64_t io_position,
+    wuffs_base__animation_blend blend,
+    wuffs_base__animation_disposal disposal,
+    wuffs_base__color_u32_argb_premul background_color) {
   if (!c) {
     return;
   }
@@ -757,10 +764,11 @@
   c->private_impl.io_position = io_position;
   c->private_impl.blend = blend;
   c->private_impl.disposal = disposal;
+  c->private_impl.background_color = background_color;
 }
 
 static inline wuffs_base__rect_ie_u32  //
-wuffs_base__frame_config__bounds(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__bounds(const wuffs_base__frame_config* c) {
   if (c) {
     return c->private_impl.bounds;
   }
@@ -774,103 +782,115 @@
 }
 
 static inline uint32_t  //
-wuffs_base__frame_config__width(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__width(const wuffs_base__frame_config* c) {
   return c ? wuffs_base__rect_ie_u32__width(&c->private_impl.bounds) : 0;
 }
 
 static inline uint32_t  //
-wuffs_base__frame_config__height(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__height(const wuffs_base__frame_config* c) {
   return c ? wuffs_base__rect_ie_u32__height(&c->private_impl.bounds) : 0;
 }
 
 // wuffs_base__frame_config__duration returns the amount of time to display
 // this frame. Zero means to display forever - a still (non-animated) image.
 static inline wuffs_base__flicks  //
-wuffs_base__frame_config__duration(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__duration(const wuffs_base__frame_config* c) {
   return c ? c->private_impl.duration : 0;
 }
 
 // wuffs_base__frame_config__index returns the index of this frame. The first
 // frame in an image has index 0, the second frame has index 1, and so on.
 static inline uint64_t  //
-wuffs_base__frame_config__index(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__index(const wuffs_base__frame_config* c) {
   return c ? c->private_impl.index : 0;
 }
 
 // wuffs_base__frame_config__io_position returns the I/O stream position before
 // the frame config.
 static inline uint64_t  //
-wuffs_base__frame_config__io_position(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__io_position(const wuffs_base__frame_config* c) {
   return c ? c->private_impl.io_position : 0;
 }
 
 // wuffs_base__frame_config__blend returns, for an animated image, how to blend
 // the transparent pixels of this frame with the existing canvas.
 static inline wuffs_base__animation_blend  //
-wuffs_base__frame_config__blend(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__blend(const wuffs_base__frame_config* c) {
   return c ? c->private_impl.blend : 0;
 }
 
 // wuffs_base__frame_config__disposal returns, for an animated image, how to
 // dispose of this frame after displaying it.
 static inline wuffs_base__animation_disposal  //
-wuffs_base__frame_config__disposal(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__disposal(const wuffs_base__frame_config* c) {
   return c ? c->private_impl.disposal : 0;
 }
 
+static inline wuffs_base__color_u32_argb_premul  //
+wuffs_base__frame_config__background_color(const wuffs_base__frame_config* c) {
+  return c ? c->private_impl.background_color : 0;
+}
+
 #ifdef __cplusplus
 
 inline void  //
-wuffs_base__frame_config::update(wuffs_base__rect_ie_u32 bounds,
-                                 wuffs_base__flicks duration,
-                                 uint64_t index,
-                                 uint64_t io_position,
-                                 wuffs_base__animation_blend blend,
-                                 wuffs_base__animation_disposal disposal) {
+wuffs_base__frame_config::update(
+    wuffs_base__rect_ie_u32 bounds,
+    wuffs_base__flicks duration,
+    uint64_t index,
+    uint64_t io_position,
+    wuffs_base__animation_blend blend,
+    wuffs_base__animation_disposal disposal,
+    wuffs_base__color_u32_argb_premul background_color) {
   wuffs_base__frame_config__update(this, bounds, duration, index, io_position,
-                                   blend, disposal);
+                                   blend, disposal, background_color);
 }
 
 inline wuffs_base__rect_ie_u32  //
-wuffs_base__frame_config::bounds() {
+wuffs_base__frame_config::bounds() const {
   return wuffs_base__frame_config__bounds(this);
 }
 
 inline uint32_t  //
-wuffs_base__frame_config::width() {
+wuffs_base__frame_config::width() const {
   return wuffs_base__frame_config__width(this);
 }
 
 inline uint32_t  //
-wuffs_base__frame_config::height() {
+wuffs_base__frame_config::height() const {
   return wuffs_base__frame_config__height(this);
 }
 
 inline wuffs_base__flicks  //
-wuffs_base__frame_config::duration() {
+wuffs_base__frame_config::duration() const {
   return wuffs_base__frame_config__duration(this);
 }
 
 inline uint64_t  //
-wuffs_base__frame_config::index() {
+wuffs_base__frame_config::index() const {
   return wuffs_base__frame_config__index(this);
 }
 
 inline uint64_t  //
-wuffs_base__frame_config::io_position() {
+wuffs_base__frame_config::io_position() const {
   return wuffs_base__frame_config__io_position(this);
 }
 
 inline wuffs_base__animation_blend  //
-wuffs_base__frame_config::blend() {
+wuffs_base__frame_config::blend() const {
   return wuffs_base__frame_config__blend(this);
 }
 
 inline wuffs_base__animation_disposal  //
-wuffs_base__frame_config::disposal() {
+wuffs_base__frame_config::disposal() const {
   return wuffs_base__frame_config__disposal(this);
 }
 
+inline wuffs_base__color_u32_argb_premul  //
+wuffs_base__frame_config::background_color() const {
+  return wuffs_base__frame_config__background_color(this);
+}
+
 #endif  // __cplusplus
 
 // --------
@@ -888,8 +908,10 @@
 #ifdef __cplusplus
   inline wuffs_base__status set_from_slice(wuffs_base__pixel_config* pixcfg,
                                            wuffs_base__slice_u8 pixbuf_memory);
+  inline wuffs_base__status set_from_table(wuffs_base__pixel_config* pixcfg,
+                                           wuffs_base__table_u8 pixbuf_memory);
   inline wuffs_base__slice_u8 palette();
-  inline wuffs_base__pixel_format pixel_format();
+  inline wuffs_base__pixel_format pixel_format() const;
   inline wuffs_base__table_u8 plane(uint32_t p);
 #endif  // __cplusplus
 
@@ -919,12 +941,13 @@
   }
   if (wuffs_base__pixel_format__is_planar(pixcfg->private_impl.pixfmt)) {
     // TODO: support planar pixel formats, concious of pixel subsampling.
-    return wuffs_base__error__bad_argument;
+    return wuffs_base__error__unsupported_option;
   }
   uint32_t bits_per_pixel =
       wuffs_base__pixel_format__bits_per_pixel(pixcfg->private_impl.pixfmt);
   if ((bits_per_pixel == 0) || ((bits_per_pixel % 8) != 0)) {
-    return wuffs_base__error__bad_argument;
+    // TODO: support fraction-of-byte pixels, e.g. 1 bit per pixel?
+    return wuffs_base__error__unsupported_option;
   }
   uint64_t bytes_per_pixel = bits_per_pixel / 8;
 
@@ -970,6 +993,38 @@
   return NULL;
 }
 
+static inline wuffs_base__status  //
+wuffs_base__pixel_buffer__set_from_table(wuffs_base__pixel_buffer* b,
+                                         wuffs_base__pixel_config* pixcfg,
+                                         wuffs_base__table_u8 pixbuf_memory) {
+  if (!b) {
+    return wuffs_base__error__bad_receiver;
+  }
+  memset(b, 0, sizeof(*b));
+  if (!pixcfg ||
+      wuffs_base__pixel_format__is_planar(pixcfg->private_impl.pixfmt)) {
+    return wuffs_base__error__bad_argument;
+  }
+  uint32_t bits_per_pixel =
+      wuffs_base__pixel_format__bits_per_pixel(pixcfg->private_impl.pixfmt);
+  if ((bits_per_pixel == 0) || ((bits_per_pixel % 8) != 0)) {
+    // TODO: support fraction-of-byte pixels, e.g. 1 bit per pixel?
+    return wuffs_base__error__unsupported_option;
+  }
+  uint64_t bytes_per_pixel = bits_per_pixel / 8;
+
+  uint64_t width_in_bytes =
+      ((uint64_t)pixcfg->private_impl.width) * bytes_per_pixel;
+  if ((width_in_bytes > pixbuf_memory.width) ||
+      (pixcfg->private_impl.height > pixbuf_memory.height)) {
+    return wuffs_base__error__bad_argument;
+  }
+
+  b->pixcfg = *pixcfg;
+  b->private_impl.planes[0] = pixbuf_memory;
+  return NULL;
+}
+
 // wuffs_base__pixel_buffer__palette returns the palette color data. If
 // non-empty, it will have length 1024.
 static inline wuffs_base__slice_u8  //
@@ -986,7 +1041,7 @@
 }
 
 static inline wuffs_base__pixel_format  //
-wuffs_base__pixel_buffer__pixel_format(wuffs_base__pixel_buffer* b) {
+wuffs_base__pixel_buffer__pixel_format(const wuffs_base__pixel_buffer* b) {
   if (b) {
     return b->pixcfg.private_impl.pixfmt;
   }
@@ -1015,13 +1070,19 @@
   return wuffs_base__pixel_buffer__set_from_slice(this, pixcfg, pixbuf_memory);
 }
 
+inline wuffs_base__status  //
+wuffs_base__pixel_buffer::set_from_table(wuffs_base__pixel_config* pixcfg,
+                                         wuffs_base__table_u8 pixbuf_memory) {
+  return wuffs_base__pixel_buffer__set_from_table(this, pixcfg, pixbuf_memory);
+}
+
 inline wuffs_base__slice_u8  //
 wuffs_base__pixel_buffer::palette() {
   return wuffs_base__pixel_buffer__palette(this);
 }
 
 inline wuffs_base__pixel_format  //
-wuffs_base__pixel_buffer::pixel_format() {
+wuffs_base__pixel_buffer::pixel_format() const {
   return wuffs_base__pixel_buffer__pixel_format(this);
 }
 
@@ -1063,20 +1124,18 @@
   } private_impl;
 
 #ifdef __cplusplus
-  inline void prepare(wuffs_base__pixel_format dst_format,
-                      wuffs_base__slice_u8 dst_palette,
-                      wuffs_base__pixel_format src_format,
-                      wuffs_base__slice_u8 src_palette);
-  inline uint64_t swizzle_packed(wuffs_base__slice_u8 dst,
-                                 wuffs_base__slice_u8 dst_palette,
-                                 wuffs_base__slice_u8 src);
+  inline wuffs_base__status prepare(wuffs_base__pixel_format dst_format,
+                                    wuffs_base__slice_u8 dst_palette,
+                                    wuffs_base__pixel_format src_format,
+                                    wuffs_base__slice_u8 src_palette);
+  inline uint64_t swizzle_interleaved(wuffs_base__slice_u8 dst,
+                                      wuffs_base__slice_u8 dst_palette,
+                                      wuffs_base__slice_u8 src) const;
 #endif  // __cplusplus
 
 } wuffs_base__pixel_swizzler;
 
-// TODO: should prepare (both the C and C++ methods) return a status?
-
-void  //
+wuffs_base__status  //
 wuffs_base__pixel_swizzler__prepare(wuffs_base__pixel_swizzler* p,
                                     wuffs_base__pixel_format dst_format,
                                     wuffs_base__slice_u8 dst_palette,
@@ -1084,28 +1143,30 @@
                                     wuffs_base__slice_u8 src_palette);
 
 uint64_t  //
-wuffs_base__pixel_swizzler__swizzle_packed(wuffs_base__pixel_swizzler* p,
-                                           wuffs_base__slice_u8 dst,
-                                           wuffs_base__slice_u8 dst_palette,
-                                           wuffs_base__slice_u8 src);
+wuffs_base__pixel_swizzler__swizzle_interleaved(
+    const wuffs_base__pixel_swizzler* p,
+    wuffs_base__slice_u8 dst,
+    wuffs_base__slice_u8 dst_palette,
+    wuffs_base__slice_u8 src);
 
 #ifdef __cplusplus
 
-inline void  //
+inline wuffs_base__status  //
 wuffs_base__pixel_swizzler::prepare(wuffs_base__pixel_format dst_format,
                                     wuffs_base__slice_u8 dst_palette,
                                     wuffs_base__pixel_format src_format,
                                     wuffs_base__slice_u8 src_palette) {
-  wuffs_base__pixel_swizzler__prepare(this, dst_format, dst_palette, src_format,
-                                      src_palette);
+  return wuffs_base__pixel_swizzler__prepare(this, dst_format, dst_palette,
+                                             src_format, src_palette);
 }
 
 uint64_t  //
-wuffs_base__pixel_swizzler::swizzle_packed(wuffs_base__slice_u8 dst,
-                                           wuffs_base__slice_u8 dst_palette,
-                                           wuffs_base__slice_u8 src) {
-  return wuffs_base__pixel_swizzler__swizzle_packed(this, dst, dst_palette,
-                                                    src);
+wuffs_base__pixel_swizzler::swizzle_interleaved(
+    wuffs_base__slice_u8 dst,
+    wuffs_base__slice_u8 dst_palette,
+    wuffs_base__slice_u8 src) const {
+  return wuffs_base__pixel_swizzler__swizzle_interleaved(this, dst, dst_palette,
+                                                         src);
 }
 
 #endif  // __cplusplus
diff --git a/internal/cgen/base/io-private.h b/internal/cgen/base/io-private.h
index 088f3ce..fad529b 100644
--- a/internal/cgen/base/io-private.h
+++ b/internal/cgen/base/io-private.h
@@ -16,65 +16,52 @@
 
 // ---------------- I/O
 
-static inline bool  //
-wuffs_base__io_buffer__is_valid(wuffs_base__io_buffer buf) {
-  return (buf.data.ptr || (buf.data.len == 0)) &&
-         (buf.data.len >= buf.meta.wi) && (buf.meta.wi >= buf.meta.ri);
-}
-
-// TODO: wuffs_base__io_reader__is_eof is no longer used by Wuffs per se, but
-// it might be handy to programs that use Wuffs. Either delete it, or promote
-// it to the public API.
+// "Null" as in "/dev/null", not as in "nullptr".
 //
-// If making this function public (i.e. moving it to base-header.h), it also
-// needs to allow NULL (i.e. implicit, callee-calculated) mark/limit.
+// TODO: ensure that this is zero-initialized.
+static wuffs_base__io_buffer wuffs_base__global__null_io_buffer;
 
-static inline bool  //
-wuffs_base__io_reader__is_eof(wuffs_base__io_reader o) {
-  wuffs_base__io_buffer* buf = o.private_impl.buf;
-  return buf && buf->meta.closed &&
-         (buf->data.ptr + buf->meta.wi == o.private_impl.limit);
+static inline wuffs_base__io_buffer*  //
+wuffs_base__null_io_reader() {
+  return &wuffs_base__global__null_io_buffer;
 }
 
-static inline bool  //
-wuffs_base__io_reader__is_valid(wuffs_base__io_reader o) {
-  wuffs_base__io_buffer* buf = o.private_impl.buf;
-  // Note: if making this function public (i.e. moving it to base-header.h), it
-  // also needs to allow NULL (i.e. implicit, callee-calculated) mark/limit.
-  return buf ? ((buf->data.ptr <= o.private_impl.mark) &&
-                (o.private_impl.mark <= o.private_impl.limit) &&
-                (o.private_impl.limit <= buf->data.ptr + buf->data.len))
-             : ((o.private_impl.mark == NULL) &&
-                (o.private_impl.limit == NULL));
+static inline wuffs_base__io_buffer*  //
+wuffs_base__null_io_writer() {
+  return &wuffs_base__global__null_io_buffer;
 }
 
-static inline bool  //
-wuffs_base__io_writer__is_valid(wuffs_base__io_writer o) {
-  wuffs_base__io_buffer* buf = o.private_impl.buf;
-  // Note: if making this function public (i.e. moving it to base-header.h), it
-  // also needs to allow NULL (i.e. implicit, callee-calculated) mark/limit.
-  return buf ? ((buf->data.ptr <= o.private_impl.mark) &&
-                (o.private_impl.mark <= o.private_impl.limit) &&
-                (o.private_impl.limit <= buf->data.ptr + buf->data.len))
-             : ((o.private_impl.mark == NULL) &&
-                (o.private_impl.limit == NULL));
+static inline uint64_t  //
+wuffs_base__io__count_since(uint64_t mark, uint64_t index) {
+  if (index >= mark) {
+    return index - mark;
+  }
+  return 0;
+}
+
+static inline wuffs_base__slice_u8  //
+wuffs_base__io__since(uint64_t mark, uint64_t index, uint8_t* ptr) {
+  if (index >= mark) {
+    return wuffs_base__make_slice_u8(ptr + mark, index - mark);
+  }
+  return wuffs_base__make_slice_u8(NULL, 0);
 }
 
 static inline uint32_t  //
 wuffs_base__io_writer__copy_n_from_history(uint8_t** ptr_iop_w,
-                                           uint8_t* io0_w,
                                            uint8_t* io1_w,
+                                           uint8_t* io2_w,
                                            uint32_t length,
                                            uint32_t distance) {
   if (!distance) {
     return 0;
   }
   uint8_t* p = *ptr_iop_w;
-  if ((size_t)(p - io0_w) < (size_t)(distance)) {
+  if ((size_t)(p - io1_w) < (size_t)(distance)) {
     return 0;
   }
   uint8_t* q = p - distance;
-  size_t n = (size_t)(io1_w - p);
+  size_t n = (size_t)(io2_w - p);
   if ((size_t)(length) > n) {
     length = (uint32_t)(n);
   } else {
@@ -106,12 +93,12 @@
 // wuffs_base__io_writer__copy_n_from_history function above, but has stronger
 // pre-conditions. The caller needs to prove that:
 //  - distance >  0
-//  - distance <= (*ptr_iop_w - io0_w)
-//  - length   <= (io1_w      - *ptr_iop_w)
+//  - distance <= (*ptr_iop_w - io1_w)
+//  - length   <= (io2_w      - *ptr_iop_w)
 static inline uint32_t  //
 wuffs_base__io_writer__copy_n_from_history_fast(uint8_t** ptr_iop_w,
-                                                uint8_t* io0_w,
                                                 uint8_t* io1_w,
+                                                uint8_t* io2_w,
                                                 uint32_t length,
                                                 uint32_t distance) {
   uint8_t* p = *ptr_iop_w;
@@ -131,18 +118,18 @@
 
 static inline uint32_t  //
 wuffs_base__io_writer__copy_n_from_reader(uint8_t** ptr_iop_w,
-                                          uint8_t* io1_w,
+                                          uint8_t* io2_w,
                                           uint32_t length,
                                           uint8_t** ptr_iop_r,
-                                          uint8_t* io1_r) {
+                                          uint8_t* io2_r) {
   uint8_t* iop_w = *ptr_iop_w;
   size_t n = length;
-  if (n > ((size_t)(io1_w - iop_w))) {
-    n = (size_t)(io1_w - iop_w);
+  if (n > ((size_t)(io2_w - iop_w))) {
+    n = (size_t)(io2_w - iop_w);
   }
   uint8_t* iop_r = *ptr_iop_r;
-  if (n > ((size_t)(io1_r - iop_r))) {
-    n = (size_t)(io1_r - iop_r);
+  if (n > ((size_t)(io2_r - iop_r))) {
+    n = (size_t)(io2_r - iop_r);
   }
   if (n > 0) {
     memmove(iop_w, iop_r, n);
@@ -154,12 +141,12 @@
 
 static inline uint64_t  //
 wuffs_base__io_writer__copy_from_slice(uint8_t** ptr_iop_w,
-                                       uint8_t* io1_w,
+                                       uint8_t* io2_w,
                                        wuffs_base__slice_u8 src) {
   uint8_t* iop_w = *ptr_iop_w;
   size_t n = src.len;
-  if (n > ((size_t)(io1_w - iop_w))) {
-    n = (size_t)(io1_w - iop_w);
+  if (n > ((size_t)(io2_w - iop_w))) {
+    n = (size_t)(io2_w - iop_w);
   }
   if (n > 0) {
     memmove(iop_w, src.ptr, n);
@@ -170,7 +157,7 @@
 
 static inline uint32_t  //
 wuffs_base__io_writer__copy_n_from_slice(uint8_t** ptr_iop_w,
-                                         uint8_t* io1_w,
+                                         uint8_t* io2_w,
                                          uint32_t length,
                                          wuffs_base__slice_u8 src) {
   uint8_t* iop_w = *ptr_iop_w;
@@ -178,8 +165,8 @@
   if (n > length) {
     n = length;
   }
-  if (n > ((size_t)(io1_w - iop_w))) {
-    n = (size_t)(io1_w - iop_w);
+  if (n > ((size_t)(io2_w - iop_w))) {
+    n = (size_t)(io2_w - iop_w);
   }
   if (n > 0) {
     memmove(iop_w, src.ptr, n);
@@ -188,11 +175,12 @@
   return (uint32_t)(n);
 }
 
-static inline wuffs_base__empty_struct  //
-wuffs_base__io_reader__set(wuffs_base__io_reader* o,
-                           wuffs_base__io_buffer* b,
+static inline wuffs_base__io_buffer*  //
+wuffs_base__io_reader__set(wuffs_base__io_buffer* b,
                            uint8_t** ptr_iop_r,
+                           uint8_t** ptr_io0_r,
                            uint8_t** ptr_io1_r,
+                           uint8_t** ptr_io2_r,
                            wuffs_base__slice_u8 data) {
   b->data = data;
   b->meta.wi = data.len;
@@ -200,42 +188,17 @@
   b->meta.pos = 0;
   b->meta.closed = false;
 
-  o->private_impl.buf = b;
-  o->private_impl.mark = data.ptr;
-  o->private_impl.limit = data.ptr + data.len;
   *ptr_iop_r = data.ptr;
-  *ptr_io1_r = data.ptr + data.len;
+  *ptr_io0_r = data.ptr;
+  *ptr_io1_r = data.ptr;
+  *ptr_io2_r = data.ptr + data.len;
 
-  wuffs_base__empty_struct ret;
-  ret.private_impl = 0;
-  return ret;
-}
-
-static inline wuffs_base__empty_struct  //
-wuffs_base__io_reader__set_limit(wuffs_base__io_reader* o,
-                                 uint8_t* iop_r,
-                                 uint64_t limit) {
-  if (o && (((size_t)(o->private_impl.limit - iop_r)) > limit)) {
-    o->private_impl.limit = iop_r + limit;
-  }
-
-  wuffs_base__empty_struct ret;
-  ret.private_impl = 0;
-  return ret;
-}
-
-static inline wuffs_base__empty_struct  //
-wuffs_base__io_reader__set_mark(wuffs_base__io_reader* o, uint8_t* mark) {
-  o->private_impl.mark = mark;
-
-  wuffs_base__empty_struct ret;
-  ret.private_impl = 0;
-  return ret;
+  return b;
 }
 
 static inline wuffs_base__slice_u8  //
-wuffs_base__io_reader__take(uint8_t** ptr_iop_r, uint8_t* io1_r, uint64_t n) {
-  if (n <= ((size_t)(io1_r - *ptr_iop_r))) {
+wuffs_base__io_reader__take(uint8_t** ptr_iop_r, uint8_t* io2_r, uint64_t n) {
+  if (n <= ((size_t)(io2_r - *ptr_iop_r))) {
     uint8_t* p = *ptr_iop_r;
     *ptr_iop_r += n;
     return wuffs_base__make_slice_u8(p, n);
@@ -243,11 +206,12 @@
   return wuffs_base__make_slice_u8(NULL, 0);
 }
 
-static inline wuffs_base__empty_struct  //
-wuffs_base__io_writer__set(wuffs_base__io_writer* o,
-                           wuffs_base__io_buffer* b,
+static inline wuffs_base__io_buffer*  //
+wuffs_base__io_writer__set(wuffs_base__io_buffer* b,
                            uint8_t** ptr_iop_w,
+                           uint8_t** ptr_io0_w,
                            uint8_t** ptr_io1_w,
+                           uint8_t** ptr_io2_w,
                            wuffs_base__slice_u8 data) {
   b->data = data;
   b->meta.wi = 0;
@@ -255,24 +219,12 @@
   b->meta.pos = 0;
   b->meta.closed = false;
 
-  o->private_impl.buf = b;
-  o->private_impl.mark = data.ptr;
-  o->private_impl.limit = data.ptr + data.len;
   *ptr_iop_w = data.ptr;
-  *ptr_io1_w = data.ptr + data.len;
+  *ptr_io0_w = data.ptr;
+  *ptr_io1_w = data.ptr;
+  *ptr_io2_w = data.ptr + data.len;
 
-  wuffs_base__empty_struct ret;
-  ret.private_impl = 0;
-  return ret;
-}
-
-static inline wuffs_base__empty_struct  //
-wuffs_base__io_writer__set_mark(wuffs_base__io_writer* o, uint8_t* mark) {
-  o->private_impl.mark = mark;
-
-  wuffs_base__empty_struct ret;
-  ret.private_impl = 0;
-  return ret;
+  return b;
 }
 
 // ---------------- I/O (Utility)
diff --git a/internal/cgen/base/io-public.h b/internal/cgen/base/io-public.h
index 6e9bb58..271c7f9 100644
--- a/internal/cgen/base/io-public.h
+++ b/internal/cgen/base/io-public.h
@@ -15,34 +15,8 @@
 // limitations under the License.
 
 // ---------------- I/O
-
-struct wuffs_base__io_buffer__struct;
-
-typedef struct {
-  // Do not access the private_impl's fields directly. There is no API/ABI
-  // compatibility or safety guarantee if you do so.
-  struct {
-    struct wuffs_base__io_buffer__struct* buf;
-    // The bounds values are typically NULL, when created by the Wuffs public
-    // API. NULL means that the callee substitutes the implicit bounds derived
-    // from buf.
-    uint8_t* mark;
-    uint8_t* limit;
-  } private_impl;
-} wuffs_base__io_reader;
-
-typedef struct {
-  // Do not access the private_impl's fields directly. There is no API/ABI
-  // compatibility or safety guarantee if you do so.
-  struct {
-    struct wuffs_base__io_buffer__struct* buf;
-    // The bounds values are typically NULL, when created by the Wuffs public
-    // API. NULL means that the callee substitutes the implicit bounds derived
-    // from buf.
-    uint8_t* mark;
-    uint8_t* limit;
-  } private_impl;
-} wuffs_base__io_writer;
+//
+// See (/doc/note/io-input-output.md).
 
 // wuffs_base__io_buffer_meta is the metadata for a wuffs_base__io_buffer's
 // data.
@@ -63,10 +37,12 @@
 
 #ifdef __cplusplus
   inline void compact();
-  inline wuffs_base__io_reader reader();
-  inline wuffs_base__io_writer writer();
-  inline uint64_t reader_io_position();
-  inline uint64_t writer_io_position();
+  inline wuffs_base__io_buffer__struct* reader();  // Deprecated.
+  inline wuffs_base__io_buffer__struct* writer();  // Deprecated.
+  inline uint64_t reader_available() const;
+  inline uint64_t reader_io_position() const;
+  inline uint64_t writer_available() const;
+  inline uint64_t writer_io_position() const;
 #endif  // __cplusplus
 
 } wuffs_base__io_buffer;
@@ -115,24 +91,6 @@
   return ret;
 }
 
-static inline wuffs_base__io_reader  //
-wuffs_base__null_io_reader() {
-  wuffs_base__io_reader ret;
-  ret.private_impl.buf = NULL;
-  ret.private_impl.mark = NULL;
-  ret.private_impl.limit = NULL;
-  return ret;
-}
-
-static inline wuffs_base__io_writer  //
-wuffs_base__null_io_writer() {
-  wuffs_base__io_writer ret;
-  ret.private_impl.buf = NULL;
-  ret.private_impl.mark = NULL;
-  ret.private_impl.limit = NULL;
-  return ret;
-}
-
 // wuffs_base__io_buffer__compact moves any written but unread bytes to the
 // start of the buffer.
 static inline void  //
@@ -149,31 +107,23 @@
   buf->meta.ri = 0;
 }
 
-static inline wuffs_base__io_reader  //
-wuffs_base__io_buffer__reader(wuffs_base__io_buffer* buf) {
-  wuffs_base__io_reader ret;
-  ret.private_impl.buf = buf;
-  ret.private_impl.mark = NULL;
-  ret.private_impl.limit = NULL;
-  return ret;
-}
-
-static inline wuffs_base__io_writer  //
-wuffs_base__io_buffer__writer(wuffs_base__io_buffer* buf) {
-  wuffs_base__io_writer ret;
-  ret.private_impl.buf = buf;
-  ret.private_impl.mark = NULL;
-  ret.private_impl.limit = NULL;
-  return ret;
+static inline uint64_t  //
+wuffs_base__io_buffer__reader_available(const wuffs_base__io_buffer* buf) {
+  return buf ? buf->meta.wi - buf->meta.ri : 0;
 }
 
 static inline uint64_t  //
-wuffs_base__io_buffer__reader_io_position(wuffs_base__io_buffer* buf) {
+wuffs_base__io_buffer__reader_io_position(const wuffs_base__io_buffer* buf) {
   return buf ? wuffs_base__u64__sat_add(buf->meta.pos, buf->meta.ri) : 0;
 }
 
 static inline uint64_t  //
-wuffs_base__io_buffer__writer_io_position(wuffs_base__io_buffer* buf) {
+wuffs_base__io_buffer__writer_available(const wuffs_base__io_buffer* buf) {
+  return buf ? buf->data.len - buf->meta.wi : 0;
+}
+
+static inline uint64_t  //
+wuffs_base__io_buffer__writer_io_position(const wuffs_base__io_buffer* buf) {
   return buf ? wuffs_base__u64__sat_add(buf->meta.pos, buf->meta.wi) : 0;
 }
 
@@ -184,23 +134,33 @@
   wuffs_base__io_buffer__compact(this);
 }
 
-inline wuffs_base__io_reader  //
+inline wuffs_base__io_buffer*  //
 wuffs_base__io_buffer__struct::reader() {
-  return wuffs_base__io_buffer__reader(this);
+  return this;
 }
 
-inline wuffs_base__io_writer  //
+inline wuffs_base__io_buffer*  //
 wuffs_base__io_buffer__struct::writer() {
-  return wuffs_base__io_buffer__writer(this);
+  return this;
 }
 
 inline uint64_t  //
-wuffs_base__io_buffer__struct::reader_io_position() {
+wuffs_base__io_buffer__struct::reader_available() const {
+  return wuffs_base__io_buffer__reader_available(this);
+}
+
+inline uint64_t  //
+wuffs_base__io_buffer__struct::reader_io_position() const {
   return wuffs_base__io_buffer__reader_io_position(this);
 }
 
 inline uint64_t  //
-wuffs_base__io_buffer__struct::writer_io_position() {
+wuffs_base__io_buffer__struct::writer_available() const {
+  return wuffs_base__io_buffer__writer_available(this);
+}
+
+inline uint64_t  //
+wuffs_base__io_buffer__struct::writer_io_position() const {
   return wuffs_base__io_buffer__writer_io_position(this);
 }
 
diff --git a/internal/cgen/base/range-public.h b/internal/cgen/base/range-public.h
index 69d6ecb..b6f6be7 100644
--- a/internal/cgen/base/range-public.h
+++ b/internal/cgen/base/range-public.h
@@ -52,14 +52,14 @@
   uint32_t max_incl;
 
 #ifdef __cplusplus
-  inline bool is_empty();
-  inline bool equals(wuffs_base__range_ii_u32__struct s);
+  inline bool is_empty() const;
+  inline bool equals(wuffs_base__range_ii_u32__struct s) const;
   inline wuffs_base__range_ii_u32__struct intersect(
-      wuffs_base__range_ii_u32__struct s);
+      wuffs_base__range_ii_u32__struct s) const;
   inline wuffs_base__range_ii_u32__struct unite(
-      wuffs_base__range_ii_u32__struct s);
-  inline bool contains(uint32_t x);
-  inline bool contains_range(wuffs_base__range_ii_u32__struct s);
+      wuffs_base__range_ii_u32__struct s) const;
+  inline bool contains(uint32_t x) const;
+  inline bool contains_range(wuffs_base__range_ii_u32__struct s) const;
 #endif  // __cplusplus
 
 } wuffs_base__range_ii_u32;
@@ -73,12 +73,12 @@
 }
 
 static inline bool  //
-wuffs_base__range_ii_u32__is_empty(wuffs_base__range_ii_u32* r) {
+wuffs_base__range_ii_u32__is_empty(const wuffs_base__range_ii_u32* r) {
   return r->min_incl > r->max_incl;
 }
 
 static inline bool  //
-wuffs_base__range_ii_u32__equals(wuffs_base__range_ii_u32* r,
+wuffs_base__range_ii_u32__equals(const wuffs_base__range_ii_u32* r,
                                  wuffs_base__range_ii_u32 s) {
   return (r->min_incl == s.min_incl && r->max_incl == s.max_incl) ||
          (wuffs_base__range_ii_u32__is_empty(r) &&
@@ -86,7 +86,7 @@
 }
 
 static inline wuffs_base__range_ii_u32  //
-wuffs_base__range_ii_u32__intersect(wuffs_base__range_ii_u32* r,
+wuffs_base__range_ii_u32__intersect(const wuffs_base__range_ii_u32* r,
                                     wuffs_base__range_ii_u32 s) {
   wuffs_base__range_ii_u32 t;
   t.min_incl = wuffs_base__u32__max(r->min_incl, s.min_incl);
@@ -95,7 +95,7 @@
 }
 
 static inline wuffs_base__range_ii_u32  //
-wuffs_base__range_ii_u32__unite(wuffs_base__range_ii_u32* r,
+wuffs_base__range_ii_u32__unite(const wuffs_base__range_ii_u32* r,
                                 wuffs_base__range_ii_u32 s) {
   if (wuffs_base__range_ii_u32__is_empty(r)) {
     return s;
@@ -110,12 +110,13 @@
 }
 
 static inline bool  //
-wuffs_base__range_ii_u32__contains(wuffs_base__range_ii_u32* r, uint32_t x) {
+wuffs_base__range_ii_u32__contains(const wuffs_base__range_ii_u32* r,
+                                   uint32_t x) {
   return (r->min_incl <= x) && (x <= r->max_incl);
 }
 
 static inline bool  //
-wuffs_base__range_ii_u32__contains_range(wuffs_base__range_ii_u32* r,
+wuffs_base__range_ii_u32__contains_range(const wuffs_base__range_ii_u32* r,
                                          wuffs_base__range_ii_u32 s) {
   return wuffs_base__range_ii_u32__equals(
       &s, wuffs_base__range_ii_u32__intersect(r, s));
@@ -124,32 +125,32 @@
 #ifdef __cplusplus
 
 inline bool  //
-wuffs_base__range_ii_u32::is_empty() {
+wuffs_base__range_ii_u32::is_empty() const {
   return wuffs_base__range_ii_u32__is_empty(this);
 }
 
 inline bool  //
-wuffs_base__range_ii_u32::equals(wuffs_base__range_ii_u32 s) {
+wuffs_base__range_ii_u32::equals(wuffs_base__range_ii_u32 s) const {
   return wuffs_base__range_ii_u32__equals(this, s);
 }
 
 inline wuffs_base__range_ii_u32  //
-wuffs_base__range_ii_u32::intersect(wuffs_base__range_ii_u32 s) {
+wuffs_base__range_ii_u32::intersect(wuffs_base__range_ii_u32 s) const {
   return wuffs_base__range_ii_u32__intersect(this, s);
 }
 
 inline wuffs_base__range_ii_u32  //
-wuffs_base__range_ii_u32::unite(wuffs_base__range_ii_u32 s) {
+wuffs_base__range_ii_u32::unite(wuffs_base__range_ii_u32 s) const {
   return wuffs_base__range_ii_u32__unite(this, s);
 }
 
 inline bool  //
-wuffs_base__range_ii_u32::contains(uint32_t x) {
+wuffs_base__range_ii_u32::contains(uint32_t x) const {
   return wuffs_base__range_ii_u32__contains(this, x);
 }
 
 inline bool  //
-wuffs_base__range_ii_u32::contains_range(wuffs_base__range_ii_u32 s) {
+wuffs_base__range_ii_u32::contains_range(wuffs_base__range_ii_u32 s) const {
   return wuffs_base__range_ii_u32__contains_range(this, s);
 }
 
@@ -162,15 +163,15 @@
   uint32_t max_excl;
 
 #ifdef __cplusplus
-  inline bool is_empty();
-  inline bool equals(wuffs_base__range_ie_u32__struct s);
+  inline bool is_empty() const;
+  inline bool equals(wuffs_base__range_ie_u32__struct s) const;
   inline wuffs_base__range_ie_u32__struct intersect(
-      wuffs_base__range_ie_u32__struct s);
+      wuffs_base__range_ie_u32__struct s) const;
   inline wuffs_base__range_ie_u32__struct unite(
-      wuffs_base__range_ie_u32__struct s);
-  inline bool contains(uint32_t x);
-  inline bool contains_range(wuffs_base__range_ie_u32__struct s);
-  inline uint32_t length();
+      wuffs_base__range_ie_u32__struct s) const;
+  inline bool contains(uint32_t x) const;
+  inline bool contains_range(wuffs_base__range_ie_u32__struct s) const;
+  inline uint32_t length() const;
 #endif  // __cplusplus
 
 } wuffs_base__range_ie_u32;
@@ -184,12 +185,12 @@
 }
 
 static inline bool  //
-wuffs_base__range_ie_u32__is_empty(wuffs_base__range_ie_u32* r) {
+wuffs_base__range_ie_u32__is_empty(const wuffs_base__range_ie_u32* r) {
   return r->min_incl >= r->max_excl;
 }
 
 static inline bool  //
-wuffs_base__range_ie_u32__equals(wuffs_base__range_ie_u32* r,
+wuffs_base__range_ie_u32__equals(const wuffs_base__range_ie_u32* r,
                                  wuffs_base__range_ie_u32 s) {
   return (r->min_incl == s.min_incl && r->max_excl == s.max_excl) ||
          (wuffs_base__range_ie_u32__is_empty(r) &&
@@ -197,7 +198,7 @@
 }
 
 static inline wuffs_base__range_ie_u32  //
-wuffs_base__range_ie_u32__intersect(wuffs_base__range_ie_u32* r,
+wuffs_base__range_ie_u32__intersect(const wuffs_base__range_ie_u32* r,
                                     wuffs_base__range_ie_u32 s) {
   wuffs_base__range_ie_u32 t;
   t.min_incl = wuffs_base__u32__max(r->min_incl, s.min_incl);
@@ -206,7 +207,7 @@
 }
 
 static inline wuffs_base__range_ie_u32  //
-wuffs_base__range_ie_u32__unite(wuffs_base__range_ie_u32* r,
+wuffs_base__range_ie_u32__unite(const wuffs_base__range_ie_u32* r,
                                 wuffs_base__range_ie_u32 s) {
   if (wuffs_base__range_ie_u32__is_empty(r)) {
     return s;
@@ -221,56 +222,57 @@
 }
 
 static inline bool  //
-wuffs_base__range_ie_u32__contains(wuffs_base__range_ie_u32* r, uint32_t x) {
+wuffs_base__range_ie_u32__contains(const wuffs_base__range_ie_u32* r,
+                                   uint32_t x) {
   return (r->min_incl <= x) && (x < r->max_excl);
 }
 
 static inline bool  //
-wuffs_base__range_ie_u32__contains_range(wuffs_base__range_ie_u32* r,
+wuffs_base__range_ie_u32__contains_range(const wuffs_base__range_ie_u32* r,
                                          wuffs_base__range_ie_u32 s) {
   return wuffs_base__range_ie_u32__equals(
       &s, wuffs_base__range_ie_u32__intersect(r, s));
 }
 
 static inline uint32_t  //
-wuffs_base__range_ie_u32__length(wuffs_base__range_ie_u32* r) {
+wuffs_base__range_ie_u32__length(const wuffs_base__range_ie_u32* r) {
   return wuffs_base__u32__sat_sub(r->max_excl, r->min_incl);
 }
 
 #ifdef __cplusplus
 
 inline bool  //
-wuffs_base__range_ie_u32::is_empty() {
+wuffs_base__range_ie_u32::is_empty() const {
   return wuffs_base__range_ie_u32__is_empty(this);
 }
 
 inline bool  //
-wuffs_base__range_ie_u32::equals(wuffs_base__range_ie_u32 s) {
+wuffs_base__range_ie_u32::equals(wuffs_base__range_ie_u32 s) const {
   return wuffs_base__range_ie_u32__equals(this, s);
 }
 
 inline wuffs_base__range_ie_u32  //
-wuffs_base__range_ie_u32::intersect(wuffs_base__range_ie_u32 s) {
+wuffs_base__range_ie_u32::intersect(wuffs_base__range_ie_u32 s) const {
   return wuffs_base__range_ie_u32__intersect(this, s);
 }
 
 inline wuffs_base__range_ie_u32  //
-wuffs_base__range_ie_u32::unite(wuffs_base__range_ie_u32 s) {
+wuffs_base__range_ie_u32::unite(wuffs_base__range_ie_u32 s) const {
   return wuffs_base__range_ie_u32__unite(this, s);
 }
 
 inline bool  //
-wuffs_base__range_ie_u32::contains(uint32_t x) {
+wuffs_base__range_ie_u32::contains(uint32_t x) const {
   return wuffs_base__range_ie_u32__contains(this, x);
 }
 
 inline bool  //
-wuffs_base__range_ie_u32::contains_range(wuffs_base__range_ie_u32 s) {
+wuffs_base__range_ie_u32::contains_range(wuffs_base__range_ie_u32 s) const {
   return wuffs_base__range_ie_u32__contains_range(this, s);
 }
 
 inline uint32_t  //
-wuffs_base__range_ie_u32::length() {
+wuffs_base__range_ie_u32::length() const {
   return wuffs_base__range_ie_u32__length(this);
 }
 
@@ -283,14 +285,14 @@
   uint64_t max_incl;
 
 #ifdef __cplusplus
-  inline bool is_empty();
-  inline bool equals(wuffs_base__range_ii_u64__struct s);
+  inline bool is_empty() const;
+  inline bool equals(wuffs_base__range_ii_u64__struct s) const;
   inline wuffs_base__range_ii_u64__struct intersect(
-      wuffs_base__range_ii_u64__struct s);
+      wuffs_base__range_ii_u64__struct s) const;
   inline wuffs_base__range_ii_u64__struct unite(
-      wuffs_base__range_ii_u64__struct s);
-  inline bool contains(uint64_t x);
-  inline bool contains_range(wuffs_base__range_ii_u64__struct s);
+      wuffs_base__range_ii_u64__struct s) const;
+  inline bool contains(uint64_t x) const;
+  inline bool contains_range(wuffs_base__range_ii_u64__struct s) const;
 #endif  // __cplusplus
 
 } wuffs_base__range_ii_u64;
@@ -304,12 +306,12 @@
 }
 
 static inline bool  //
-wuffs_base__range_ii_u64__is_empty(wuffs_base__range_ii_u64* r) {
+wuffs_base__range_ii_u64__is_empty(const wuffs_base__range_ii_u64* r) {
   return r->min_incl > r->max_incl;
 }
 
 static inline bool  //
-wuffs_base__range_ii_u64__equals(wuffs_base__range_ii_u64* r,
+wuffs_base__range_ii_u64__equals(const wuffs_base__range_ii_u64* r,
                                  wuffs_base__range_ii_u64 s) {
   return (r->min_incl == s.min_incl && r->max_incl == s.max_incl) ||
          (wuffs_base__range_ii_u64__is_empty(r) &&
@@ -317,7 +319,7 @@
 }
 
 static inline wuffs_base__range_ii_u64  //
-wuffs_base__range_ii_u64__intersect(wuffs_base__range_ii_u64* r,
+wuffs_base__range_ii_u64__intersect(const wuffs_base__range_ii_u64* r,
                                     wuffs_base__range_ii_u64 s) {
   wuffs_base__range_ii_u64 t;
   t.min_incl = wuffs_base__u64__max(r->min_incl, s.min_incl);
@@ -326,7 +328,7 @@
 }
 
 static inline wuffs_base__range_ii_u64  //
-wuffs_base__range_ii_u64__unite(wuffs_base__range_ii_u64* r,
+wuffs_base__range_ii_u64__unite(const wuffs_base__range_ii_u64* r,
                                 wuffs_base__range_ii_u64 s) {
   if (wuffs_base__range_ii_u64__is_empty(r)) {
     return s;
@@ -341,12 +343,13 @@
 }
 
 static inline bool  //
-wuffs_base__range_ii_u64__contains(wuffs_base__range_ii_u64* r, uint64_t x) {
+wuffs_base__range_ii_u64__contains(const wuffs_base__range_ii_u64* r,
+                                   uint64_t x) {
   return (r->min_incl <= x) && (x <= r->max_incl);
 }
 
 static inline bool  //
-wuffs_base__range_ii_u64__contains_range(wuffs_base__range_ii_u64* r,
+wuffs_base__range_ii_u64__contains_range(const wuffs_base__range_ii_u64* r,
                                          wuffs_base__range_ii_u64 s) {
   return wuffs_base__range_ii_u64__equals(
       &s, wuffs_base__range_ii_u64__intersect(r, s));
@@ -355,32 +358,32 @@
 #ifdef __cplusplus
 
 inline bool  //
-wuffs_base__range_ii_u64::is_empty() {
+wuffs_base__range_ii_u64::is_empty() const {
   return wuffs_base__range_ii_u64__is_empty(this);
 }
 
 inline bool  //
-wuffs_base__range_ii_u64::equals(wuffs_base__range_ii_u64 s) {
+wuffs_base__range_ii_u64::equals(wuffs_base__range_ii_u64 s) const {
   return wuffs_base__range_ii_u64__equals(this, s);
 }
 
 inline wuffs_base__range_ii_u64  //
-wuffs_base__range_ii_u64::intersect(wuffs_base__range_ii_u64 s) {
+wuffs_base__range_ii_u64::intersect(wuffs_base__range_ii_u64 s) const {
   return wuffs_base__range_ii_u64__intersect(this, s);
 }
 
 inline wuffs_base__range_ii_u64  //
-wuffs_base__range_ii_u64::unite(wuffs_base__range_ii_u64 s) {
+wuffs_base__range_ii_u64::unite(wuffs_base__range_ii_u64 s) const {
   return wuffs_base__range_ii_u64__unite(this, s);
 }
 
 inline bool  //
-wuffs_base__range_ii_u64::contains(uint64_t x) {
+wuffs_base__range_ii_u64::contains(uint64_t x) const {
   return wuffs_base__range_ii_u64__contains(this, x);
 }
 
 inline bool  //
-wuffs_base__range_ii_u64::contains_range(wuffs_base__range_ii_u64 s) {
+wuffs_base__range_ii_u64::contains_range(wuffs_base__range_ii_u64 s) const {
   return wuffs_base__range_ii_u64__contains_range(this, s);
 }
 
@@ -393,15 +396,15 @@
   uint64_t max_excl;
 
 #ifdef __cplusplus
-  inline bool is_empty();
-  inline bool equals(wuffs_base__range_ie_u64__struct s);
+  inline bool is_empty() const;
+  inline bool equals(wuffs_base__range_ie_u64__struct s) const;
   inline wuffs_base__range_ie_u64__struct intersect(
-      wuffs_base__range_ie_u64__struct s);
+      wuffs_base__range_ie_u64__struct s) const;
   inline wuffs_base__range_ie_u64__struct unite(
-      wuffs_base__range_ie_u64__struct s);
-  inline bool contains(uint64_t x);
-  inline bool contains_range(wuffs_base__range_ie_u64__struct s);
-  inline uint64_t length();
+      wuffs_base__range_ie_u64__struct s) const;
+  inline bool contains(uint64_t x) const;
+  inline bool contains_range(wuffs_base__range_ie_u64__struct s) const;
+  inline uint64_t length() const;
 #endif  // __cplusplus
 
 } wuffs_base__range_ie_u64;
@@ -415,12 +418,12 @@
 }
 
 static inline bool  //
-wuffs_base__range_ie_u64__is_empty(wuffs_base__range_ie_u64* r) {
+wuffs_base__range_ie_u64__is_empty(const wuffs_base__range_ie_u64* r) {
   return r->min_incl >= r->max_excl;
 }
 
 static inline bool  //
-wuffs_base__range_ie_u64__equals(wuffs_base__range_ie_u64* r,
+wuffs_base__range_ie_u64__equals(const wuffs_base__range_ie_u64* r,
                                  wuffs_base__range_ie_u64 s) {
   return (r->min_incl == s.min_incl && r->max_excl == s.max_excl) ||
          (wuffs_base__range_ie_u64__is_empty(r) &&
@@ -428,7 +431,7 @@
 }
 
 static inline wuffs_base__range_ie_u64  //
-wuffs_base__range_ie_u64__intersect(wuffs_base__range_ie_u64* r,
+wuffs_base__range_ie_u64__intersect(const wuffs_base__range_ie_u64* r,
                                     wuffs_base__range_ie_u64 s) {
   wuffs_base__range_ie_u64 t;
   t.min_incl = wuffs_base__u64__max(r->min_incl, s.min_incl);
@@ -437,7 +440,7 @@
 }
 
 static inline wuffs_base__range_ie_u64  //
-wuffs_base__range_ie_u64__unite(wuffs_base__range_ie_u64* r,
+wuffs_base__range_ie_u64__unite(const wuffs_base__range_ie_u64* r,
                                 wuffs_base__range_ie_u64 s) {
   if (wuffs_base__range_ie_u64__is_empty(r)) {
     return s;
@@ -452,56 +455,57 @@
 }
 
 static inline bool  //
-wuffs_base__range_ie_u64__contains(wuffs_base__range_ie_u64* r, uint64_t x) {
+wuffs_base__range_ie_u64__contains(const wuffs_base__range_ie_u64* r,
+                                   uint64_t x) {
   return (r->min_incl <= x) && (x < r->max_excl);
 }
 
 static inline bool  //
-wuffs_base__range_ie_u64__contains_range(wuffs_base__range_ie_u64* r,
+wuffs_base__range_ie_u64__contains_range(const wuffs_base__range_ie_u64* r,
                                          wuffs_base__range_ie_u64 s) {
   return wuffs_base__range_ie_u64__equals(
       &s, wuffs_base__range_ie_u64__intersect(r, s));
 }
 
 static inline uint64_t  //
-wuffs_base__range_ie_u64__length(wuffs_base__range_ie_u64* r) {
+wuffs_base__range_ie_u64__length(const wuffs_base__range_ie_u64* r) {
   return wuffs_base__u64__sat_sub(r->max_excl, r->min_incl);
 }
 
 #ifdef __cplusplus
 
 inline bool  //
-wuffs_base__range_ie_u64::is_empty() {
+wuffs_base__range_ie_u64::is_empty() const {
   return wuffs_base__range_ie_u64__is_empty(this);
 }
 
 inline bool  //
-wuffs_base__range_ie_u64::equals(wuffs_base__range_ie_u64 s) {
+wuffs_base__range_ie_u64::equals(wuffs_base__range_ie_u64 s) const {
   return wuffs_base__range_ie_u64__equals(this, s);
 }
 
 inline wuffs_base__range_ie_u64  //
-wuffs_base__range_ie_u64::intersect(wuffs_base__range_ie_u64 s) {
+wuffs_base__range_ie_u64::intersect(wuffs_base__range_ie_u64 s) const {
   return wuffs_base__range_ie_u64__intersect(this, s);
 }
 
 inline wuffs_base__range_ie_u64  //
-wuffs_base__range_ie_u64::unite(wuffs_base__range_ie_u64 s) {
+wuffs_base__range_ie_u64::unite(wuffs_base__range_ie_u64 s) const {
   return wuffs_base__range_ie_u64__unite(this, s);
 }
 
 inline bool  //
-wuffs_base__range_ie_u64::contains(uint64_t x) {
+wuffs_base__range_ie_u64::contains(uint64_t x) const {
   return wuffs_base__range_ie_u64__contains(this, x);
 }
 
 inline bool  //
-wuffs_base__range_ie_u64::contains_range(wuffs_base__range_ie_u64 s) {
+wuffs_base__range_ie_u64::contains_range(wuffs_base__range_ie_u64 s) const {
   return wuffs_base__range_ie_u64__contains_range(this, s);
 }
 
 inline uint64_t  //
-wuffs_base__range_ie_u64::length() {
+wuffs_base__range_ie_u64::length() const {
   return wuffs_base__range_ie_u64__length(this);
 }
 
@@ -525,14 +529,14 @@
   uint32_t max_incl_y;
 
 #ifdef __cplusplus
-  inline bool is_empty();
-  inline bool equals(wuffs_base__rect_ii_u32__struct s);
+  inline bool is_empty() const;
+  inline bool equals(wuffs_base__rect_ii_u32__struct s) const;
   inline wuffs_base__rect_ii_u32__struct intersect(
-      wuffs_base__rect_ii_u32__struct s);
+      wuffs_base__rect_ii_u32__struct s) const;
   inline wuffs_base__rect_ii_u32__struct unite(
-      wuffs_base__rect_ii_u32__struct s);
-  inline bool contains(uint32_t x, uint32_t y);
-  inline bool contains_rect(wuffs_base__rect_ii_u32__struct s);
+      wuffs_base__rect_ii_u32__struct s) const;
+  inline bool contains(uint32_t x, uint32_t y) const;
+  inline bool contains_rect(wuffs_base__rect_ii_u32__struct s) const;
 #endif  // __cplusplus
 
 } wuffs_base__rect_ii_u32;
@@ -551,12 +555,12 @@
 }
 
 static inline bool  //
-wuffs_base__rect_ii_u32__is_empty(wuffs_base__rect_ii_u32* r) {
+wuffs_base__rect_ii_u32__is_empty(const wuffs_base__rect_ii_u32* r) {
   return (r->min_incl_x > r->max_incl_x) || (r->min_incl_y > r->max_incl_y);
 }
 
 static inline bool  //
-wuffs_base__rect_ii_u32__equals(wuffs_base__rect_ii_u32* r,
+wuffs_base__rect_ii_u32__equals(const wuffs_base__rect_ii_u32* r,
                                 wuffs_base__rect_ii_u32 s) {
   return (r->min_incl_x == s.min_incl_x && r->min_incl_y == s.min_incl_y &&
           r->max_incl_x == s.max_incl_x && r->max_incl_y == s.max_incl_y) ||
@@ -565,7 +569,7 @@
 }
 
 static inline wuffs_base__rect_ii_u32  //
-wuffs_base__rect_ii_u32__intersect(wuffs_base__rect_ii_u32* r,
+wuffs_base__rect_ii_u32__intersect(const wuffs_base__rect_ii_u32* r,
                                    wuffs_base__rect_ii_u32 s) {
   wuffs_base__rect_ii_u32 t;
   t.min_incl_x = wuffs_base__u32__max(r->min_incl_x, s.min_incl_x);
@@ -576,7 +580,7 @@
 }
 
 static inline wuffs_base__rect_ii_u32  //
-wuffs_base__rect_ii_u32__unite(wuffs_base__rect_ii_u32* r,
+wuffs_base__rect_ii_u32__unite(const wuffs_base__rect_ii_u32* r,
                                wuffs_base__rect_ii_u32 s) {
   if (wuffs_base__rect_ii_u32__is_empty(r)) {
     return s;
@@ -593,7 +597,7 @@
 }
 
 static inline bool  //
-wuffs_base__rect_ii_u32__contains(wuffs_base__rect_ii_u32* r,
+wuffs_base__rect_ii_u32__contains(const wuffs_base__rect_ii_u32* r,
                                   uint32_t x,
                                   uint32_t y) {
   return (r->min_incl_x <= x) && (x <= r->max_incl_x) && (r->min_incl_y <= y) &&
@@ -601,7 +605,7 @@
 }
 
 static inline bool  //
-wuffs_base__rect_ii_u32__contains_rect(wuffs_base__rect_ii_u32* r,
+wuffs_base__rect_ii_u32__contains_rect(const wuffs_base__rect_ii_u32* r,
                                        wuffs_base__rect_ii_u32 s) {
   return wuffs_base__rect_ii_u32__equals(
       &s, wuffs_base__rect_ii_u32__intersect(r, s));
@@ -610,32 +614,32 @@
 #ifdef __cplusplus
 
 inline bool  //
-wuffs_base__rect_ii_u32::is_empty() {
+wuffs_base__rect_ii_u32::is_empty() const {
   return wuffs_base__rect_ii_u32__is_empty(this);
 }
 
 inline bool  //
-wuffs_base__rect_ii_u32::equals(wuffs_base__rect_ii_u32 s) {
+wuffs_base__rect_ii_u32::equals(wuffs_base__rect_ii_u32 s) const {
   return wuffs_base__rect_ii_u32__equals(this, s);
 }
 
 inline wuffs_base__rect_ii_u32  //
-wuffs_base__rect_ii_u32::intersect(wuffs_base__rect_ii_u32 s) {
+wuffs_base__rect_ii_u32::intersect(wuffs_base__rect_ii_u32 s) const {
   return wuffs_base__rect_ii_u32__intersect(this, s);
 }
 
 inline wuffs_base__rect_ii_u32  //
-wuffs_base__rect_ii_u32::unite(wuffs_base__rect_ii_u32 s) {
+wuffs_base__rect_ii_u32::unite(wuffs_base__rect_ii_u32 s) const {
   return wuffs_base__rect_ii_u32__unite(this, s);
 }
 
 inline bool  //
-wuffs_base__rect_ii_u32::contains(uint32_t x, uint32_t y) {
+wuffs_base__rect_ii_u32::contains(uint32_t x, uint32_t y) const {
   return wuffs_base__rect_ii_u32__contains(this, x, y);
 }
 
 inline bool  //
-wuffs_base__rect_ii_u32::contains_rect(wuffs_base__rect_ii_u32 s) {
+wuffs_base__rect_ii_u32::contains_rect(wuffs_base__rect_ii_u32 s) const {
   return wuffs_base__rect_ii_u32__contains_rect(this, s);
 }
 
@@ -660,16 +664,16 @@
   uint32_t max_excl_y;
 
 #ifdef __cplusplus
-  inline bool is_empty();
-  inline bool equals(wuffs_base__rect_ie_u32__struct s);
+  inline bool is_empty() const;
+  inline bool equals(wuffs_base__rect_ie_u32__struct s) const;
   inline wuffs_base__rect_ie_u32__struct intersect(
-      wuffs_base__rect_ie_u32__struct s);
+      wuffs_base__rect_ie_u32__struct s) const;
   inline wuffs_base__rect_ie_u32__struct unite(
-      wuffs_base__rect_ie_u32__struct s);
-  inline bool contains(uint32_t x, uint32_t y);
-  inline bool contains_rect(wuffs_base__rect_ie_u32__struct s);
-  inline uint32_t width();
-  inline uint32_t height();
+      wuffs_base__rect_ie_u32__struct s) const;
+  inline bool contains(uint32_t x, uint32_t y) const;
+  inline bool contains_rect(wuffs_base__rect_ie_u32__struct s) const;
+  inline uint32_t width() const;
+  inline uint32_t height() const;
 #endif  // __cplusplus
 
 } wuffs_base__rect_ie_u32;
@@ -688,12 +692,12 @@
 }
 
 static inline bool  //
-wuffs_base__rect_ie_u32__is_empty(wuffs_base__rect_ie_u32* r) {
+wuffs_base__rect_ie_u32__is_empty(const wuffs_base__rect_ie_u32* r) {
   return (r->min_incl_x >= r->max_excl_x) || (r->min_incl_y >= r->max_excl_y);
 }
 
 static inline bool  //
-wuffs_base__rect_ie_u32__equals(wuffs_base__rect_ie_u32* r,
+wuffs_base__rect_ie_u32__equals(const wuffs_base__rect_ie_u32* r,
                                 wuffs_base__rect_ie_u32 s) {
   return (r->min_incl_x == s.min_incl_x && r->min_incl_y == s.min_incl_y &&
           r->max_excl_x == s.max_excl_x && r->max_excl_y == s.max_excl_y) ||
@@ -702,7 +706,7 @@
 }
 
 static inline wuffs_base__rect_ie_u32  //
-wuffs_base__rect_ie_u32__intersect(wuffs_base__rect_ie_u32* r,
+wuffs_base__rect_ie_u32__intersect(const wuffs_base__rect_ie_u32* r,
                                    wuffs_base__rect_ie_u32 s) {
   wuffs_base__rect_ie_u32 t;
   t.min_incl_x = wuffs_base__u32__max(r->min_incl_x, s.min_incl_x);
@@ -713,7 +717,7 @@
 }
 
 static inline wuffs_base__rect_ie_u32  //
-wuffs_base__rect_ie_u32__unite(wuffs_base__rect_ie_u32* r,
+wuffs_base__rect_ie_u32__unite(const wuffs_base__rect_ie_u32* r,
                                wuffs_base__rect_ie_u32 s) {
   if (wuffs_base__rect_ie_u32__is_empty(r)) {
     return s;
@@ -730,7 +734,7 @@
 }
 
 static inline bool  //
-wuffs_base__rect_ie_u32__contains(wuffs_base__rect_ie_u32* r,
+wuffs_base__rect_ie_u32__contains(const wuffs_base__rect_ie_u32* r,
                                   uint32_t x,
                                   uint32_t y) {
   return (r->min_incl_x <= x) && (x < r->max_excl_x) && (r->min_incl_y <= y) &&
@@ -738,61 +742,61 @@
 }
 
 static inline bool  //
-wuffs_base__rect_ie_u32__contains_rect(wuffs_base__rect_ie_u32* r,
+wuffs_base__rect_ie_u32__contains_rect(const wuffs_base__rect_ie_u32* r,
                                        wuffs_base__rect_ie_u32 s) {
   return wuffs_base__rect_ie_u32__equals(
       &s, wuffs_base__rect_ie_u32__intersect(r, s));
 }
 
 static inline uint32_t  //
-wuffs_base__rect_ie_u32__width(wuffs_base__rect_ie_u32* r) {
+wuffs_base__rect_ie_u32__width(const wuffs_base__rect_ie_u32* r) {
   return wuffs_base__u32__sat_sub(r->max_excl_x, r->min_incl_x);
 }
 
 static inline uint32_t  //
-wuffs_base__rect_ie_u32__height(wuffs_base__rect_ie_u32* r) {
+wuffs_base__rect_ie_u32__height(const wuffs_base__rect_ie_u32* r) {
   return wuffs_base__u32__sat_sub(r->max_excl_y, r->min_incl_y);
 }
 
 #ifdef __cplusplus
 
 inline bool  //
-wuffs_base__rect_ie_u32::is_empty() {
+wuffs_base__rect_ie_u32::is_empty() const {
   return wuffs_base__rect_ie_u32__is_empty(this);
 }
 
 inline bool  //
-wuffs_base__rect_ie_u32::equals(wuffs_base__rect_ie_u32 s) {
+wuffs_base__rect_ie_u32::equals(wuffs_base__rect_ie_u32 s) const {
   return wuffs_base__rect_ie_u32__equals(this, s);
 }
 
 inline wuffs_base__rect_ie_u32  //
-wuffs_base__rect_ie_u32::intersect(wuffs_base__rect_ie_u32 s) {
+wuffs_base__rect_ie_u32::intersect(wuffs_base__rect_ie_u32 s) const {
   return wuffs_base__rect_ie_u32__intersect(this, s);
 }
 
 inline wuffs_base__rect_ie_u32  //
-wuffs_base__rect_ie_u32::unite(wuffs_base__rect_ie_u32 s) {
+wuffs_base__rect_ie_u32::unite(wuffs_base__rect_ie_u32 s) const {
   return wuffs_base__rect_ie_u32__unite(this, s);
 }
 
 inline bool  //
-wuffs_base__rect_ie_u32::contains(uint32_t x, uint32_t y) {
+wuffs_base__rect_ie_u32::contains(uint32_t x, uint32_t y) const {
   return wuffs_base__rect_ie_u32__contains(this, x, y);
 }
 
 inline bool  //
-wuffs_base__rect_ie_u32::contains_rect(wuffs_base__rect_ie_u32 s) {
+wuffs_base__rect_ie_u32::contains_rect(wuffs_base__rect_ie_u32 s) const {
   return wuffs_base__rect_ie_u32__contains_rect(this, s);
 }
 
 inline uint32_t  //
-wuffs_base__rect_ie_u32::width() {
+wuffs_base__rect_ie_u32::width() const {
   return wuffs_base__rect_ie_u32__width(this);
 }
 
 inline uint32_t  //
-wuffs_base__rect_ie_u32::height() {
+wuffs_base__rect_ie_u32::height() const {
   return wuffs_base__rect_ie_u32__height(this);
 }
 
diff --git a/internal/cgen/builtin.go b/internal/cgen/builtin.go
index 636637e..1529d6b 100644
--- a/internal/cgen/builtin.go
+++ b/internal/cgen/builtin.go
@@ -102,75 +102,68 @@
 func (g *gen) writeBuiltinIO(b *buffer, recv *a.Expr, method t.ID, args []*a.Node, depth uint32) error {
 	switch method {
 	case t.IDAvailable:
-		p0, p1 := "", ""
+		p, q := "", ""
 		// TODO: don't hard-code these.
 		switch recv.Str(g.tm) {
 		case "args.dst":
-			p0 = "io1_a_dst"
-			p1 = "iop_a_dst"
+			p = "io2_a_dst"
+			q = "iop_a_dst"
 		case "args.src":
-			p0 = "io1_a_src"
-			p1 = "iop_a_src"
+			p = "io2_a_src"
+			q = "iop_a_src"
 		case "w":
-			p0 = "io1_v_w"
-			p1 = "iop_v_w"
+			p = "io2_v_w"
+			q = "iop_v_w"
 		}
-		if p0 == "" {
+		if p == "" {
 			return fmt.Errorf(`TODO: cgen a "foo.available" expression`)
 		}
-		b.printf("((uint64_t)(%s - %s))", p0, p1)
+		b.printf("((uint64_t)(%s - %s))", p, q)
 		return nil
-
-	case t.IDSet:
-		typ, v := "reader", "r"
-		if len(args) == 1 {
-			typ, v = "writer", "w"
-		}
-		b.printf("wuffs_base__io_%s__set(&v_%s, &u_%s, &iop_v_%s, &io1_v_%s,", typ, v, v, v, v)
-		return g.writeArgs(b, args, depth)
-
 	}
 	return errNoSuchBuiltin
 }
 
 func (g *gen) writeBuiltinIOReader(b *buffer, recv *a.Expr, method t.ID, args []*a.Node, depth uint32) error {
 	// TODO: don't hard-code the recv being a_src.
+	prefix, name := aPrefix, "src"
+	if recv.Operator() == 0 {
+		prefix, name = vPrefix, recv.Ident().Str(g.tm)
+	}
+
 	switch method {
 	case t.IDUndoByte:
 		b.writes("(iop_a_src--, wuffs_base__make_empty_struct())")
 		return nil
 
 	case t.IDCanUndoByte:
-		b.writes("(iop_a_src > io0_a_src)")
+		b.writes("(iop_a_src > io1_a_src)")
+		return nil
+
+	case t.IDCountSince:
+		b.printf("wuffs_base__io__count_since(")
+		if err := g.writeExpr(b, args[0].AsArg().Value(), depth); err != nil {
+			return err
+		}
+		b.printf(", ((uint64_t)(%s%s%s - %s%s%s)))", iopPrefix, prefix, name, io0Prefix, prefix, name)
+		return nil
+
+	case t.IDMark:
+		b.printf("((uint64_t)(%s%s%s - %s%s%s))", iopPrefix, prefix, name, io0Prefix, prefix, name)
 		return nil
 
 	case t.IDPosition:
-		b.printf("(a_src.private_impl.buf ? wuffs_base__u64__sat_add(" +
-			"a_src.private_impl.buf->meta.pos, ((uint64_t)(iop_a_src - a_src.private_impl.buf->data.ptr))) : 0)")
+		b.printf("(a_src ? wuffs_base__u64__sat_add(" +
+			"a_src->meta.pos, ((uint64_t)(iop_a_src - a_src->data.ptr))) : 0)")
 		return nil
 
-	case t.IDSetLimit:
-		b.printf("wuffs_base__io_reader__set_limit(&%ssrc, iop_a_src,", aPrefix)
-		// TODO: update the iop variables?
-		return g.writeArgs(b, args, depth)
-
-	case t.IDSetMark:
-		b.printf("wuffs_base__io_reader__set_mark(&%ssrc, iop_a_src)", aPrefix)
-		return nil
-
-	case t.IDSinceMark, t.IDSinceMarkLength:
-		prefix, name := aPrefix, "src"
-		if recv.Operator() == 0 {
-			prefix, name = vPrefix, recv.Ident().Str(g.tm)
+	case t.IDSince:
+		b.printf("wuffs_base__io__since(")
+		if err := g.writeExpr(b, args[0].AsArg().Value(), depth); err != nil {
+			return err
 		}
-
-		if method == t.IDSinceMark {
-			b.printf("wuffs_base__make_slice_u8(%s%s.private_impl.mark, (size_t)(", prefix, name)
-		}
-		b.printf("iop_%s%s - %s%s.private_impl.mark", prefix, name, prefix, name)
-		if method == t.IDSinceMark {
-			b.writes("))")
-		}
+		b.printf(", ((uint64_t)(%s%s%s - %s%s%s)), %s%s%s)",
+			iopPrefix, prefix, name, io0Prefix, prefix, name, io0Prefix, prefix, name)
 		return nil
 
 	case t.IDSkipFast:
@@ -186,7 +179,7 @@
 		return nil
 
 	case t.IDTake:
-		b.printf("wuffs_base__io_reader__take(&iop_a_src, io1_a_src,")
+		b.printf("wuffs_base__io_reader__take(&iop_a_src, io2_a_src,")
 		return g.writeArgs(b, args, depth)
 	}
 
@@ -210,6 +203,11 @@
 
 func (g *gen) writeBuiltinIOWriter(b *buffer, recv *a.Expr, method t.ID, args []*a.Node, depth uint32) error {
 	// TODO: don't hard-code the recv being a_dst or w.
+	prefix, name := aPrefix, "dst"
+	if recv.Operator() == 0 {
+		prefix, name = vPrefix, recv.Ident().Str(g.tm)
+	}
+
 	switch method {
 	case t.IDCopyNFromHistory, t.IDCopyNFromHistoryFast:
 		suffix := ""
@@ -217,7 +215,7 @@
 			suffix = "_fast"
 		}
 		b.printf("wuffs_base__io_writer__copy_n_from_history%s("+
-			"&iop_a_dst, %sdst.private_impl.mark, io1_a_dst",
+			"&iop_a_dst, %sdst->data.ptr, io2_a_dst",
 			suffix, aPrefix)
 		for _, o := range args {
 			b.writeb(',')
@@ -229,48 +227,48 @@
 		return nil
 
 	case t.IDCopyNFromReader:
-		b.printf("wuffs_base__io_writer__copy_n_from_reader(&iop_a_dst, io1_a_dst,")
+		b.printf("wuffs_base__io_writer__copy_n_from_reader(&iop_a_dst, io2_a_dst,")
 		if err := g.writeExpr(b, args[0].AsArg().Value(), depth); err != nil {
 			return err
 		}
 		// TODO: don't assume that the last argument is "args.src".
-		b.printf(", &iop_a_src, io1_a_src)")
+		b.printf(", &iop_a_src, io2_a_src)")
 		return nil
 
 	case t.IDCopyFromSlice:
-		b.printf("wuffs_base__io_writer__copy_from_slice(&iop_a_dst, io1_a_dst,")
+		b.printf("wuffs_base__io_writer__copy_from_slice(&iop_a_dst, io2_a_dst,")
 		return g.writeArgs(b, args, depth)
 
 	case t.IDCopyNFromSlice:
-		b.printf("wuffs_base__io_writer__copy_n_from_slice(&iop_a_dst, io1_a_dst,")
+		b.printf("wuffs_base__io_writer__copy_n_from_slice(&iop_a_dst, io2_a_dst,")
 		return g.writeArgs(b, args, depth)
 
+	case t.IDCountSince:
+		b.printf("wuffs_base__io__count_since(")
+		if err := g.writeExpr(b, args[0].AsArg().Value(), depth); err != nil {
+			return err
+		}
+		b.printf(", ((uint64_t)(iop_a_dst - io0_a_dst)))")
+		return nil
+
+	case t.IDHistoryAvailable:
+		b.printf("((uint64_t)(iop_%s%s - %s%s->data.ptr))", prefix, name, prefix, name)
+		return nil
+
+	case t.IDMark:
+		b.printf("((uint64_t)(iop_a_dst - io0_a_dst))")
+		return nil
+
 	case t.IDPosition:
-		b.printf("(a_dst.private_impl.buf ? wuffs_base__u64__sat_add(" +
-			"a_dst.private_impl.buf->meta.pos, iop_a_dst - a_dst.private_impl.buf->data.ptr) : 0)")
+		b.printf("(a_dst ? wuffs_base__u64__sat_add(a_dst->meta.pos, iop_a_dst - a_dst->data.ptr) : 0)")
 		return nil
 
-	case t.IDSetMark:
-		// TODO: is a private_impl.mark the right representation? What
-		// if the function is passed a (ptr io_writer) instead of a
-		// (io_writer)? Do we still want to have that mark live outside of
-		// the function scope?
-		b.printf("wuffs_base__io_writer__set_mark(&%sdst, iop_a_dst)", aPrefix)
-		return nil
-
-	case t.IDSinceMark, t.IDSinceMarkLength:
-		prefix, name := aPrefix, "dst"
-		if recv.Operator() == 0 {
-			prefix, name = vPrefix, recv.Ident().Str(g.tm)
+	case t.IDSince:
+		b.printf("wuffs_base__io__since(")
+		if err := g.writeExpr(b, args[0].AsArg().Value(), depth); err != nil {
+			return err
 		}
-
-		if method == t.IDSinceMark {
-			b.printf("wuffs_base__make_slice_u8(%s%s.private_impl.mark, (size_t)(", prefix, name)
-		}
-		b.printf("iop_%s%s - %s%s.private_impl.mark", prefix, name, prefix, name)
-		if method == t.IDSinceMark {
-			b.writes("))")
-		}
+		b.printf(", ((uint64_t)(iop_a_dst - io0_a_dst)), io0_a_dst)")
 		return nil
 	}
 
@@ -401,25 +399,6 @@
 		return g.writeArgs(b, args, depth)
 
 	case t.IDLength:
-		if recv.Operator() == t.IDOpenParen {
-			if method := recv.LHS().AsExpr(); method.Operator() == t.IDDot && method.Ident() == t.IDSinceMark {
-				if lhs := method.LHS().AsExpr(); lhs.MType().IsIOType() {
-					b.writes("((uint64_t)(")
-					if lhs.MType().QID()[1] == t.IDIOReader {
-						if err := g.writeBuiltinIOReader(b, lhs, t.IDSinceMarkLength, nil, depth); err != nil {
-							return err
-						}
-					} else {
-						if err := g.writeBuiltinIOWriter(b, lhs, t.IDSinceMarkLength, nil, depth); err != nil {
-							return err
-						}
-					}
-					b.writes("))")
-					return nil
-				}
-			}
-		}
-
 		b.writes("((uint64_t)(")
 		if err := g.writeExpr(b, recv, depth); err != nil {
 			return err
@@ -579,7 +558,7 @@
 			temp := g.currFunk.tempW
 			g.currFunk.tempW++
 
-			b.printf("if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {" +
+			b.printf("if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {" +
 				"status = wuffs_base__suspension__short_read; goto suspend; }")
 
 			// TODO: watch for passing an array type to writeCTypeName? In C, an
@@ -596,7 +575,7 @@
 				if err := g.writeCoroSuspPoint(b, false); err != nil {
 					return err
 				}
-				b.printf("if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {" +
+				b.printf("if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {" +
 					"status = wuffs_base__suspension__short_read; goto suspend; }")
 				b.printf("iop_a_src++;\n")
 				return nil
@@ -617,9 +596,9 @@
 				return err
 			}
 
-			b.printf("if (%s > ((uint64_t)(io1_a_src - iop_a_src))) {\n", scratchName)
-			b.printf("%s -= ((uint64_t)(io1_a_src - iop_a_src));\n", scratchName)
-			b.printf("iop_a_src = io1_a_src;\n")
+			b.printf("if (%s > ((uint64_t)(io2_a_src - iop_a_src))) {\n", scratchName)
+			b.printf("%s -= ((uint64_t)(io2_a_src - iop_a_src));\n", scratchName)
+			b.printf("iop_a_src = io2_a_src;\n")
 
 			b.writes("status = wuffs_base__suspension__short_read; goto suspend; }\n")
 			b.printf("iop_a_src += %s;\n", scratchName)
@@ -643,7 +622,7 @@
 			if err := g.writeCoroSuspPoint(b, false); err != nil {
 				return err
 			}
-			b.writes("if (iop_a_dst == io1_a_dst) {\n" +
+			b.writes("if (iop_a_dst == io2_a_dst) {\n" +
 				"status = wuffs_base__suspension__short_write; goto suspend; }\n" +
 				"*iop_a_dst++ = ")
 			x := n.Args()[0].AsArg().Value()
@@ -681,7 +660,7 @@
 	scratchName := fmt.Sprintf("self->private_data.%s%s[0].scratch",
 		sPrefix, g.currFunk.astFunc.FuncName().Str(g.tm))
 
-	b.printf("if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= %d)) {", xx/8)
+	b.printf("if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= %d)) {", xx/8)
 	b.printf("%s%d =", tPrefix, temp)
 	if xx != yy {
 		b.printf("((uint%d_t)(", yy)
@@ -699,7 +678,7 @@
 	}
 	b.printf("while (true) {")
 
-	b.printf("if (WUFFS_BASE__UNLIKELY(iop_%s == io1_%s)) {"+
+	b.printf("if (WUFFS_BASE__UNLIKELY(iop_%s == io2_%s)) {"+
 		"status = wuffs_base__suspension__short_read; goto suspend; }",
 		preName, preName)
 
diff --git a/internal/cgen/cgen.go b/internal/cgen/cgen.go
index 52f9b3c..d5cb53b 100644
--- a/internal/cgen/cgen.go
+++ b/internal/cgen/cgen.go
@@ -68,27 +68,29 @@
 // reading or writing the next byte (and advancing the stream) is essentially
 // "etc = *iop_a_src++" or "*io_a_dst++ = etc".
 //
-// The other two prefixes, giving names like io0_etc and io1_etc, are
+// The other two prefixes, giving names like io1_etc and io2_etc, are
 // auxilliary pointers: lower and upper inclusive bounds. As an iop_etc pointer
-// advances, it cannot advance past io1_etc. In the rarer case that an iop_etc
-// pointer retreats, undoing a read or write, it cannot retreat past io0_etc.
+// advances, it cannot advance past io2_etc. In the rarer case that an iop_etc
+// pointer retreats, undoing a read or write, it cannot retreat past io1_etc.
+//
+// The iop_etc pointer can change over the lifetime of a function. The ioN_etc
+// pointers, for numeric N, cannot.
 //
 // At the start of a function, these pointers are initialized from an
-// io_buffer's fields (ptr, ri, wi, len), or possibly a limit field. For an
-// io_reader:
-//  - io0_etc = ptr + ri
+// io_buffer's fields (ptr, ri, wi, len). For an io_reader:
+//  - io0_etc = ptr
+//  - io1_etc = ptr + ri
 //  - iop_etc = ptr + ri
-//  - io1_etc = ptr + wi   or  limit
+//  - io2_etc = ptr + wi
 // and for an io_writer:
-//  - io0_etc = ptr + wi
+//  - io0_etc = ptr
+//  - io1_etc = ptr + wi
 //  - iop_etc = ptr + wi
-//  - io1_etc = ptr + len  or  limit
-//
-// TODO: discuss marks and limits, and how (if at all) auxilliary pointers can
-// change over a function's lifetime.
+//  - io2_etc = ptr + len
 const (
-	io0Prefix = "io0_" // Lower bound.
-	io1Prefix = "io1_" // Upper bound.
+	io0Prefix = "io0_" // Base.
+	io1Prefix = "io1_" // Lower bound.
+	io2Prefix = "io2_" // Upper bound.
 	iopPrefix = "iop_" // Pointer.
 )
 
@@ -263,6 +265,19 @@
 
 func insertBaseAllPublicH(buf *buffer) error {
 	if err := expandBangBangInsert(buf, baseCorePublicH, map[string]func(*buffer) error{
+		"// !! INSERT FourCCs.\n": func(b *buffer) error {
+			for _, z := range builtin.FourCCs {
+				b.printf("// %s.\n#define WUFFS_BASE__FOURCC__%s 0x%02X%02X%02X%02X\n\n",
+					z[1],
+					strings.ToUpper(strings.TrimSpace(z[0])),
+					z[0][0],
+					z[0][1],
+					z[0][2],
+					z[0][3],
+				)
+			}
+			return nil
+		},
 		"// !! INSERT wuffs_base__status names.\n": func(b *buffer) error {
 			for _, z := range builtin.Statuses {
 				msg, _ := t.Unescape(z)
diff --git a/internal/cgen/data.go b/internal/cgen/data.go
index 9b91b5e..09121da 100644
--- a/internal/cgen/data.go
+++ b/internal/cgen/data.go
@@ -24,11 +24,13 @@
 	""
 
 const baseImageImplC = "" +
-	"// ---------------- Images\n\nconst uint32_t wuffs_base__pixel_format__bits_per_channel[16] = {\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,\n    0x08, 0x0A, 0x0C, 0x10, 0x18, 0x20, 0x30, 0x40,\n};\n\nstatic uint64_t  //\nwuffs_base__pixel_swizzler__copy_1_1(wuffs_base__slice_u8 dst,\n                                     wuffs_base__slice_u8 dst_palette,\n                                     wuffs_base__slice_u8 src) {\n  return wuffs_base__slice_u8__copy_from_slice(dst, src);\n}\n\nstatic uint64_t  //\nwuffs_base__pixel_swizzler__copy_4_1(wuffs_base__slice_u8 dst,\n                                     wuffs_base__slice_u8 dst_palette,\n                                     wuffs_base__slice_u8 src) {\n  if (dst_palette.len != 1024) {\n    return 0;\n  }\n  size_t dst_len4 = dst.len / 4;\n  size_t len = dst_len4 < src.len ? dst_len4 : src.len;\n  uint8_t* d = dst.ptr;\n  uint8_t* s = src.ptr;\n\n  size_t n = len;\n  const int N = 4;\n\n  while (n >= N) {\n    wuffs_base__store_u32le(\n        d + (0 * 4),\n        wuffs_base__load_u3" +
-	"2le(dst_palette.ptr + ((uint32_t)(s[0]) * 4)));\n    wuffs_base__store_u32le(\n        d + (1 * 4),\n        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[1]) * 4)));\n    wuffs_base__store_u32le(\n        d + (2 * 4),\n        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[2]) * 4)));\n    wuffs_base__store_u32le(\n        d + (3 * 4),\n        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[3]) * 4)));\n\n    s += 1 * N;\n    d += 4 * N;\n    n -= (size_t)(1 * N);\n  }\n\n  while (n >= 1) {\n    wuffs_base__store_u32le(\n        d + (0 * 4),\n        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[0]) * 4)));\n\n    s += 1 * 1;\n    d += 4 * 1;\n    n -= (size_t)(1 * 1);\n  }\n\n  return len;\n}\n\nstatic uint64_t  //\nwuffs_base__pixel_swizzler__swap_rgbx_bgrx(wuffs_base__slice_u8 dst,\n                                           wuffs_base__slice_u8 src) {\n  size_t len4 = (dst.len < src.len ? dst.len : src.len) / 4;\n  uint8_t* d = dst.ptr;\n  uint8_t* s = src.ptr;\n\n  size_t n = len4;\n  while (n--) {\n    uin" +
-	"t8_t b0 = s[0];\n    uint8_t b1 = s[1];\n    uint8_t b2 = s[2];\n    uint8_t b3 = s[3];\n    d[0] = b2;\n    d[1] = b1;\n    d[2] = b0;\n    d[3] = b3;\n    s += 4;\n    d += 4;\n  }\n  return len4 * 4;\n}\n\nvoid  //\nwuffs_base__pixel_swizzler__prepare(wuffs_base__pixel_swizzler* p,\n                                    wuffs_base__pixel_format dst_format,\n                                    wuffs_base__slice_u8 dst_palette,\n                                    wuffs_base__pixel_format src_format,\n                                    wuffs_base__slice_u8 src_palette) {\n  if (!p) {\n    return;\n  }\n\n  // TODO: support many more formats.\n\n  uint64_t (*func)(wuffs_base__slice_u8 dst, wuffs_base__slice_u8 dst_palette,\n                   wuffs_base__slice_u8 src) = NULL;\n\n  switch (src_format) {\n    case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_BINARY:\n      switch (dst_format) {\n        case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_NONPREMUL:\n        case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_PREMUL:\n        case WUFFS_BASE__PIXEL" +
-	"_FORMAT__INDEXED__BGRA_BINARY:\n          if (wuffs_base__slice_u8__copy_from_slice(dst_palette, src_palette) !=\n              1024) {\n            break;\n          }\n          func = wuffs_base__pixel_swizzler__copy_1_1;\n          break;\n        case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL:\n        case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL:\n        case WUFFS_BASE__PIXEL_FORMAT__BGRA_BINARY:\n          if (wuffs_base__slice_u8__copy_from_slice(dst_palette, src_palette) !=\n              1024) {\n            break;\n          }\n          func = wuffs_base__pixel_swizzler__copy_4_1;\n          break;\n        case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL:\n        case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL:\n        case WUFFS_BASE__PIXEL_FORMAT__RGBA_BINARY:\n          if (wuffs_base__pixel_swizzler__swap_rgbx_bgrx(dst_palette,\n                                                         src_palette) != 1024) {\n            break;\n          }\n          func = wuffs_base__pixel_swizzler__copy_4_1;\n          break;\n        d" +
-	"efault:\n          break;\n      }\n      break;\n\n    default:\n      break;\n  }\n\n  p->private_impl.func = func;\n}\n\nuint64_t  //\nwuffs_base__pixel_swizzler__swizzle_packed(wuffs_base__pixel_swizzler* p,\n                                           wuffs_base__slice_u8 dst,\n                                           wuffs_base__slice_u8 dst_palette,\n                                           wuffs_base__slice_u8 src) {\n  if (p && p->private_impl.func) {\n    return (*(p->private_impl.func))(dst, dst_palette, src);\n  }\n  return 0;\n}\n" +
+	"// ---------------- Images\n\nconst uint32_t wuffs_base__pixel_format__bits_per_channel[16] = {\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,\n    0x08, 0x0A, 0x0C, 0x10, 0x18, 0x20, 0x30, 0x40,\n};\n\nstatic uint64_t  //\nwuffs_base__pixel_swizzler__copy_1_1(wuffs_base__slice_u8 dst,\n                                     wuffs_base__slice_u8 dst_palette,\n                                     wuffs_base__slice_u8 src) {\n  return wuffs_base__slice_u8__copy_from_slice(dst, src);\n}\n\nstatic uint64_t  //\nwuffs_base__pixel_swizzler__copy_3_1(wuffs_base__slice_u8 dst,\n                                     wuffs_base__slice_u8 dst_palette,\n                                     wuffs_base__slice_u8 src) {\n  if (dst_palette.len != 1024) {\n    return 0;\n  }\n  size_t dst_len3 = dst.len / 3;\n  size_t len = dst_len3 < src.len ? dst_len3 : src.len;\n  uint8_t* d = dst.ptr;\n  uint8_t* s = src.ptr;\n  size_t n = len;\n\n  // N is the loop unroll count.\n  const int N = 4;\n\n  // The comparison in the while condition is \">\", not \">=\", be" +
+	"cause with \">=\",\n  // the last 4-byte store could write past the end of the dst slice.\n  //\n  // Each 4-byte store writes one too many bytes, but a subsequent store will\n  // overwrite that with the correct byte. There is always another store,\n  // whether a 4-byte store in this loop or a 1-byte store in the next loop.\n  while (n > N) {\n    wuffs_base__store_u32le(\n        d + (0 * 3),\n        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[0]) * 4)));\n    wuffs_base__store_u32le(\n        d + (1 * 3),\n        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[1]) * 4)));\n    wuffs_base__store_u32le(\n        d + (2 * 3),\n        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[2]) * 4)));\n    wuffs_base__store_u32le(\n        d + (3 * 3),\n        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[3]) * 4)));\n\n    s += 1 * N;\n    d += 3 * N;\n    n -= (size_t)(1 * N);\n  }\n\n  while (n >= 1) {\n    uint32_t color =\n        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[0]) * 4));\n    d[0" +
+	"] = (uint8_t)(color >> 0);\n    d[1] = (uint8_t)(color >> 8);\n    d[2] = (uint8_t)(color >> 16);\n\n    s += 1 * 1;\n    d += 3 * 1;\n    n -= (size_t)(1 * 1);\n  }\n\n  return len;\n}\nstatic uint64_t  //\nwuffs_base__pixel_swizzler__copy_4_1(wuffs_base__slice_u8 dst,\n                                     wuffs_base__slice_u8 dst_palette,\n                                     wuffs_base__slice_u8 src) {\n  if (dst_palette.len != 1024) {\n    return 0;\n  }\n  size_t dst_len4 = dst.len / 4;\n  size_t len = dst_len4 < src.len ? dst_len4 : src.len;\n  uint8_t* d = dst.ptr;\n  uint8_t* s = src.ptr;\n  size_t n = len;\n\n  // N is the loop unroll count.\n  const int N = 4;\n\n  while (n >= N) {\n    wuffs_base__store_u32le(\n        d + (0 * 4),\n        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[0]) * 4)));\n    wuffs_base__store_u32le(\n        d + (1 * 4),\n        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[1]) * 4)));\n    wuffs_base__store_u32le(\n        d + (2 * 4),\n        wuffs_base__load_u32le(dst_palette.ptr + (" +
+	"(uint32_t)(s[2]) * 4)));\n    wuffs_base__store_u32le(\n        d + (3 * 4),\n        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[3]) * 4)));\n\n    s += 1 * N;\n    d += 4 * N;\n    n -= (size_t)(1 * N);\n  }\n\n  while (n >= 1) {\n    wuffs_base__store_u32le(\n        d + (0 * 4),\n        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[0]) * 4)));\n\n    s += 1 * 1;\n    d += 4 * 1;\n    n -= (size_t)(1 * 1);\n  }\n\n  return len;\n}\n\nstatic uint64_t  //\nwuffs_base__pixel_swizzler__swap_rgbx_bgrx(wuffs_base__slice_u8 dst,\n                                           wuffs_base__slice_u8 src) {\n  size_t len4 = (dst.len < src.len ? dst.len : src.len) / 4;\n  uint8_t* d = dst.ptr;\n  uint8_t* s = src.ptr;\n\n  size_t n = len4;\n  while (n--) {\n    uint8_t b0 = s[0];\n    uint8_t b1 = s[1];\n    uint8_t b2 = s[2];\n    uint8_t b3 = s[3];\n    d[0] = b2;\n    d[1] = b1;\n    d[2] = b0;\n    d[3] = b3;\n    s += 4;\n    d += 4;\n  }\n  return len4 * 4;\n}\n\nwuffs_base__status  //\nwuffs_base__pixel_swizzler__prepare(wuffs_base__pixel_s" +
+	"wizzler* p,\n                                    wuffs_base__pixel_format dst_format,\n                                    wuffs_base__slice_u8 dst_palette,\n                                    wuffs_base__pixel_format src_format,\n                                    wuffs_base__slice_u8 src_palette) {\n  if (!p) {\n    return wuffs_base__error__bad_receiver;\n  }\n\n  // TODO: support many more formats.\n\n  uint64_t (*func)(wuffs_base__slice_u8 dst, wuffs_base__slice_u8 dst_palette,\n                   wuffs_base__slice_u8 src) = NULL;\n\n  switch (src_format) {\n    case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_BINARY:\n      switch (dst_format) {\n        case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_NONPREMUL:\n        case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_PREMUL:\n        case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_BINARY:\n          if (wuffs_base__slice_u8__copy_from_slice(dst_palette, src_palette) !=\n              1024) {\n            break;\n          }\n          func = wuffs_base__pixel_swizzler__copy_1_1;\n       " +
+	"   break;\n        case WUFFS_BASE__PIXEL_FORMAT__BGR:\n          if (wuffs_base__slice_u8__copy_from_slice(dst_palette, src_palette) !=\n              1024) {\n            break;\n          }\n          func = wuffs_base__pixel_swizzler__copy_3_1;\n          break;\n        case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL:\n        case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL:\n        case WUFFS_BASE__PIXEL_FORMAT__BGRA_BINARY:\n          if (wuffs_base__slice_u8__copy_from_slice(dst_palette, src_palette) !=\n              1024) {\n            break;\n          }\n          func = wuffs_base__pixel_swizzler__copy_4_1;\n          break;\n        case WUFFS_BASE__PIXEL_FORMAT__RGB:\n          if (wuffs_base__pixel_swizzler__swap_rgbx_bgrx(dst_palette,\n                                                         src_palette) != 1024) {\n            break;\n          }\n          func = wuffs_base__pixel_swizzler__copy_3_1;\n          break;\n        case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL:\n        case WUFFS_BASE__PIXEL_FORMAT__RGBA_PR" +
+	"EMUL:\n        case WUFFS_BASE__PIXEL_FORMAT__RGBA_BINARY:\n          if (wuffs_base__pixel_swizzler__swap_rgbx_bgrx(dst_palette,\n                                                         src_palette) != 1024) {\n            break;\n          }\n          func = wuffs_base__pixel_swizzler__copy_4_1;\n          break;\n        default:\n          break;\n      }\n      break;\n\n    default:\n      break;\n  }\n\n  p->private_impl.func = func;\n  return func ? NULL : wuffs_base__error__unsupported_option;\n}\n\nuint64_t  //\nwuffs_base__pixel_swizzler__swizzle_interleaved(\n    const wuffs_base__pixel_swizzler* p,\n    wuffs_base__slice_u8 dst,\n    wuffs_base__slice_u8 dst_palette,\n    wuffs_base__slice_u8 src) {\n  if (p && p->private_impl.func) {\n    return (*(p->private_impl.func))(dst, dst_palette, src);\n  }\n  return 0;\n}\n" +
 	""
 
 const baseCorePrivateH = "" +
@@ -71,6 +73,8 @@
 	"// --------\n\n// A status is either NULL (meaning OK) or a string message. That message is\n// human-readable, for programmers, but it is not for end users. It is not\n// localized, and does not contain additional contextual information such as a\n// source filename.\n//\n// Status strings are statically allocated and should never be free'd. They can\n// be compared by the == operator and not just by strcmp.\n//\n// Statuses come in four categories:\n//  - OK:          the request was completed, successfully.\n//  - Warnings:    the request was completed, unsuccessfully.\n//  - Suspensions: the request was not completed, but can be re-tried.\n//  - Errors:      the request was not completed, permanently.\n//\n// When a function returns an incomplete status, a suspension means that that\n// function should be called again within a new context, such as after flushing\n// or re-filling an I/O buffer. An error means that an irrecoverable failure\n// state was reached.\ntypedef const char* wuffs_base__status;\n\n// !! INSERT wuffs_bas" +
 	"e__status names.\n\nstatic inline bool  //\nwuffs_base__status__is_complete(wuffs_base__status z) {\n  return (z == NULL) || ((*z != '$') && (*z != '#'));\n}\n\nstatic inline bool  //\nwuffs_base__status__is_error(wuffs_base__status z) {\n  return z && (*z == '#');\n}\n\nstatic inline bool  //\nwuffs_base__status__is_ok(wuffs_base__status z) {\n  return z == NULL;\n}\n\nstatic inline bool  //\nwuffs_base__status__is_suspension(wuffs_base__status z) {\n  return z && (*z == '$');\n}\n\nstatic inline bool  //\nwuffs_base__status__is_warning(wuffs_base__status z) {\n  return z && (*z != '$') && (*z != '#');\n}\n\n" +
 	"" +
+	"// --------\n\n// FourCC constants.\n\n// !! INSERT FourCCs.\n\n" +
+	"" +
 	"// --------\n\n// Flicks are a unit of time. One flick (frame-tick) is 1 / 705_600_000 of a\n// second. See https://github.com/OculusVR/Flicks\ntypedef int64_t wuffs_base__flicks;\n\n#define WUFFS_BASE__FLICKS_PER_SECOND ((uint64_t)705600000)\n#define WUFFS_BASE__FLICKS_PER_MILLISECOND ((uint64_t)705600)\n\n" +
 	"" +
 	"// ---------------- Numeric Types\n\nstatic inline uint8_t  //\nwuffs_base__u8__min(uint8_t x, uint8_t y) {\n  return x < y ? x : y;\n}\n\nstatic inline uint8_t  //\nwuffs_base__u8__max(uint8_t x, uint8_t y) {\n  return x > y ? x : y;\n}\n\nstatic inline uint16_t  //\nwuffs_base__u16__min(uint16_t x, uint16_t y) {\n  return x < y ? x : y;\n}\n\nstatic inline uint16_t  //\nwuffs_base__u16__max(uint16_t x, uint16_t y) {\n  return x > y ? x : y;\n}\n\nstatic inline uint32_t  //\nwuffs_base__u32__min(uint32_t x, uint32_t y) {\n  return x < y ? x : y;\n}\n\nstatic inline uint32_t  //\nwuffs_base__u32__max(uint32_t x, uint32_t y) {\n  return x > y ? x : y;\n}\n\nstatic inline uint64_t  //\nwuffs_base__u64__min(uint64_t x, uint64_t y) {\n  return x < y ? x : y;\n}\n\nstatic inline uint64_t  //\nwuffs_base__u64__max(uint64_t x, uint64_t y) {\n  return x > y ? x : y;\n}\n\n" +
@@ -101,80 +105,81 @@
 const baseImagePublicH = "" +
 	"// ---------------- Images\n\n// wuffs_base__color_u32_argb_premul is an 8 bit per channel premultiplied\n// Alpha, Red, Green, Blue color, as a uint32_t value. It is in word order, not\n// byte order: its value is always 0xAARRGGBB, regardless of endianness.\ntypedef uint32_t wuffs_base__color_u32_argb_premul;\n\n" +
 	"" +
-	"// --------\n\n// wuffs_base__pixel_format encodes the format of the bytes that constitute an\n// image frame's pixel data. Its bits:\n//  - bit        31  is reserved.\n//  - bits 30 .. 28 encodes color (and channel order, in terms of memory).\n//  - bit        27  is reserved.\n//  - bits 26 .. 24 encodes transparency.\n//  - bits 23 .. 21 are reserved.\n//  - bit        20 indicates big-endian/MSB-first (as opposed to little/LSB).\n//  - bit        19 indicates floating point (as opposed to integer).\n//  - bit        18 indicates palette-indexed. The number-of-planes (the next\n//                  field) will be zero, as the format is considered packed,\n//                  but the 8-bit N-BGRA color data is stored in plane 3.\n//  - bits 17 .. 16 are the number of planes, minus 1. Zero means packed.\n//  - bits 15 .. 12 encodes the number of bits (depth) in the 3rd channel.\n//  - bits 11 ..  8 encodes the number of bits (depth) in the 2nd channel.\n//  - bits  7 ..  4 encodes the number of bits (depth) in the 1st channe" +
-	"l.\n//  - bits  3 ..  0 encodes the number of bits (depth) in the 0th channel.\n//\n// The bit fields of a wuffs_base__pixel_format are not independent. For\n// example, the number of planes should not be greater than the number of\n// channels. Similarly, bits 15..4 are unused (and should be zero) if bits\n// 31..24 (color and transparency) together imply only 1 channel (gray, no\n// alpha) and floating point samples should mean a bit depth of 16, 32 or 64.\n//\n// Formats hold between 1 and 4 channels. For example: Y (1 channel: gray), YA\n// (2 channels: gray and alpha), BGR (3 channels: blue, green, red) or CMYK (4\n// channels: cyan, magenta, yellow, black).\n//\n// For direct formats with N > 1 channels, those channels can be laid out in\n// either 1 (packed) or N (planar) planes. For example, RGBA data is usually\n// packed, but YCbCr data is usually planar, due to chroma subsampling (for\n// details, see the wuffs_base__pixel_subsampling type).\n//\n// For indexed formats, the palette (always 256 × 4 bytes) holds 8 bi" +
-	"ts per\n// channel non-alpha-premultiplied BGRA color data. There is only 1 plane (for\n// the index), as the format is considered packed. Plane 0 holds the per-pixel\n// indices. Plane 3 is re-purposed to hold the per-index colors.\n//\n// The color field is encoded in 3 bits:\n//  - 0 means                   A (Alpha).\n//  - 1 means Y         or     YA (Gray, Alpha).\n//  - 2 means YCbCr     or YCbCrA (Luma, Chroma-blue, Chroma-red, Alpha).\n//  - 3 means YCoCg     or YCoCgA (Luma, Chroma-orange, Chroma-green, Alpha).\n//  - 4 means BGR, BGRX or   BGRA (Blue, Green, Red, X-padding or Alpha).\n//  - 5 means RGB, RGBX or   RGBA (Red, Green, Blue, X-padding or Alpha).\n//  - 6 means CMY       or   CMYK (Cyan, Magenta, Yellow, Black).\n//  - all other values are reserved.\n//\n// In Wuffs, channels are given in memory order (also known as byte order),\n// regardless of endianness, since the C type for the pixel data is an array of\n// bytes, not an array of uint32_t. For example, packed BGRA with 8 bits per\n// channel means th" +
-	"at the bytes in memory are always Blue, Green, Red then\n// Alpha. On big-endian systems, that is the uint32_t 0xBBGGRRAA. On\n// little-endian, 0xAARRGGBB.\n//\n// When the color field (3 bits) encodes multiple options, the transparency\n// field (3 bits) distinguishes them:\n//  - 0 means fully opaque, no extra channels\n//  - 1 means fully opaque, one extra channel (X or K, padding or black).\n//  - 5 means one extra alpha channel, other channels are non-premultiplied.\n//  - 6 means one extra alpha channel, other channels are     premultiplied.\n//  - 7 means one extra alpha channel, binary alpha.\n//  - all other values are reserved.\n//\n// Binary alpha means that if a color is not completely opaque, it is\n// completely transparent black. As a source pixel format, it can therefore be\n// treated as either non-premultiplied or premultiplied.\n//\n// The zero wuffs_base__pixel_format value is an invalid pixel format, as it is\n// invalid to combine the zero color (alpha only) with the zero transparency.\n//\n// Bit depth is" +
-	" encoded in 4 bits:\n//  -  0 means the channel or index is unused.\n//  -  x means a bit depth of  x, for x in the range 1..8.\n//  -  9 means a bit depth of 10.\n//  - 10 means a bit depth of 12.\n//  - 11 means a bit depth of 16.\n//  - 12 means a bit depth of 24.\n//  - 13 means a bit depth of 32.\n//  - 14 means a bit depth of 48.\n//  - 15 means a bit depth of 64.\n//\n// For example, wuffs_base__pixel_format 0x5510BBBB is a natural format for\n// decoding a PNG image - network byte order (also known as big-endian),\n// packed, non-premultiplied alpha - that happens to be 16-bit-depth truecolor\n// with alpha (RGBA). In memory order:\n//\n//  ptr+0  ptr+1  ptr+2  ptr+3  ptr+4  ptr+5  ptr+6  ptr+7\n//  Rhi    Rlo    Ghi    Glo    Bhi    Blo    Ahi    Alo\n//\n// For example, the value wuffs_base__pixel_format 0x40000565 means BGR with no\n// alpha or padding, 5/6/5 bits for blue/green/red, packed 2 bytes per pixel,\n// laid out LSB-first in memory order:\n//\n//  ptr+0...........  ptr+1...........\n//  MSB          LSB  MSB    " +
-	"      LSB\n//  G₂G₁G₀B₄B₃B₂B₁B₀  R₄R₃R₂R₁R₀G₅G₄G₃\n//\n// On little-endian systems (but not big-endian), this Wuffs pixel format value\n// (0x40000565) corresponds to the Cairo library's CAIRO_FORMAT_RGB16_565, the\n// SDL2 (Simple DirectMedia Layer 2) library's SDL_PIXELFORMAT_RGB565 and the\n// Skia library's kRGB_565_SkColorType. Note BGR in Wuffs versus RGB in the\n// other libraries.\n//\n// Regardless of endianness, this Wuffs pixel format value (0x40000565)\n// corresponds to the V4L2 (Video For Linux 2) library's V4L2_PIX_FMT_RGB565\n// and the Wayland-DRM library's WL_DRM_FORMAT_RGB565.\n//\n// Different software libraries name their pixel formats (and especially their\n// channel order) either according to memory layout or as bits of a native\n// integer type like uint32_t. The two conventions differ because of a system's\n// endianness. As mentioned earlier, Wuffs pixel formats are always in memory\n// order. More detail of other software libraries' naming conventions is in the\n// Pi" +
-	"xel Format Guide at https://afrantzis.github.io/pixel-format-guide/\n//\n// Do not manipulate these bits directly; they are private implementation\n// details. Use methods such as wuffs_base__pixel_format__num_planes instead.\ntypedef uint32_t wuffs_base__pixel_format;\n\n// Common 8-bit-depth pixel formats. This list is not exhaustive; not all valid\n// wuffs_base__pixel_format values are present.\n\n#define WUFFS_BASE__PIXEL_FORMAT__INVALID ((wuffs_base__pixel_format)0x00000000)\n\n#define WUFFS_BASE__PIXEL_FORMAT__A ((wuffs_base__pixel_format)0x02000008)\n\n#define WUFFS_BASE__PIXEL_FORMAT__Y ((wuffs_base__pixel_format)0x10000008)\n#define WUFFS_BASE__PIXEL_FORMAT__YA_NONPREMUL \\\n  ((wuffs_base__pixel_format)0x15000008)\n#define WUFFS_BASE__PIXEL_FORMAT__YA_PREMUL \\\n  ((wuffs_base__pixel_format)0x16000008)\n\n#define WUFFS_BASE__PIXEL_FORMAT__YCBCR ((wuffs_base__pixel_format)0x20020888)\n#define WUFFS_BASE__PIXEL_FORMAT__YCBCRK ((wuffs_base__pixel_format)0x21038888)\n#define WUFFS_BASE__PIXEL_FORMAT__YCBCRA_NONPREMUL \\\n  ((w" +
-	"uffs_base__pixel_format)0x25038888)\n\n#define WUFFS_BASE__PIXEL_FORMAT__YCOCG ((wuffs_base__pixel_format)0x30020888)\n#define WUFFS_BASE__PIXEL_FORMAT__YCOCGK ((wuffs_base__pixel_format)0x31038888)\n#define WUFFS_BASE__PIXEL_FORMAT__YCOCGA_NONPREMUL \\\n  ((wuffs_base__pixel_format)0x35038888)\n\n#define WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_NONPREMUL \\\n  ((wuffs_base__pixel_format)0x45040008)\n#define WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_PREMUL \\\n  ((wuffs_base__pixel_format)0x46040008)\n#define WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_BINARY \\\n  ((wuffs_base__pixel_format)0x47040008)\n\n#define WUFFS_BASE__PIXEL_FORMAT__BGR ((wuffs_base__pixel_format)0x40000888)\n#define WUFFS_BASE__PIXEL_FORMAT__BGRX ((wuffs_base__pixel_format)0x41008888)\n#define WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL \\\n  ((wuffs_base__pixel_format)0x45008888)\n#define WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL \\\n  ((wuffs_base__pixel_format)0x46008888)\n#define WUFFS_BASE__PIXEL_FORMAT__BGRA_BINARY \\\n  ((wuffs_base__pixel_format)0x47008888)\n\n#define W" +
-	"UFFS_BASE__PIXEL_FORMAT__RGB ((wuffs_base__pixel_format)0x50000888)\n#define WUFFS_BASE__PIXEL_FORMAT__RGBX ((wuffs_base__pixel_format)0x51008888)\n#define WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL \\\n  ((wuffs_base__pixel_format)0x55008888)\n#define WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL \\\n  ((wuffs_base__pixel_format)0x56008888)\n#define WUFFS_BASE__PIXEL_FORMAT__RGBA_BINARY \\\n  ((wuffs_base__pixel_format)0x57008888)\n\n#define WUFFS_BASE__PIXEL_FORMAT__CMY ((wuffs_base__pixel_format)0x60020888)\n#define WUFFS_BASE__PIXEL_FORMAT__CMYK ((wuffs_base__pixel_format)0x61038888)\n\nextern const uint32_t wuffs_base__pixel_format__bits_per_channel[16];\n\nstatic inline bool  //\nwuffs_base__pixel_format__is_valid(wuffs_base__pixel_format f) {\n  return f != 0;\n}\n\n// wuffs_base__pixel_format__bits_per_pixel returns, for packed pixel formats,\n// the number of bits per pixel. It returns 0 for planar pixel formats.\nstatic inline uint32_t  //\nwuffs_base__pixel_format__bits_per_pixel(wuffs_base__pixel_format f) {\n  if (((f >> 16) & 0x03" +
-	") != 0) {\n    return 0;\n  }\n  return wuffs_base__pixel_format__bits_per_channel[0x0F & (f >> 0)] +\n         wuffs_base__pixel_format__bits_per_channel[0x0F & (f >> 4)] +\n         wuffs_base__pixel_format__bits_per_channel[0x0F & (f >> 8)] +\n         wuffs_base__pixel_format__bits_per_channel[0x0F & (f >> 12)];\n}\n\nstatic inline bool  //\nwuffs_base__pixel_format__is_indexed(wuffs_base__pixel_format f) {\n  return (f >> 18) & 0x01;\n}\n\nstatic inline bool  //\nwuffs_base__pixel_format__is_packed(wuffs_base__pixel_format f) {\n  return ((f >> 16) & 0x03) == 0;\n}\n\nstatic inline bool  //\nwuffs_base__pixel_format__is_planar(wuffs_base__pixel_format f) {\n  return ((f >> 16) & 0x03) != 0;\n}\n\nstatic inline uint32_t  //\nwuffs_base__pixel_format__num_planes(wuffs_base__pixel_format f) {\n  return ((f >> 16) & 0x03) + 1;\n}\n\n#define WUFFS_BASE__PIXEL_FORMAT__NUM_PLANES_MAX 4\n\n#define WUFFS_BASE__PIXEL_FORMAT__INDEXED__INDEX_PLANE 0\n#define WUFFS_BASE__PIXEL_FORMAT__INDEXED__COLOR_PLANE 3\n\n" +
+	"// --------\n\n// wuffs_base__pixel_format encodes the format of the bytes that constitute an\n// image frame's pixel data. Its bits:\n//  - bit        31  is reserved.\n//  - bits 30 .. 28 encodes color (and channel order, in terms of memory).\n//  - bit        27  is reserved.\n//  - bits 26 .. 24 encodes transparency.\n//  - bits 23 .. 21 are reserved.\n//  - bit        20 indicates big-endian/MSB-first (as opposed to little/LSB).\n//  - bit        19 indicates floating point (as opposed to integer).\n//  - bit        18 indicates palette-indexed. The number-of-planes (the next\n//                  field) will be 0, as the format is considered interleaved,\n//                  but the 8-bit N-BGRA color data is stored in plane 3.\n//  - bits 17 .. 16 are the number of planes, minus 1. Zero means interleaved.\n//  - bits 15 .. 12 encodes the number of bits (depth) in the 3rd channel.\n//  - bits 11 ..  8 encodes the number of bits (depth) in the 2nd channel.\n//  - bits  7 ..  4 encodes the number of bits (depth) in the 1st" +
+	" channel.\n//  - bits  3 ..  0 encodes the number of bits (depth) in the 0th channel.\n//\n// The bit fields of a wuffs_base__pixel_format are not independent. For\n// example, the number of planes should not be greater than the number of\n// channels. Similarly, bits 15..4 are unused (and should be zero) if bits\n// 31..24 (color and transparency) together imply only 1 channel (gray, no\n// alpha) and floating point samples should mean a bit depth of 16, 32 or 64.\n//\n// Formats hold between 1 and 4 channels. For example: Y (1 channel: gray), YA\n// (2 channels: gray and alpha), BGR (3 channels: blue, green, red) or CMYK (4\n// channels: cyan, magenta, yellow, black).\n//\n// For direct formats with N > 1 channels, those channels can be laid out in\n// either 1 (interleaved) or N (planar) planes. For example, RGBA data is\n// usually interleaved, but YCbCr data is usually planar, due to chroma\n// subsampling (for details, see the wuffs_base__pixel_subsampling type).\n//\n// For indexed formats, the palette (always 256 × 4 " +
+	"bytes) holds 8 bits per\n// channel non-alpha-premultiplied BGRA color data. There is only 1 plane (for\n// the index), as the format is considered interleaved. Plane 0 holds the\n// per-pixel indices. Plane 3 is re-purposed to hold the per-index colors.\n//\n// The color field is encoded in 3 bits:\n//  - 0 means                   A (Alpha).\n//  - 1 means Y         or     YA (Gray, Alpha).\n//  - 2 means YCbCr     or YCbCrA (Luma, Chroma-blue, Chroma-red, Alpha).\n//  - 3 means YCoCg     or YCoCgA (Luma, Chroma-orange, Chroma-green, Alpha).\n//  - 4 means BGR, BGRX or   BGRA (Blue, Green, Red, X-padding or Alpha).\n//  - 5 means RGB, RGBX or   RGBA (Red, Green, Blue, X-padding or Alpha).\n//  - 6 means CMY       or   CMYK (Cyan, Magenta, Yellow, Black).\n//  - all other values are reserved.\n//\n// In Wuffs, channels are given in memory order (also known as byte order),\n// regardless of endianness, since the C type for the pixel data is an array of\n// bytes, not an array of uint32_t. For example, interleaved BGRA with 8 b" +
+	"its\n// per channel means that the bytes in memory are always Blue, Green, Red then\n// Alpha. On big-endian systems, that is the uint32_t 0xBBGGRRAA. On\n// little-endian, 0xAARRGGBB.\n//\n// When the color field (3 bits) encodes multiple options, the transparency\n// field (3 bits) distinguishes them:\n//  - 0 means fully opaque, no extra channels\n//  - 1 means fully opaque, one extra channel (X or K, padding or black).\n//  - 5 means one extra alpha channel, other channels are non-premultiplied.\n//  - 6 means one extra alpha channel, other channels are     premultiplied.\n//  - 7 means one extra alpha channel, binary alpha.\n//  - all other values are reserved.\n//\n// Binary alpha means that if a color is not completely opaque, it is\n// completely transparent black. As a source pixel format, it can therefore be\n// treated as either non-premultiplied or premultiplied.\n//\n// The zero wuffs_base__pixel_format value is an invalid pixel format, as it is\n// invalid to combine the zero color (alpha only) with the zero trans" +
+	"parency.\n//\n// Bit depth is encoded in 4 bits:\n//  -  0 means the channel or index is unused.\n//  -  x means a bit depth of  x, for x in the range 1..8.\n//  -  9 means a bit depth of 10.\n//  - 10 means a bit depth of 12.\n//  - 11 means a bit depth of 16.\n//  - 12 means a bit depth of 24.\n//  - 13 means a bit depth of 32.\n//  - 14 means a bit depth of 48.\n//  - 15 means a bit depth of 64.\n//\n// For example, wuffs_base__pixel_format 0x5510BBBB is a natural format for\n// decoding a PNG image - network byte order (also known as big-endian),\n// interleaved, non-premultiplied alpha - that happens to be 16-bit-depth\n// truecolor with alpha (RGBA). In memory order:\n//\n//  ptr+0  ptr+1  ptr+2  ptr+3  ptr+4  ptr+5  ptr+6  ptr+7\n//  Rhi    Rlo    Ghi    Glo    Bhi    Blo    Ahi    Alo\n//\n// For example, the value wuffs_base__pixel_format 0x40000565 means BGR with no\n// alpha or padding, 5/6/5 bits for blue/green/red, interleaved 2 bytes per\n// pixel, laid out LSB-first in memory order:\n//\n//  ptr+0...........  ptr+1...." +
+	".......\n//  MSB          LSB  MSB          LSB\n//  G₂G₁G₀B₄B₃B₂B₁B₀  R₄R₃R₂R₁R₀G₅G₄G₃\n//\n// On little-endian systems (but not big-endian), this Wuffs pixel format value\n// (0x40000565) corresponds to the Cairo library's CAIRO_FORMAT_RGB16_565, the\n// SDL2 (Simple DirectMedia Layer 2) library's SDL_PIXELFORMAT_RGB565 and the\n// Skia library's kRGB_565_SkColorType. Note BGR in Wuffs versus RGB in the\n// other libraries.\n//\n// Regardless of endianness, this Wuffs pixel format value (0x40000565)\n// corresponds to the V4L2 (Video For Linux 2) library's V4L2_PIX_FMT_RGB565\n// and the Wayland-DRM library's WL_DRM_FORMAT_RGB565.\n//\n// Different software libraries name their pixel formats (and especially their\n// channel order) either according to memory layout or as bits of a native\n// integer type like uint32_t. The two conventions differ because of a system's\n// endianness. As mentioned earlier, Wuffs pixel formats are always in memory\n// order. More detail of other software librarie" +
+	"s' naming conventions is in the\n// Pixel Format Guide at https://afrantzis.github.io/pixel-format-guide/\n//\n// Do not manipulate these bits directly; they are private implementation\n// details. Use methods such as wuffs_base__pixel_format__num_planes instead.\ntypedef uint32_t wuffs_base__pixel_format;\n\n// Common 8-bit-depth pixel formats. This list is not exhaustive; not all valid\n// wuffs_base__pixel_format values are present.\n\n#define WUFFS_BASE__PIXEL_FORMAT__INVALID ((wuffs_base__pixel_format)0x00000000)\n\n#define WUFFS_BASE__PIXEL_FORMAT__A ((wuffs_base__pixel_format)0x02000008)\n\n#define WUFFS_BASE__PIXEL_FORMAT__Y ((wuffs_base__pixel_format)0x10000008)\n#define WUFFS_BASE__PIXEL_FORMAT__YA_NONPREMUL \\\n  ((wuffs_base__pixel_format)0x15000008)\n#define WUFFS_BASE__PIXEL_FORMAT__YA_PREMUL \\\n  ((wuffs_base__pixel_format)0x16000008)\n\n#define WUFFS_BASE__PIXEL_FORMAT__YCBCR ((wuffs_base__pixel_format)0x20020888)\n#define WUFFS_BASE__PIXEL_FORMAT__YCBCRK ((wuffs_base__pixel_format)0x21038888)\n#define WUFFS_BASE__P" +
+	"IXEL_FORMAT__YCBCRA_NONPREMUL \\\n  ((wuffs_base__pixel_format)0x25038888)\n\n#define WUFFS_BASE__PIXEL_FORMAT__YCOCG ((wuffs_base__pixel_format)0x30020888)\n#define WUFFS_BASE__PIXEL_FORMAT__YCOCGK ((wuffs_base__pixel_format)0x31038888)\n#define WUFFS_BASE__PIXEL_FORMAT__YCOCGA_NONPREMUL \\\n  ((wuffs_base__pixel_format)0x35038888)\n\n#define WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_NONPREMUL \\\n  ((wuffs_base__pixel_format)0x45040008)\n#define WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_PREMUL \\\n  ((wuffs_base__pixel_format)0x46040008)\n#define WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_BINARY \\\n  ((wuffs_base__pixel_format)0x47040008)\n\n#define WUFFS_BASE__PIXEL_FORMAT__BGR ((wuffs_base__pixel_format)0x40000888)\n#define WUFFS_BASE__PIXEL_FORMAT__BGRX ((wuffs_base__pixel_format)0x41008888)\n#define WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL \\\n  ((wuffs_base__pixel_format)0x45008888)\n#define WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL \\\n  ((wuffs_base__pixel_format)0x46008888)\n#define WUFFS_BASE__PIXEL_FORMAT__BGRA_BINARY \\\n  ((wuffs_base" +
+	"__pixel_format)0x47008888)\n\n#define WUFFS_BASE__PIXEL_FORMAT__RGB ((wuffs_base__pixel_format)0x50000888)\n#define WUFFS_BASE__PIXEL_FORMAT__RGBX ((wuffs_base__pixel_format)0x51008888)\n#define WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL \\\n  ((wuffs_base__pixel_format)0x55008888)\n#define WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL \\\n  ((wuffs_base__pixel_format)0x56008888)\n#define WUFFS_BASE__PIXEL_FORMAT__RGBA_BINARY \\\n  ((wuffs_base__pixel_format)0x57008888)\n\n#define WUFFS_BASE__PIXEL_FORMAT__CMY ((wuffs_base__pixel_format)0x60020888)\n#define WUFFS_BASE__PIXEL_FORMAT__CMYK ((wuffs_base__pixel_format)0x61038888)\n\nextern const uint32_t wuffs_base__pixel_format__bits_per_channel[16];\n\nstatic inline bool  //\nwuffs_base__pixel_format__is_valid(wuffs_base__pixel_format f) {\n  return f != 0;\n}\n\n// wuffs_base__pixel_format__bits_per_pixel returns the number of bits per\n// pixel for interleaved pixel formats, and returns 0 for planar pixel formats.\nstatic inline uint32_t  //\nwuffs_base__pixel_format__bits_per_pixel(wuffs_base__" +
+	"pixel_format f) {\n  if (((f >> 16) & 0x03) != 0) {\n    return 0;\n  }\n  return wuffs_base__pixel_format__bits_per_channel[0x0F & (f >> 0)] +\n         wuffs_base__pixel_format__bits_per_channel[0x0F & (f >> 4)] +\n         wuffs_base__pixel_format__bits_per_channel[0x0F & (f >> 8)] +\n         wuffs_base__pixel_format__bits_per_channel[0x0F & (f >> 12)];\n}\n\nstatic inline bool  //\nwuffs_base__pixel_format__is_indexed(wuffs_base__pixel_format f) {\n  return (f >> 18) & 0x01;\n}\n\nstatic inline bool  //\nwuffs_base__pixel_format__is_interleaved(wuffs_base__pixel_format f) {\n  return ((f >> 16) & 0x03) == 0;\n}\n\nstatic inline bool  //\nwuffs_base__pixel_format__is_planar(wuffs_base__pixel_format f) {\n  return ((f >> 16) & 0x03) != 0;\n}\n\nstatic inline uint32_t  //\nwuffs_base__pixel_format__num_planes(wuffs_base__pixel_format f) {\n  return ((f >> 16) & 0x03) + 1;\n}\n\n#define WUFFS_BASE__PIXEL_FORMAT__NUM_PLANES_MAX 4\n\n#define WUFFS_BASE__PIXEL_FORMAT__INDEXED__INDEX_PLANE 0\n#define WUFFS_BASE__PIXEL_FORMAT__INDEXED__COLOR_PLA" +
+	"NE 3\n\n" +
 	"" +
-	"// --------\n\n// wuffs_base__pixel_subsampling encodes the mapping of pixel space coordinates\n// (x, y) to pixel buffer indices (i, j). That mapping can differ for each\n// plane p. For a depth of 8 bits (1 byte), the p'th plane's sample starts at\n// (planes[p].ptr + (j * planes[p].stride) + i).\n//\n// For packed pixel formats, the mapping is trivial: i = x and j = y. For\n// planar pixel formats, the mapping can differ due to chroma subsampling. For\n// example, consider a three plane YCbCr pixel format with 4:2:2 subsampling.\n// For the luma (Y) channel, there is one sample for every pixel, but for the\n// chroma (Cb, Cr) channels, there is one sample for every two pixels: pairs of\n// horizontally adjacent pixels form one macropixel, i = x / 2 and j == y. In\n// general, for a given p:\n//  - i = (x + bias_x) >> shift_x.\n//  - j = (y + bias_y) >> shift_y.\n// where biases and shifts are in the range 0..3 and 0..2 respectively.\n//\n// In general, the biases will be zero after decoding an image. However, making\n// a su" +
-	"b-image may change the bias, since the (x, y) coordinates are relative\n// to the sub-image's top-left origin, but the backing pixel buffers were\n// created relative to the original image's origin.\n//\n// For each plane p, each of those four numbers (biases and shifts) are encoded\n// in two bits, which combine to form an 8 bit unsigned integer:\n//\n//  e_p = (bias_x << 6) | (shift_x << 4) | (bias_y << 2) | (shift_y << 0)\n//\n// Those e_p values (e_0 for the first plane, e_1 for the second plane, etc)\n// combine to form a wuffs_base__pixel_subsampling value:\n//\n//  pixsub = (e_3 << 24) | (e_2 << 16) | (e_1 << 8) | (e_0 << 0)\n//\n// Do not manipulate these bits directly; they are private implementation\n// details. Use methods such as wuffs_base__pixel_subsampling__bias_x instead.\ntypedef uint32_t wuffs_base__pixel_subsampling;\n\n#define WUFFS_BASE__PIXEL_SUBSAMPLING__NONE ((wuffs_base__pixel_subsampling)0)\n\n#define WUFFS_BASE__PIXEL_SUBSAMPLING__444 \\\n  ((wuffs_base__pixel_subsampling)0x000000)\n#define WUFFS_BASE__PI" +
-	"XEL_SUBSAMPLING__440 \\\n  ((wuffs_base__pixel_subsampling)0x010100)\n#define WUFFS_BASE__PIXEL_SUBSAMPLING__422 \\\n  ((wuffs_base__pixel_subsampling)0x101000)\n#define WUFFS_BASE__PIXEL_SUBSAMPLING__420 \\\n  ((wuffs_base__pixel_subsampling)0x111100)\n#define WUFFS_BASE__PIXEL_SUBSAMPLING__411 \\\n  ((wuffs_base__pixel_subsampling)0x202000)\n#define WUFFS_BASE__PIXEL_SUBSAMPLING__410 \\\n  ((wuffs_base__pixel_subsampling)0x212100)\n\nstatic inline uint32_t  //\nwuffs_base__pixel_subsampling__bias_x(wuffs_base__pixel_subsampling s,\n                                      uint32_t plane) {\n  uint32_t shift = ((plane & 0x03) * 8) + 6;\n  return (s >> shift) & 0x03;\n}\n\nstatic inline uint32_t  //\nwuffs_base__pixel_subsampling__shift_x(wuffs_base__pixel_subsampling s,\n                                       uint32_t plane) {\n  uint32_t shift = ((plane & 0x03) * 8) + 4;\n  return (s >> shift) & 0x03;\n}\n\nstatic inline uint32_t  //\nwuffs_base__pixel_subsampling__bias_y(wuffs_base__pixel_subsampling s,\n                                    " +
-	"  uint32_t plane) {\n  uint32_t shift = ((plane & 0x03) * 8) + 2;\n  return (s >> shift) & 0x03;\n}\n\nstatic inline uint32_t  //\nwuffs_base__pixel_subsampling__shift_y(wuffs_base__pixel_subsampling s,\n                                       uint32_t plane) {\n  uint32_t shift = ((plane & 0x03) * 8) + 0;\n  return (s >> shift) & 0x03;\n}\n\n" +
+	"// --------\n\n// wuffs_base__pixel_subsampling encodes the mapping of pixel space coordinates\n// (x, y) to pixel buffer indices (i, j). That mapping can differ for each\n// plane p. For a depth of 8 bits (1 byte), the p'th plane's sample starts at\n// (planes[p].ptr + (j * planes[p].stride) + i).\n//\n// For interleaved pixel formats, the mapping is trivial: i = x and j = y. For\n// planar pixel formats, the mapping can differ due to chroma subsampling. For\n// example, consider a three plane YCbCr pixel format with 4:2:2 subsampling.\n// For the luma (Y) channel, there is one sample for every pixel, but for the\n// chroma (Cb, Cr) channels, there is one sample for every two pixels: pairs of\n// horizontally adjacent pixels form one macropixel, i = x / 2 and j == y. In\n// general, for a given p:\n//  - i = (x + bias_x) >> shift_x.\n//  - j = (y + bias_y) >> shift_y.\n// where biases and shifts are in the range 0..3 and 0..2 respectively.\n//\n// In general, the biases will be zero after decoding an image. However, making\n//" +
+	" a sub-image may change the bias, since the (x, y) coordinates are relative\n// to the sub-image's top-left origin, but the backing pixel buffers were\n// created relative to the original image's origin.\n//\n// For each plane p, each of those four numbers (biases and shifts) are encoded\n// in two bits, which combine to form an 8 bit unsigned integer:\n//\n//  e_p = (bias_x << 6) | (shift_x << 4) | (bias_y << 2) | (shift_y << 0)\n//\n// Those e_p values (e_0 for the first plane, e_1 for the second plane, etc)\n// combine to form a wuffs_base__pixel_subsampling value:\n//\n//  pixsub = (e_3 << 24) | (e_2 << 16) | (e_1 << 8) | (e_0 << 0)\n//\n// Do not manipulate these bits directly; they are private implementation\n// details. Use methods such as wuffs_base__pixel_subsampling__bias_x instead.\ntypedef uint32_t wuffs_base__pixel_subsampling;\n\n#define WUFFS_BASE__PIXEL_SUBSAMPLING__NONE ((wuffs_base__pixel_subsampling)0)\n\n#define WUFFS_BASE__PIXEL_SUBSAMPLING__444 \\\n  ((wuffs_base__pixel_subsampling)0x000000)\n#define WUFFS_BAS" +
+	"E__PIXEL_SUBSAMPLING__440 \\\n  ((wuffs_base__pixel_subsampling)0x010100)\n#define WUFFS_BASE__PIXEL_SUBSAMPLING__422 \\\n  ((wuffs_base__pixel_subsampling)0x101000)\n#define WUFFS_BASE__PIXEL_SUBSAMPLING__420 \\\n  ((wuffs_base__pixel_subsampling)0x111100)\n#define WUFFS_BASE__PIXEL_SUBSAMPLING__411 \\\n  ((wuffs_base__pixel_subsampling)0x202000)\n#define WUFFS_BASE__PIXEL_SUBSAMPLING__410 \\\n  ((wuffs_base__pixel_subsampling)0x212100)\n\nstatic inline uint32_t  //\nwuffs_base__pixel_subsampling__bias_x(wuffs_base__pixel_subsampling s,\n                                      uint32_t plane) {\n  uint32_t shift = ((plane & 0x03) * 8) + 6;\n  return (s >> shift) & 0x03;\n}\n\nstatic inline uint32_t  //\nwuffs_base__pixel_subsampling__shift_x(wuffs_base__pixel_subsampling s,\n                                       uint32_t plane) {\n  uint32_t shift = ((plane & 0x03) * 8) + 4;\n  return (s >> shift) & 0x03;\n}\n\nstatic inline uint32_t  //\nwuffs_base__pixel_subsampling__bias_y(wuffs_base__pixel_subsampling s,\n                               " +
+	"       uint32_t plane) {\n  uint32_t shift = ((plane & 0x03) * 8) + 2;\n  return (s >> shift) & 0x03;\n}\n\nstatic inline uint32_t  //\nwuffs_base__pixel_subsampling__shift_y(wuffs_base__pixel_subsampling s,\n                                       uint32_t plane) {\n  uint32_t shift = ((plane & 0x03) * 8) + 0;\n  return (s >> shift) & 0x03;\n}\n\n" +
 	"" +
-	"// --------\n\ntypedef struct {\n  // Do not access the private_impl's fields directly. There is no API/ABI\n  // compatibility or safety guarantee if you do so.\n  struct {\n    wuffs_base__pixel_format pixfmt;\n    wuffs_base__pixel_subsampling pixsub;\n    uint32_t width;\n    uint32_t height;\n  } private_impl;\n\n#ifdef __cplusplus\n  inline void set(wuffs_base__pixel_format pixfmt,\n                  wuffs_base__pixel_subsampling pixsub,\n                  uint32_t width,\n                  uint32_t height);\n  inline void invalidate();\n  inline bool is_valid();\n  inline wuffs_base__pixel_format pixel_format();\n  inline wuffs_base__pixel_subsampling pixel_subsampling();\n  inline wuffs_base__rect_ie_u32 bounds();\n  inline uint32_t width();\n  inline uint32_t height();\n  inline uint64_t pixbuf_len();\n#endif  // __cplusplus\n\n} wuffs_base__pixel_config;\n\nstatic inline wuffs_base__pixel_config  //\nwuffs_base__null_pixel_config() {\n  wuffs_base__pixel_config ret;\n  ret.private_impl.pixfmt = 0;\n  ret.private_impl.pixsub = 0;\n  " +
-	"ret.private_impl.width = 0;\n  ret.private_impl.height = 0;\n  return ret;\n}\n\n// TODO: Should this function return bool? An error type?\nstatic inline void  //\nwuffs_base__pixel_config__set(wuffs_base__pixel_config* c,\n                              wuffs_base__pixel_format pixfmt,\n                              wuffs_base__pixel_subsampling pixsub,\n                              uint32_t width,\n                              uint32_t height) {\n  if (!c) {\n    return;\n  }\n  if (pixfmt) {\n    uint64_t wh = ((uint64_t)width) * ((uint64_t)height);\n    // TODO: handle things other than 1 byte per pixel.\n    if (wh <= ((uint64_t)SIZE_MAX)) {\n      c->private_impl.pixfmt = pixfmt;\n      c->private_impl.pixsub = pixsub;\n      c->private_impl.width = width;\n      c->private_impl.height = height;\n      return;\n    }\n  }\n\n  c->private_impl.pixfmt = 0;\n  c->private_impl.pixsub = 0;\n  c->private_impl.width = 0;\n  c->private_impl.height = 0;\n}\n\nstatic inline void  //\nwuffs_base__pixel_config__invalidate(wuffs_base__pixel_config*" +
-	" c) {\n  if (c) {\n    c->private_impl.pixfmt = 0;\n    c->private_impl.pixsub = 0;\n    c->private_impl.width = 0;\n    c->private_impl.height = 0;\n  }\n}\n\nstatic inline bool  //\nwuffs_base__pixel_config__is_valid(wuffs_base__pixel_config* c) {\n  return c && c->private_impl.pixfmt;\n}\n\nstatic inline wuffs_base__pixel_format  //\nwuffs_base__pixel_config__pixel_format(wuffs_base__pixel_config* c) {\n  return c ? c->private_impl.pixfmt : 0;\n}\n\nstatic inline wuffs_base__pixel_subsampling  //\nwuffs_base__pixel_config__pixel_subsampling(wuffs_base__pixel_config* c) {\n  return c ? c->private_impl.pixsub : 0;\n}\n\nstatic inline wuffs_base__rect_ie_u32  //\nwuffs_base__pixel_config__bounds(wuffs_base__pixel_config* c) {\n  if (c) {\n    wuffs_base__rect_ie_u32 ret;\n    ret.min_incl_x = 0;\n    ret.min_incl_y = 0;\n    ret.max_excl_x = c->private_impl.width;\n    ret.max_excl_y = c->private_impl.height;\n    return ret;\n  }\n\n  wuffs_base__rect_ie_u32 ret;\n  ret.min_incl_x = 0;\n  ret.min_incl_y = 0;\n  ret.max_excl_x = 0;\n  ret.max_excl" +
-	"_y = 0;\n  return ret;\n}\n\nstatic inline uint32_t  //\nwuffs_base__pixel_config__width(wuffs_base__pixel_config* c) {\n  return c ? c->private_impl.width : 0;\n}\n\nstatic inline uint32_t  //\nwuffs_base__pixel_config__height(wuffs_base__pixel_config* c) {\n  return c ? c->private_impl.height : 0;\n}\n\n// TODO: this is the right API for planar (not packed) pixbufs? Should it allow\n// decoding into a color model different from the format's intrinsic one? For\n// example, decoding a JPEG image straight to RGBA instead of to YCbCr?\nstatic inline uint64_t  //\nwuffs_base__pixel_config__pixbuf_len(wuffs_base__pixel_config* c) {\n  if (!c) {\n    return 0;\n  }\n  if (wuffs_base__pixel_format__is_planar(c->private_impl.pixfmt)) {\n    // TODO: support planar pixel formats, concious of pixel subsampling.\n    return 0;\n  }\n  uint32_t bits_per_pixel =\n      wuffs_base__pixel_format__bits_per_pixel(c->private_impl.pixfmt);\n  if ((bits_per_pixel == 0) || ((bits_per_pixel % 8) != 0)) {\n    // TODO: support fraction-of-byte pixels, e.g. 1 " +
-	"bit per pixel?\n    return 0;\n  }\n  uint64_t bytes_per_pixel = bits_per_pixel / 8;\n\n  uint64_t n =\n      ((uint64_t)c->private_impl.width) * ((uint64_t)c->private_impl.height);\n  if (n > (UINT64_MAX / bytes_per_pixel)) {\n    return 0;\n  }\n  n *= bytes_per_pixel;\n\n  if (wuffs_base__pixel_format__is_indexed(c->private_impl.pixfmt)) {\n    if (n > (UINT64_MAX - 1024)) {\n      return 0;\n    }\n    n += 1024;\n  }\n\n  return n;\n}\n\n#ifdef __cplusplus\n\ninline void  //\nwuffs_base__pixel_config::set(wuffs_base__pixel_format pixfmt,\n                              wuffs_base__pixel_subsampling pixsub,\n                              uint32_t width,\n                              uint32_t height) {\n  wuffs_base__pixel_config__set(this, pixfmt, pixsub, width, height);\n}\n\ninline void  //\nwuffs_base__pixel_config::invalidate() {\n  wuffs_base__pixel_config__invalidate(this);\n}\n\ninline bool  //\nwuffs_base__pixel_config::is_valid() {\n  return wuffs_base__pixel_config__is_valid(this);\n}\n\ninline wuffs_base__pixel_format  //\nwuffs_base__p" +
-	"ixel_config::pixel_format() {\n  return wuffs_base__pixel_config__pixel_format(this);\n}\n\ninline wuffs_base__pixel_subsampling  //\nwuffs_base__pixel_config::pixel_subsampling() {\n  return wuffs_base__pixel_config__pixel_subsampling(this);\n}\n\ninline wuffs_base__rect_ie_u32  //\nwuffs_base__pixel_config::bounds() {\n  return wuffs_base__pixel_config__bounds(this);\n}\n\ninline uint32_t  //\nwuffs_base__pixel_config::width() {\n  return wuffs_base__pixel_config__width(this);\n}\n\ninline uint32_t  //\nwuffs_base__pixel_config::height() {\n  return wuffs_base__pixel_config__height(this);\n}\n\ninline uint64_t  //\nwuffs_base__pixel_config::pixbuf_len() {\n  return wuffs_base__pixel_config__pixbuf_len(this);\n}\n\n#endif  // __cplusplus\n\n" +
+	"// --------\n\ntypedef struct {\n  // Do not access the private_impl's fields directly. There is no API/ABI\n  // compatibility or safety guarantee if you do so.\n  struct {\n    wuffs_base__pixel_format pixfmt;\n    wuffs_base__pixel_subsampling pixsub;\n    uint32_t width;\n    uint32_t height;\n  } private_impl;\n\n#ifdef __cplusplus\n  inline void set(wuffs_base__pixel_format pixfmt,\n                  wuffs_base__pixel_subsampling pixsub,\n                  uint32_t width,\n                  uint32_t height);\n  inline void invalidate();\n  inline bool is_valid() const;\n  inline wuffs_base__pixel_format pixel_format() const;\n  inline wuffs_base__pixel_subsampling pixel_subsampling() const;\n  inline wuffs_base__rect_ie_u32 bounds() const;\n  inline uint32_t width() const;\n  inline uint32_t height() const;\n  inline uint64_t pixbuf_len() const;\n#endif  // __cplusplus\n\n} wuffs_base__pixel_config;\n\nstatic inline wuffs_base__pixel_config  //\nwuffs_base__null_pixel_config() {\n  wuffs_base__pixel_config ret;\n  ret.private_impl.pix" +
+	"fmt = 0;\n  ret.private_impl.pixsub = 0;\n  ret.private_impl.width = 0;\n  ret.private_impl.height = 0;\n  return ret;\n}\n\n// TODO: Should this function return bool? An error type?\nstatic inline void  //\nwuffs_base__pixel_config__set(wuffs_base__pixel_config* c,\n                              wuffs_base__pixel_format pixfmt,\n                              wuffs_base__pixel_subsampling pixsub,\n                              uint32_t width,\n                              uint32_t height) {\n  if (!c) {\n    return;\n  }\n  if (pixfmt) {\n    uint64_t wh = ((uint64_t)width) * ((uint64_t)height);\n    // TODO: handle things other than 1 byte per pixel.\n    if (wh <= ((uint64_t)SIZE_MAX)) {\n      c->private_impl.pixfmt = pixfmt;\n      c->private_impl.pixsub = pixsub;\n      c->private_impl.width = width;\n      c->private_impl.height = height;\n      return;\n    }\n  }\n\n  c->private_impl.pixfmt = 0;\n  c->private_impl.pixsub = 0;\n  c->private_impl.width = 0;\n  c->private_impl.height = 0;\n}\n\nstatic inline void  //\nwuffs_base__pixel_co" +
+	"nfig__invalidate(wuffs_base__pixel_config* c) {\n  if (c) {\n    c->private_impl.pixfmt = 0;\n    c->private_impl.pixsub = 0;\n    c->private_impl.width = 0;\n    c->private_impl.height = 0;\n  }\n}\n\nstatic inline bool  //\nwuffs_base__pixel_config__is_valid(const wuffs_base__pixel_config* c) {\n  return c && c->private_impl.pixfmt;\n}\n\nstatic inline wuffs_base__pixel_format  //\nwuffs_base__pixel_config__pixel_format(const wuffs_base__pixel_config* c) {\n  return c ? c->private_impl.pixfmt : 0;\n}\n\nstatic inline wuffs_base__pixel_subsampling  //\nwuffs_base__pixel_config__pixel_subsampling(const wuffs_base__pixel_config* c) {\n  return c ? c->private_impl.pixsub : 0;\n}\n\nstatic inline wuffs_base__rect_ie_u32  //\nwuffs_base__pixel_config__bounds(const wuffs_base__pixel_config* c) {\n  if (c) {\n    wuffs_base__rect_ie_u32 ret;\n    ret.min_incl_x = 0;\n    ret.min_incl_y = 0;\n    ret.max_excl_x = c->private_impl.width;\n    ret.max_excl_y = c->private_impl.height;\n    return ret;\n  }\n\n  wuffs_base__rect_ie_u32 ret;\n  ret.min_incl" +
+	"_x = 0;\n  ret.min_incl_y = 0;\n  ret.max_excl_x = 0;\n  ret.max_excl_y = 0;\n  return ret;\n}\n\nstatic inline uint32_t  //\nwuffs_base__pixel_config__width(const wuffs_base__pixel_config* c) {\n  return c ? c->private_impl.width : 0;\n}\n\nstatic inline uint32_t  //\nwuffs_base__pixel_config__height(const wuffs_base__pixel_config* c) {\n  return c ? c->private_impl.height : 0;\n}\n\n// TODO: this is the right API for planar (not interleaved) pixbufs? Should it\n// allow decoding into a color model different from the format's intrinsic one?\n// For example, decoding a JPEG image straight to RGBA instead of to YCbCr?\nstatic inline uint64_t  //\nwuffs_base__pixel_config__pixbuf_len(const wuffs_base__pixel_config* c) {\n  if (!c) {\n    return 0;\n  }\n  if (wuffs_base__pixel_format__is_planar(c->private_impl.pixfmt)) {\n    // TODO: support planar pixel formats, concious of pixel subsampling.\n    return 0;\n  }\n  uint32_t bits_per_pixel =\n      wuffs_base__pixel_format__bits_per_pixel(c->private_impl.pixfmt);\n  if ((bits_per_pixel == 0" +
+	") || ((bits_per_pixel % 8) != 0)) {\n    // TODO: support fraction-of-byte pixels, e.g. 1 bit per pixel?\n    return 0;\n  }\n  uint64_t bytes_per_pixel = bits_per_pixel / 8;\n\n  uint64_t n =\n      ((uint64_t)c->private_impl.width) * ((uint64_t)c->private_impl.height);\n  if (n > (UINT64_MAX / bytes_per_pixel)) {\n    return 0;\n  }\n  n *= bytes_per_pixel;\n\n  if (wuffs_base__pixel_format__is_indexed(c->private_impl.pixfmt)) {\n    if (n > (UINT64_MAX - 1024)) {\n      return 0;\n    }\n    n += 1024;\n  }\n\n  return n;\n}\n\n#ifdef __cplusplus\n\ninline void  //\nwuffs_base__pixel_config::set(wuffs_base__pixel_format pixfmt,\n                              wuffs_base__pixel_subsampling pixsub,\n                              uint32_t width,\n                              uint32_t height) {\n  wuffs_base__pixel_config__set(this, pixfmt, pixsub, width, height);\n}\n\ninline void  //\nwuffs_base__pixel_config::invalidate() {\n  wuffs_base__pixel_config__invalidate(this);\n}\n\ninline bool  //\nwuffs_base__pixel_config::is_valid() const {\n  return" +
+	" wuffs_base__pixel_config__is_valid(this);\n}\n\ninline wuffs_base__pixel_format  //\nwuffs_base__pixel_config::pixel_format() const {\n  return wuffs_base__pixel_config__pixel_format(this);\n}\n\ninline wuffs_base__pixel_subsampling  //\nwuffs_base__pixel_config::pixel_subsampling() const {\n  return wuffs_base__pixel_config__pixel_subsampling(this);\n}\n\ninline wuffs_base__rect_ie_u32  //\nwuffs_base__pixel_config::bounds() const {\n  return wuffs_base__pixel_config__bounds(this);\n}\n\ninline uint32_t  //\nwuffs_base__pixel_config::width() const {\n  return wuffs_base__pixel_config__width(this);\n}\n\ninline uint32_t  //\nwuffs_base__pixel_config::height() const {\n  return wuffs_base__pixel_config__height(this);\n}\n\ninline uint64_t  //\nwuffs_base__pixel_config::pixbuf_len() const {\n  return wuffs_base__pixel_config__pixbuf_len(this);\n}\n\n#endif  // __cplusplus\n\n" +
 	"" +
-	"// --------\n\ntypedef struct {\n  wuffs_base__pixel_config pixcfg;\n\n  // Do not access the private_impl's fields directly. There is no API/ABI\n  // compatibility or safety guarantee if you do so.\n  struct {\n    uint64_t first_frame_io_position;\n    bool first_frame_is_opaque;\n  } private_impl;\n\n#ifdef __cplusplus\n  inline void set(wuffs_base__pixel_format pixfmt,\n                  wuffs_base__pixel_subsampling pixsub,\n                  uint32_t width,\n                  uint32_t height,\n                  uint64_t first_frame_io_position,\n                  bool first_frame_is_opaque);\n  inline void invalidate();\n  inline bool is_valid();\n  inline uint64_t first_frame_io_position();\n  inline bool first_frame_is_opaque();\n#endif  // __cplusplus\n\n} wuffs_base__image_config;\n\nstatic inline wuffs_base__image_config  //\nwuffs_base__null_image_config() {\n  wuffs_base__image_config ret;\n  ret.pixcfg = wuffs_base__null_pixel_config();\n  ret.private_impl.first_frame_io_position = 0;\n  ret.private_impl.first_frame_is_opaque" +
-	" = false;\n  return ret;\n}\n\n// TODO: Should this function return bool? An error type?\nstatic inline void  //\nwuffs_base__image_config__set(wuffs_base__image_config* c,\n                              wuffs_base__pixel_format pixfmt,\n                              wuffs_base__pixel_subsampling pixsub,\n                              uint32_t width,\n                              uint32_t height,\n                              uint64_t first_frame_io_position,\n                              bool first_frame_is_opaque) {\n  if (!c) {\n    return;\n  }\n  if (wuffs_base__pixel_format__is_valid(pixfmt)) {\n    c->pixcfg.private_impl.pixfmt = pixfmt;\n    c->pixcfg.private_impl.pixsub = pixsub;\n    c->pixcfg.private_impl.width = width;\n    c->pixcfg.private_impl.height = height;\n    c->private_impl.first_frame_io_position = first_frame_io_position;\n    c->private_impl.first_frame_is_opaque = first_frame_is_opaque;\n    return;\n  }\n\n  c->pixcfg.private_impl.pixfmt = 0;\n  c->pixcfg.private_impl.pixsub = 0;\n  c->pixcfg.private_impl.w" +
-	"idth = 0;\n  c->pixcfg.private_impl.height = 0;\n  c->private_impl.first_frame_io_position = 0;\n  c->private_impl.first_frame_is_opaque = 0;\n}\n\nstatic inline void  //\nwuffs_base__image_config__invalidate(wuffs_base__image_config* c) {\n  if (c) {\n    c->pixcfg.private_impl.pixfmt = 0;\n    c->pixcfg.private_impl.pixsub = 0;\n    c->pixcfg.private_impl.width = 0;\n    c->pixcfg.private_impl.height = 0;\n    c->private_impl.first_frame_io_position = 0;\n    c->private_impl.first_frame_is_opaque = 0;\n  }\n}\n\nstatic inline bool  //\nwuffs_base__image_config__is_valid(wuffs_base__image_config* c) {\n  return c && wuffs_base__pixel_config__is_valid(&(c->pixcfg));\n}\n\nstatic inline uint64_t  //\nwuffs_base__image_config__first_frame_io_position(wuffs_base__image_config* c) {\n  return c ? c->private_impl.first_frame_io_position : 0;\n}\n\nstatic inline bool  //\nwuffs_base__image_config__first_frame_is_opaque(wuffs_base__image_config* c) {\n  return c ? c->private_impl.first_frame_is_opaque : false;\n}\n\n#ifdef __cplusplus\n\ninline void " +
-	" //\nwuffs_base__image_config::set(wuffs_base__pixel_format pixfmt,\n                              wuffs_base__pixel_subsampling pixsub,\n                              uint32_t width,\n                              uint32_t height,\n                              uint64_t first_frame_io_position,\n                              bool first_frame_is_opaque) {\n  wuffs_base__image_config__set(this, pixfmt, pixsub, width, height,\n                                first_frame_io_position, first_frame_is_opaque);\n}\n\ninline void  //\nwuffs_base__image_config::invalidate() {\n  wuffs_base__image_config__invalidate(this);\n}\n\ninline bool  //\nwuffs_base__image_config::is_valid() {\n  return wuffs_base__image_config__is_valid(this);\n}\n\ninline uint64_t  //\nwuffs_base__image_config::first_frame_io_position() {\n  return wuffs_base__image_config__first_frame_io_position(this);\n}\n\ninline bool  //\nwuffs_base__image_config::first_frame_is_opaque() {\n  return wuffs_base__image_config__first_frame_is_opaque(this);\n}\n\n#endif  // __cplusplus\n\n" +
+	"// --------\n\ntypedef struct {\n  wuffs_base__pixel_config pixcfg;\n\n  // Do not access the private_impl's fields directly. There is no API/ABI\n  // compatibility or safety guarantee if you do so.\n  struct {\n    uint64_t first_frame_io_position;\n    bool first_frame_is_opaque;\n  } private_impl;\n\n#ifdef __cplusplus\n  inline void set(wuffs_base__pixel_format pixfmt,\n                  wuffs_base__pixel_subsampling pixsub,\n                  uint32_t width,\n                  uint32_t height,\n                  uint64_t first_frame_io_position,\n                  bool first_frame_is_opaque);\n  inline void invalidate();\n  inline bool is_valid() const;\n  inline uint64_t first_frame_io_position() const;\n  inline bool first_frame_is_opaque() const;\n#endif  // __cplusplus\n\n} wuffs_base__image_config;\n\nstatic inline wuffs_base__image_config  //\nwuffs_base__null_image_config() {\n  wuffs_base__image_config ret;\n  ret.pixcfg = wuffs_base__null_pixel_config();\n  ret.private_impl.first_frame_io_position = 0;\n  ret.private_impl.fir" +
+	"st_frame_is_opaque = false;\n  return ret;\n}\n\n// TODO: Should this function return bool? An error type?\nstatic inline void  //\nwuffs_base__image_config__set(wuffs_base__image_config* c,\n                              wuffs_base__pixel_format pixfmt,\n                              wuffs_base__pixel_subsampling pixsub,\n                              uint32_t width,\n                              uint32_t height,\n                              uint64_t first_frame_io_position,\n                              bool first_frame_is_opaque) {\n  if (!c) {\n    return;\n  }\n  if (wuffs_base__pixel_format__is_valid(pixfmt)) {\n    c->pixcfg.private_impl.pixfmt = pixfmt;\n    c->pixcfg.private_impl.pixsub = pixsub;\n    c->pixcfg.private_impl.width = width;\n    c->pixcfg.private_impl.height = height;\n    c->private_impl.first_frame_io_position = first_frame_io_position;\n    c->private_impl.first_frame_is_opaque = first_frame_is_opaque;\n    return;\n  }\n\n  c->pixcfg.private_impl.pixfmt = 0;\n  c->pixcfg.private_impl.pixsub = 0;\n  c->pix" +
+	"cfg.private_impl.width = 0;\n  c->pixcfg.private_impl.height = 0;\n  c->private_impl.first_frame_io_position = 0;\n  c->private_impl.first_frame_is_opaque = 0;\n}\n\nstatic inline void  //\nwuffs_base__image_config__invalidate(wuffs_base__image_config* c) {\n  if (c) {\n    c->pixcfg.private_impl.pixfmt = 0;\n    c->pixcfg.private_impl.pixsub = 0;\n    c->pixcfg.private_impl.width = 0;\n    c->pixcfg.private_impl.height = 0;\n    c->private_impl.first_frame_io_position = 0;\n    c->private_impl.first_frame_is_opaque = 0;\n  }\n}\n\nstatic inline bool  //\nwuffs_base__image_config__is_valid(const wuffs_base__image_config* c) {\n  return c && wuffs_base__pixel_config__is_valid(&(c->pixcfg));\n}\n\nstatic inline uint64_t  //\nwuffs_base__image_config__first_frame_io_position(\n    const wuffs_base__image_config* c) {\n  return c ? c->private_impl.first_frame_io_position : 0;\n}\n\nstatic inline bool  //\nwuffs_base__image_config__first_frame_is_opaque(\n    const wuffs_base__image_config* c) {\n  return c ? c->private_impl.first_frame_is_opaqu" +
+	"e : false;\n}\n\n#ifdef __cplusplus\n\ninline void  //\nwuffs_base__image_config::set(wuffs_base__pixel_format pixfmt,\n                              wuffs_base__pixel_subsampling pixsub,\n                              uint32_t width,\n                              uint32_t height,\n                              uint64_t first_frame_io_position,\n                              bool first_frame_is_opaque) {\n  wuffs_base__image_config__set(this, pixfmt, pixsub, width, height,\n                                first_frame_io_position, first_frame_is_opaque);\n}\n\ninline void  //\nwuffs_base__image_config::invalidate() {\n  wuffs_base__image_config__invalidate(this);\n}\n\ninline bool  //\nwuffs_base__image_config::is_valid() const {\n  return wuffs_base__image_config__is_valid(this);\n}\n\ninline uint64_t  //\nwuffs_base__image_config::first_frame_io_position() const {\n  return wuffs_base__image_config__first_frame_io_position(this);\n}\n\ninline bool  //\nwuffs_base__image_config::first_frame_is_opaque() const {\n  return wuffs_base__image_co" +
+	"nfig__first_frame_is_opaque(this);\n}\n\n#endif  // __cplusplus\n\n" +
 	"" +
 	"// --------\n\n// wuffs_base__animation_blend encodes, for an animated image, how to blend the\n// transparent pixels of this frame with the existing canvas. In Porter-Duff\n// compositing operator terminology:\n//  - 0 means the frame may be transparent, and should be blended \"src over\n//    dst\", also known as just \"over\".\n//  - 1 means the frame may be transparent, and should be blended \"src\".\n//  - 2 means the frame is completely opaque, so that \"src over dst\" and \"src\"\n//    are equivalent.\n//\n// These semantics are conservative. It is valid for a completely opaque frame\n// to have a blend value other than 2.\ntypedef uint8_t wuffs_base__animation_blend;\n\n#define WUFFS_BASE__ANIMATION_BLEND__SRC_OVER_DST \\\n  ((wuffs_base__animation_blend)0)\n#define WUFFS_BASE__ANIMATION_BLEND__SRC ((wuffs_base__animation_blend)1)\n#define WUFFS_BASE__ANIMATION_BLEND__OPAQUE ((wuffs_base__animation_blend)2)\n\n" +
 	"" +
 	"// --------\n\n// wuffs_base__animation_disposal encodes, for an animated image, how to\n// dispose of a frame after displaying it:\n//  - None means to draw the next frame on top of this one.\n//  - Restore Background means to clear the frame's dirty rectangle to \"the\n//    background color\" (in practice, this means transparent black) before\n//    drawing the next frame.\n//  - Restore Previous means to undo the current frame, so that the next frame\n//    is drawn on top of the previous one.\ntypedef uint8_t wuffs_base__animation_disposal;\n\n#define WUFFS_BASE__ANIMATION_DISPOSAL__NONE ((wuffs_base__animation_disposal)0)\n#define WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_BACKGROUND \\\n  ((wuffs_base__animation_disposal)1)\n#define WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_PREVIOUS \\\n  ((wuffs_base__animation_disposal)2)\n\n" +
 	"" +
-	"// --------\n\ntypedef struct {\n  // Do not access the private_impl's fields directly. There is no API/ABI\n  // compatibility or safety guarantee if you do so.\n  struct {\n    wuffs_base__rect_ie_u32 bounds;\n    wuffs_base__flicks duration;\n    uint64_t index;\n    uint64_t io_position;\n    wuffs_base__animation_blend blend;\n    wuffs_base__animation_disposal disposal;\n  } private_impl;\n\n#ifdef __cplusplus\n  inline void update(wuffs_base__rect_ie_u32 bounds,\n                     wuffs_base__flicks duration,\n                     uint64_t index,\n                     uint64_t io_position,\n                     wuffs_base__animation_blend blend,\n                     wuffs_base__animation_disposal disposal);\n  inline wuffs_base__rect_ie_u32 bounds();\n  inline uint32_t width();\n  inline uint32_t height();\n  inline wuffs_base__flicks duration();\n  inline uint64_t index();\n  inline uint64_t io_position();\n  inline wuffs_base__animation_blend blend();\n  inline wuffs_base__animation_disposal disposal();\n#endif  // __cpluspl" +
-	"us\n\n} wuffs_base__frame_config;\n\nstatic inline wuffs_base__frame_config  //\nwuffs_base__null_frame_config() {\n  wuffs_base__frame_config ret;\n  ret.private_impl.bounds = wuffs_base__make_rect_ie_u32(0, 0, 0, 0);\n  ret.private_impl.duration = 0;\n  ret.private_impl.index = 0;\n  ret.private_impl.io_position = 0;\n  ret.private_impl.blend = 0;\n  ret.private_impl.disposal = 0;\n  return ret;\n}\n\nstatic inline void  //\nwuffs_base__frame_config__update(wuffs_base__frame_config* c,\n                                 wuffs_base__rect_ie_u32 bounds,\n                                 wuffs_base__flicks duration,\n                                 uint64_t index,\n                                 uint64_t io_position,\n                                 wuffs_base__animation_blend blend,\n                                 wuffs_base__animation_disposal disposal) {\n  if (!c) {\n    return;\n  }\n\n  c->private_impl.bounds = bounds;\n  c->private_impl.duration = duration;\n  c->private_impl.index = index;\n  c->private_impl.io_position = io_po" +
-	"sition;\n  c->private_impl.blend = blend;\n  c->private_impl.disposal = disposal;\n}\n\nstatic inline wuffs_base__rect_ie_u32  //\nwuffs_base__frame_config__bounds(wuffs_base__frame_config* c) {\n  if (c) {\n    return c->private_impl.bounds;\n  }\n\n  wuffs_base__rect_ie_u32 ret;\n  ret.min_incl_x = 0;\n  ret.min_incl_y = 0;\n  ret.max_excl_x = 0;\n  ret.max_excl_y = 0;\n  return ret;\n}\n\nstatic inline uint32_t  //\nwuffs_base__frame_config__width(wuffs_base__frame_config* c) {\n  return c ? wuffs_base__rect_ie_u32__width(&c->private_impl.bounds) : 0;\n}\n\nstatic inline uint32_t  //\nwuffs_base__frame_config__height(wuffs_base__frame_config* c) {\n  return c ? wuffs_base__rect_ie_u32__height(&c->private_impl.bounds) : 0;\n}\n\n// wuffs_base__frame_config__duration returns the amount of time to display\n// this frame. Zero means to display forever - a still (non-animated) image.\nstatic inline wuffs_base__flicks  //\nwuffs_base__frame_config__duration(wuffs_base__frame_config* c) {\n  return c ? c->private_impl.duration : 0;\n}\n\n// wuffs_b" +
-	"ase__frame_config__index returns the index of this frame. The first\n// frame in an image has index 0, the second frame has index 1, and so on.\nstatic inline uint64_t  //\nwuffs_base__frame_config__index(wuffs_base__frame_config* c) {\n  return c ? c->private_impl.index : 0;\n}\n\n// wuffs_base__frame_config__io_position returns the I/O stream position before\n// the frame config.\nstatic inline uint64_t  //\nwuffs_base__frame_config__io_position(wuffs_base__frame_config* c) {\n  return c ? c->private_impl.io_position : 0;\n}\n\n// wuffs_base__frame_config__blend returns, for an animated image, how to blend\n// the transparent pixels of this frame with the existing canvas.\nstatic inline wuffs_base__animation_blend  //\nwuffs_base__frame_config__blend(wuffs_base__frame_config* c) {\n  return c ? c->private_impl.blend : 0;\n}\n\n// wuffs_base__frame_config__disposal returns, for an animated image, how to\n// dispose of this frame after displaying it.\nstatic inline wuffs_base__animation_disposal  //\nwuffs_base__frame_config__dispos" +
-	"al(wuffs_base__frame_config* c) {\n  return c ? c->private_impl.disposal : 0;\n}\n\n#ifdef __cplusplus\n\ninline void  //\nwuffs_base__frame_config::update(wuffs_base__rect_ie_u32 bounds,\n                                 wuffs_base__flicks duration,\n                                 uint64_t index,\n                                 uint64_t io_position,\n                                 wuffs_base__animation_blend blend,\n                                 wuffs_base__animation_disposal disposal) {\n  wuffs_base__frame_config__update(this, bounds, duration, index, io_position,\n                                   blend, disposal);\n}\n\ninline wuffs_base__rect_ie_u32  //\nwuffs_base__frame_config::bounds() {\n  return wuffs_base__frame_config__bounds(this);\n}\n\ninline uint32_t  //\nwuffs_base__frame_config::width() {\n  return wuffs_base__frame_config__width(this);\n}\n\ninline uint32_t  //\nwuffs_base__frame_config::height() {\n  return wuffs_base__frame_config__height(this);\n}\n\ninline wuffs_base__flicks  //\nwuffs_base__frame_config::du" +
-	"ration() {\n  return wuffs_base__frame_config__duration(this);\n}\n\ninline uint64_t  //\nwuffs_base__frame_config::index() {\n  return wuffs_base__frame_config__index(this);\n}\n\ninline uint64_t  //\nwuffs_base__frame_config::io_position() {\n  return wuffs_base__frame_config__io_position(this);\n}\n\ninline wuffs_base__animation_blend  //\nwuffs_base__frame_config::blend() {\n  return wuffs_base__frame_config__blend(this);\n}\n\ninline wuffs_base__animation_disposal  //\nwuffs_base__frame_config::disposal() {\n  return wuffs_base__frame_config__disposal(this);\n}\n\n#endif  // __cplusplus\n\n" +
+	"// --------\n\ntypedef struct {\n  // Do not access the private_impl's fields directly. There is no API/ABI\n  // compatibility or safety guarantee if you do so.\n  struct {\n    wuffs_base__rect_ie_u32 bounds;\n    wuffs_base__flicks duration;\n    uint64_t index;\n    uint64_t io_position;\n    wuffs_base__animation_blend blend;\n    wuffs_base__animation_disposal disposal;\n    wuffs_base__color_u32_argb_premul background_color;\n  } private_impl;\n\n#ifdef __cplusplus\n  inline void update(wuffs_base__rect_ie_u32 bounds,\n                     wuffs_base__flicks duration,\n                     uint64_t index,\n                     uint64_t io_position,\n                     wuffs_base__animation_blend blend,\n                     wuffs_base__animation_disposal disposal,\n                     wuffs_base__color_u32_argb_premul background_color);\n  inline wuffs_base__rect_ie_u32 bounds() const;\n  inline uint32_t width() const;\n  inline uint32_t height() const;\n  inline wuffs_base__flicks duration() const;\n  inline uint64_t index()" +
+	" const;\n  inline uint64_t io_position() const;\n  inline wuffs_base__animation_blend blend() const;\n  inline wuffs_base__animation_disposal disposal() const;\n  inline wuffs_base__color_u32_argb_premul background_color() const;\n#endif  // __cplusplus\n\n} wuffs_base__frame_config;\n\nstatic inline wuffs_base__frame_config  //\nwuffs_base__null_frame_config() {\n  wuffs_base__frame_config ret;\n  ret.private_impl.bounds = wuffs_base__make_rect_ie_u32(0, 0, 0, 0);\n  ret.private_impl.duration = 0;\n  ret.private_impl.index = 0;\n  ret.private_impl.io_position = 0;\n  ret.private_impl.blend = 0;\n  ret.private_impl.disposal = 0;\n  return ret;\n}\n\nstatic inline void  //\nwuffs_base__frame_config__update(\n    wuffs_base__frame_config* c,\n    wuffs_base__rect_ie_u32 bounds,\n    wuffs_base__flicks duration,\n    uint64_t index,\n    uint64_t io_position,\n    wuffs_base__animation_blend blend,\n    wuffs_base__animation_disposal disposal,\n    wuffs_base__color_u32_argb_premul background_color) {\n  if (!c) {\n    return;\n  }\n\n  c->privat" +
+	"e_impl.bounds = bounds;\n  c->private_impl.duration = duration;\n  c->private_impl.index = index;\n  c->private_impl.io_position = io_position;\n  c->private_impl.blend = blend;\n  c->private_impl.disposal = disposal;\n  c->private_impl.background_color = background_color;\n}\n\nstatic inline wuffs_base__rect_ie_u32  //\nwuffs_base__frame_config__bounds(const wuffs_base__frame_config* c) {\n  if (c) {\n    return c->private_impl.bounds;\n  }\n\n  wuffs_base__rect_ie_u32 ret;\n  ret.min_incl_x = 0;\n  ret.min_incl_y = 0;\n  ret.max_excl_x = 0;\n  ret.max_excl_y = 0;\n  return ret;\n}\n\nstatic inline uint32_t  //\nwuffs_base__frame_config__width(const wuffs_base__frame_config* c) {\n  return c ? wuffs_base__rect_ie_u32__width(&c->private_impl.bounds) : 0;\n}\n\nstatic inline uint32_t  //\nwuffs_base__frame_config__height(const wuffs_base__frame_config* c) {\n  return c ? wuffs_base__rect_ie_u32__height(&c->private_impl.bounds) : 0;\n}\n\n// wuffs_base__frame_config__duration returns the amount of time to display\n// this frame. Zero means to d" +
+	"isplay forever - a still (non-animated) image.\nstatic inline wuffs_base__flicks  //\nwuffs_base__frame_config__duration(const wuffs_base__frame_config* c) {\n  return c ? c->private_impl.duration : 0;\n}\n\n// wuffs_base__frame_config__index returns the index of this frame. The first\n// frame in an image has index 0, the second frame has index 1, and so on.\nstatic inline uint64_t  //\nwuffs_base__frame_config__index(const wuffs_base__frame_config* c) {\n  return c ? c->private_impl.index : 0;\n}\n\n// wuffs_base__frame_config__io_position returns the I/O stream position before\n// the frame config.\nstatic inline uint64_t  //\nwuffs_base__frame_config__io_position(const wuffs_base__frame_config* c) {\n  return c ? c->private_impl.io_position : 0;\n}\n\n// wuffs_base__frame_config__blend returns, for an animated image, how to blend\n// the transparent pixels of this frame with the existing canvas.\nstatic inline wuffs_base__animation_blend  //\nwuffs_base__frame_config__blend(const wuffs_base__frame_config* c) {\n  return c ? c->p" +
+	"rivate_impl.blend : 0;\n}\n\n// wuffs_base__frame_config__disposal returns, for an animated image, how to\n// dispose of this frame after displaying it.\nstatic inline wuffs_base__animation_disposal  //\nwuffs_base__frame_config__disposal(const wuffs_base__frame_config* c) {\n  return c ? c->private_impl.disposal : 0;\n}\n\nstatic inline wuffs_base__color_u32_argb_premul  //\nwuffs_base__frame_config__background_color(const wuffs_base__frame_config* c) {\n  return c ? c->private_impl.background_color : 0;\n}\n\n#ifdef __cplusplus\n\ninline void  //\nwuffs_base__frame_config::update(\n    wuffs_base__rect_ie_u32 bounds,\n    wuffs_base__flicks duration,\n    uint64_t index,\n    uint64_t io_position,\n    wuffs_base__animation_blend blend,\n    wuffs_base__animation_disposal disposal,\n    wuffs_base__color_u32_argb_premul background_color) {\n  wuffs_base__frame_config__update(this, bounds, duration, index, io_position,\n                                   blend, disposal, background_color);\n}\n\ninline wuffs_base__rect_ie_u32  //\nwuffs_b" +
+	"ase__frame_config::bounds() const {\n  return wuffs_base__frame_config__bounds(this);\n}\n\ninline uint32_t  //\nwuffs_base__frame_config::width() const {\n  return wuffs_base__frame_config__width(this);\n}\n\ninline uint32_t  //\nwuffs_base__frame_config::height() const {\n  return wuffs_base__frame_config__height(this);\n}\n\ninline wuffs_base__flicks  //\nwuffs_base__frame_config::duration() const {\n  return wuffs_base__frame_config__duration(this);\n}\n\ninline uint64_t  //\nwuffs_base__frame_config::index() const {\n  return wuffs_base__frame_config__index(this);\n}\n\ninline uint64_t  //\nwuffs_base__frame_config::io_position() const {\n  return wuffs_base__frame_config__io_position(this);\n}\n\ninline wuffs_base__animation_blend  //\nwuffs_base__frame_config::blend() const {\n  return wuffs_base__frame_config__blend(this);\n}\n\ninline wuffs_base__animation_disposal  //\nwuffs_base__frame_config::disposal() const {\n  return wuffs_base__frame_config__disposal(this);\n}\n\ninline wuffs_base__color_u32_argb_premul  //\nwuffs_base__frame_confi" +
+	"g::background_color() const {\n  return wuffs_base__frame_config__background_color(this);\n}\n\n#endif  // __cplusplus\n\n" +
 	"" +
-	"// --------\n\ntypedef struct {\n  wuffs_base__pixel_config pixcfg;\n\n  // Do not access the private_impl's fields directly. There is no API/ABI\n  // compatibility or safety guarantee if you do so.\n  struct {\n    wuffs_base__table_u8 planes[WUFFS_BASE__PIXEL_FORMAT__NUM_PLANES_MAX];\n    // TODO: color spaces.\n  } private_impl;\n\n#ifdef __cplusplus\n  inline wuffs_base__status set_from_slice(wuffs_base__pixel_config* pixcfg,\n                                           wuffs_base__slice_u8 pixbuf_memory);\n  inline wuffs_base__slice_u8 palette();\n  inline wuffs_base__pixel_format pixel_format();\n  inline wuffs_base__table_u8 plane(uint32_t p);\n#endif  // __cplusplus\n\n} wuffs_base__pixel_buffer;\n\nstatic inline wuffs_base__pixel_buffer  //\nwuffs_base__null_pixel_buffer() {\n  wuffs_base__pixel_buffer ret;\n  ret.pixcfg = wuffs_base__null_pixel_config();\n  ret.private_impl.planes[0] = wuffs_base__null_table_u8();\n  ret.private_impl.planes[1] = wuffs_base__null_table_u8();\n  ret.private_impl.planes[2] = wuffs_base__null_tabl" +
-	"e_u8();\n  ret.private_impl.planes[3] = wuffs_base__null_table_u8();\n  return ret;\n}\n\nstatic inline wuffs_base__status  //\nwuffs_base__pixel_buffer__set_from_slice(wuffs_base__pixel_buffer* b,\n                                         wuffs_base__pixel_config* pixcfg,\n                                         wuffs_base__slice_u8 pixbuf_memory) {\n  if (!b) {\n    return wuffs_base__error__bad_receiver;\n  }\n  memset(b, 0, sizeof(*b));\n  if (!pixcfg) {\n    return wuffs_base__error__bad_argument;\n  }\n  if (wuffs_base__pixel_format__is_planar(pixcfg->private_impl.pixfmt)) {\n    // TODO: support planar pixel formats, concious of pixel subsampling.\n    return wuffs_base__error__bad_argument;\n  }\n  uint32_t bits_per_pixel =\n      wuffs_base__pixel_format__bits_per_pixel(pixcfg->private_impl.pixfmt);\n  if ((bits_per_pixel == 0) || ((bits_per_pixel % 8) != 0)) {\n    return wuffs_base__error__bad_argument;\n  }\n  uint64_t bytes_per_pixel = bits_per_pixel / 8;\n\n  uint8_t* ptr = pixbuf_memory.ptr;\n  uint64_t len = pixbuf_memo" +
-	"ry.len;\n  if (wuffs_base__pixel_format__is_indexed(pixcfg->private_impl.pixfmt)) {\n    // Split a 1024 byte chunk (256 palette entries × 4 bytes per entry) from\n    // the start of pixbuf_memory. We split from the start, not the end, so\n    // that the both chunks' pointers have the same alignment as the original\n    // pointer, up to an alignment of 1024.\n    if (len < 1024) {\n      return wuffs_base__error__bad_argument_length_too_short;\n    }\n    wuffs_base__table_u8* tab =\n        &b->private_impl.planes[WUFFS_BASE__PIXEL_FORMAT__INDEXED__COLOR_PLANE];\n    tab->ptr = ptr;\n    tab->width = 1024;\n    tab->height = 1;\n    tab->stride = 1024;\n    ptr += 1024;\n    len -= 1024;\n  }\n\n  uint64_t wh = ((uint64_t)pixcfg->private_impl.width) *\n                ((uint64_t)pixcfg->private_impl.height);\n  size_t width = (size_t)(pixcfg->private_impl.width);\n  if ((wh > (UINT64_MAX / bytes_per_pixel)) ||\n      (width > (SIZE_MAX / bytes_per_pixel))) {\n    return wuffs_base__error__bad_argument;\n  }\n  wh *= bytes_per_pix" +
-	"el;\n  width *= bytes_per_pixel;\n  if (wh > len) {\n    return wuffs_base__error__bad_argument_length_too_short;\n  }\n\n  b->pixcfg = *pixcfg;\n  wuffs_base__table_u8* tab = &b->private_impl.planes[0];\n  tab->ptr = ptr;\n  tab->width = width;\n  tab->height = pixcfg->private_impl.height;\n  tab->stride = width;\n  return NULL;\n}\n\n// wuffs_base__pixel_buffer__palette returns the palette color data. If\n// non-empty, it will have length 1024.\nstatic inline wuffs_base__slice_u8  //\nwuffs_base__pixel_buffer__palette(wuffs_base__pixel_buffer* b) {\n  if (b &&\n      wuffs_base__pixel_format__is_indexed(b->pixcfg.private_impl.pixfmt)) {\n    wuffs_base__table_u8* tab =\n        &b->private_impl.planes[WUFFS_BASE__PIXEL_FORMAT__INDEXED__COLOR_PLANE];\n    if ((tab->width == 1024) && (tab->height == 1)) {\n      return wuffs_base__make_slice_u8(tab->ptr, 1024);\n    }\n  }\n  return wuffs_base__make_slice_u8(NULL, 0);\n}\n\nstatic inline wuffs_base__pixel_format  //\nwuffs_base__pixel_buffer__pixel_format(wuffs_base__pixel_buffer* b) {\n  i" +
-	"f (b) {\n    return b->pixcfg.private_impl.pixfmt;\n  }\n  return WUFFS_BASE__PIXEL_FORMAT__INVALID;\n}\n\nstatic inline wuffs_base__table_u8  //\nwuffs_base__pixel_buffer__plane(wuffs_base__pixel_buffer* b, uint32_t p) {\n  if (b && (p < WUFFS_BASE__PIXEL_FORMAT__NUM_PLANES_MAX)) {\n    return b->private_impl.planes[p];\n  }\n\n  wuffs_base__table_u8 ret;\n  ret.ptr = NULL;\n  ret.width = 0;\n  ret.height = 0;\n  ret.stride = 0;\n  return ret;\n}\n\n#ifdef __cplusplus\n\ninline wuffs_base__status  //\nwuffs_base__pixel_buffer::set_from_slice(wuffs_base__pixel_config* pixcfg,\n                                         wuffs_base__slice_u8 pixbuf_memory) {\n  return wuffs_base__pixel_buffer__set_from_slice(this, pixcfg, pixbuf_memory);\n}\n\ninline wuffs_base__slice_u8  //\nwuffs_base__pixel_buffer::palette() {\n  return wuffs_base__pixel_buffer__palette(this);\n}\n\ninline wuffs_base__pixel_format  //\nwuffs_base__pixel_buffer::pixel_format() {\n  return wuffs_base__pixel_buffer__pixel_format(this);\n}\n\ninline wuffs_base__table_u8  //\nwuffs_base" +
-	"__pixel_buffer::plane(uint32_t p) {\n  return wuffs_base__pixel_buffer__plane(this, p);\n}\n\n#endif  // __cplusplus\n\n" +
+	"// --------\n\ntypedef struct {\n  wuffs_base__pixel_config pixcfg;\n\n  // Do not access the private_impl's fields directly. There is no API/ABI\n  // compatibility or safety guarantee if you do so.\n  struct {\n    wuffs_base__table_u8 planes[WUFFS_BASE__PIXEL_FORMAT__NUM_PLANES_MAX];\n    // TODO: color spaces.\n  } private_impl;\n\n#ifdef __cplusplus\n  inline wuffs_base__status set_from_slice(wuffs_base__pixel_config* pixcfg,\n                                           wuffs_base__slice_u8 pixbuf_memory);\n  inline wuffs_base__status set_from_table(wuffs_base__pixel_config* pixcfg,\n                                           wuffs_base__table_u8 pixbuf_memory);\n  inline wuffs_base__slice_u8 palette();\n  inline wuffs_base__pixel_format pixel_format() const;\n  inline wuffs_base__table_u8 plane(uint32_t p);\n#endif  // __cplusplus\n\n} wuffs_base__pixel_buffer;\n\nstatic inline wuffs_base__pixel_buffer  //\nwuffs_base__null_pixel_buffer() {\n  wuffs_base__pixel_buffer ret;\n  ret.pixcfg = wuffs_base__null_pixel_config();\n  ret.pri" +
+	"vate_impl.planes[0] = wuffs_base__null_table_u8();\n  ret.private_impl.planes[1] = wuffs_base__null_table_u8();\n  ret.private_impl.planes[2] = wuffs_base__null_table_u8();\n  ret.private_impl.planes[3] = wuffs_base__null_table_u8();\n  return ret;\n}\n\nstatic inline wuffs_base__status  //\nwuffs_base__pixel_buffer__set_from_slice(wuffs_base__pixel_buffer* b,\n                                         wuffs_base__pixel_config* pixcfg,\n                                         wuffs_base__slice_u8 pixbuf_memory) {\n  if (!b) {\n    return wuffs_base__error__bad_receiver;\n  }\n  memset(b, 0, sizeof(*b));\n  if (!pixcfg) {\n    return wuffs_base__error__bad_argument;\n  }\n  if (wuffs_base__pixel_format__is_planar(pixcfg->private_impl.pixfmt)) {\n    // TODO: support planar pixel formats, concious of pixel subsampling.\n    return wuffs_base__error__unsupported_option;\n  }\n  uint32_t bits_per_pixel =\n      wuffs_base__pixel_format__bits_per_pixel(pixcfg->private_impl.pixfmt);\n  if ((bits_per_pixel == 0) || ((bits_per_pixel % 8) !=" +
+	" 0)) {\n    // TODO: support fraction-of-byte pixels, e.g. 1 bit per pixel?\n    return wuffs_base__error__unsupported_option;\n  }\n  uint64_t bytes_per_pixel = bits_per_pixel / 8;\n\n  uint8_t* ptr = pixbuf_memory.ptr;\n  uint64_t len = pixbuf_memory.len;\n  if (wuffs_base__pixel_format__is_indexed(pixcfg->private_impl.pixfmt)) {\n    // Split a 1024 byte chunk (256 palette entries × 4 bytes per entry) from\n    // the start of pixbuf_memory. We split from the start, not the end, so\n    // that the both chunks' pointers have the same alignment as the original\n    // pointer, up to an alignment of 1024.\n    if (len < 1024) {\n      return wuffs_base__error__bad_argument_length_too_short;\n    }\n    wuffs_base__table_u8* tab =\n        &b->private_impl.planes[WUFFS_BASE__PIXEL_FORMAT__INDEXED__COLOR_PLANE];\n    tab->ptr = ptr;\n    tab->width = 1024;\n    tab->height = 1;\n    tab->stride = 1024;\n    ptr += 1024;\n    len -= 1024;\n  }\n\n  uint64_t wh = ((uint64_t)pixcfg->private_impl.width) *\n                ((uint64_t)pixcfg" +
+	"->private_impl.height);\n  size_t width = (size_t)(pixcfg->private_impl.width);\n  if ((wh > (UINT64_MAX / bytes_per_pixel)) ||\n      (width > (SIZE_MAX / bytes_per_pixel))) {\n    return wuffs_base__error__bad_argument;\n  }\n  wh *= bytes_per_pixel;\n  width *= bytes_per_pixel;\n  if (wh > len) {\n    return wuffs_base__error__bad_argument_length_too_short;\n  }\n\n  b->pixcfg = *pixcfg;\n  wuffs_base__table_u8* tab = &b->private_impl.planes[0];\n  tab->ptr = ptr;\n  tab->width = width;\n  tab->height = pixcfg->private_impl.height;\n  tab->stride = width;\n  return NULL;\n}\n\nstatic inline wuffs_base__status  //\nwuffs_base__pixel_buffer__set_from_table(wuffs_base__pixel_buffer* b,\n                                         wuffs_base__pixel_config* pixcfg,\n                                         wuffs_base__table_u8 pixbuf_memory) {\n  if (!b) {\n    return wuffs_base__error__bad_receiver;\n  }\n  memset(b, 0, sizeof(*b));\n  if (!pixcfg ||\n      wuffs_base__pixel_format__is_planar(pixcfg->private_impl.pixfmt)) {\n    return wuffs_b" +
+	"ase__error__bad_argument;\n  }\n  uint32_t bits_per_pixel =\n      wuffs_base__pixel_format__bits_per_pixel(pixcfg->private_impl.pixfmt);\n  if ((bits_per_pixel == 0) || ((bits_per_pixel % 8) != 0)) {\n    // TODO: support fraction-of-byte pixels, e.g. 1 bit per pixel?\n    return wuffs_base__error__unsupported_option;\n  }\n  uint64_t bytes_per_pixel = bits_per_pixel / 8;\n\n  uint64_t width_in_bytes =\n      ((uint64_t)pixcfg->private_impl.width) * bytes_per_pixel;\n  if ((width_in_bytes > pixbuf_memory.width) ||\n      (pixcfg->private_impl.height > pixbuf_memory.height)) {\n    return wuffs_base__error__bad_argument;\n  }\n\n  b->pixcfg = *pixcfg;\n  b->private_impl.planes[0] = pixbuf_memory;\n  return NULL;\n}\n\n// wuffs_base__pixel_buffer__palette returns the palette color data. If\n// non-empty, it will have length 1024.\nstatic inline wuffs_base__slice_u8  //\nwuffs_base__pixel_buffer__palette(wuffs_base__pixel_buffer* b) {\n  if (b &&\n      wuffs_base__pixel_format__is_indexed(b->pixcfg.private_impl.pixfmt)) {\n    wuffs_base" +
+	"__table_u8* tab =\n        &b->private_impl.planes[WUFFS_BASE__PIXEL_FORMAT__INDEXED__COLOR_PLANE];\n    if ((tab->width == 1024) && (tab->height == 1)) {\n      return wuffs_base__make_slice_u8(tab->ptr, 1024);\n    }\n  }\n  return wuffs_base__make_slice_u8(NULL, 0);\n}\n\nstatic inline wuffs_base__pixel_format  //\nwuffs_base__pixel_buffer__pixel_format(const wuffs_base__pixel_buffer* b) {\n  if (b) {\n    return b->pixcfg.private_impl.pixfmt;\n  }\n  return WUFFS_BASE__PIXEL_FORMAT__INVALID;\n}\n\nstatic inline wuffs_base__table_u8  //\nwuffs_base__pixel_buffer__plane(wuffs_base__pixel_buffer* b, uint32_t p) {\n  if (b && (p < WUFFS_BASE__PIXEL_FORMAT__NUM_PLANES_MAX)) {\n    return b->private_impl.planes[p];\n  }\n\n  wuffs_base__table_u8 ret;\n  ret.ptr = NULL;\n  ret.width = 0;\n  ret.height = 0;\n  ret.stride = 0;\n  return ret;\n}\n\n#ifdef __cplusplus\n\ninline wuffs_base__status  //\nwuffs_base__pixel_buffer::set_from_slice(wuffs_base__pixel_config* pixcfg,\n                                         wuffs_base__slice_u8 pixbuf_memory" +
+	") {\n  return wuffs_base__pixel_buffer__set_from_slice(this, pixcfg, pixbuf_memory);\n}\n\ninline wuffs_base__status  //\nwuffs_base__pixel_buffer::set_from_table(wuffs_base__pixel_config* pixcfg,\n                                         wuffs_base__table_u8 pixbuf_memory) {\n  return wuffs_base__pixel_buffer__set_from_table(this, pixcfg, pixbuf_memory);\n}\n\ninline wuffs_base__slice_u8  //\nwuffs_base__pixel_buffer::palette() {\n  return wuffs_base__pixel_buffer__palette(this);\n}\n\ninline wuffs_base__pixel_format  //\nwuffs_base__pixel_buffer::pixel_format() const {\n  return wuffs_base__pixel_buffer__pixel_format(this);\n}\n\ninline wuffs_base__table_u8  //\nwuffs_base__pixel_buffer::plane(uint32_t p) {\n  return wuffs_base__pixel_buffer__plane(this, p);\n}\n\n#endif  // __cplusplus\n\n" +
 	"" +
 	"// --------\n\ntypedef struct {\n  // Do not access the private_impl's fields directly. There is no API/ABI\n  // compatibility or safety guarantee if you do so.\n  struct {\n    uint8_t TODO;\n  } private_impl;\n\n#ifdef __cplusplus\n#endif  // __cplusplus\n\n} wuffs_base__decode_frame_options;\n\n#ifdef __cplusplus\n\n#endif  // __cplusplus\n\n" +
 	"" +
-	"// --------\n\ntypedef struct {\n  // Do not access the private_impl's fields directly. There is no API/ABI\n  // compatibility or safety guarantee if you do so.\n  struct {\n    // TODO: should the func type take restrict pointers?\n    uint64_t (*func)(wuffs_base__slice_u8 dst,\n                     wuffs_base__slice_u8 dst_palette,\n                     wuffs_base__slice_u8 src);\n  } private_impl;\n\n#ifdef __cplusplus\n  inline void prepare(wuffs_base__pixel_format dst_format,\n                      wuffs_base__slice_u8 dst_palette,\n                      wuffs_base__pixel_format src_format,\n                      wuffs_base__slice_u8 src_palette);\n  inline uint64_t swizzle_packed(wuffs_base__slice_u8 dst,\n                                 wuffs_base__slice_u8 dst_palette,\n                                 wuffs_base__slice_u8 src);\n#endif  // __cplusplus\n\n} wuffs_base__pixel_swizzler;\n\n// TODO: should prepare (both the C and C++ methods) return a status?\n\nvoid  //\nwuffs_base__pixel_swizzler__prepare(wuffs_base__pixel_swi" +
-	"zzler* p,\n                                    wuffs_base__pixel_format dst_format,\n                                    wuffs_base__slice_u8 dst_palette,\n                                    wuffs_base__pixel_format src_format,\n                                    wuffs_base__slice_u8 src_palette);\n\nuint64_t  //\nwuffs_base__pixel_swizzler__swizzle_packed(wuffs_base__pixel_swizzler* p,\n                                           wuffs_base__slice_u8 dst,\n                                           wuffs_base__slice_u8 dst_palette,\n                                           wuffs_base__slice_u8 src);\n\n#ifdef __cplusplus\n\ninline void  //\nwuffs_base__pixel_swizzler::prepare(wuffs_base__pixel_format dst_format,\n                                    wuffs_base__slice_u8 dst_palette,\n                                    wuffs_base__pixel_format src_format,\n                                    wuffs_base__slice_u8 src_palette) {\n  wuffs_base__pixel_swizzler__prepare(this, dst_format, dst_palette, src_format,\n                 " +
-	"                     src_palette);\n}\n\nuint64_t  //\nwuffs_base__pixel_swizzler::swizzle_packed(wuffs_base__slice_u8 dst,\n                                           wuffs_base__slice_u8 dst_palette,\n                                           wuffs_base__slice_u8 src) {\n  return wuffs_base__pixel_swizzler__swizzle_packed(this, dst, dst_palette,\n                                                    src);\n}\n\n#endif  // __cplusplus\n" +
+	"// --------\n\ntypedef struct {\n  // Do not access the private_impl's fields directly. There is no API/ABI\n  // compatibility or safety guarantee if you do so.\n  struct {\n    // TODO: should the func type take restrict pointers?\n    uint64_t (*func)(wuffs_base__slice_u8 dst,\n                     wuffs_base__slice_u8 dst_palette,\n                     wuffs_base__slice_u8 src);\n  } private_impl;\n\n#ifdef __cplusplus\n  inline wuffs_base__status prepare(wuffs_base__pixel_format dst_format,\n                                    wuffs_base__slice_u8 dst_palette,\n                                    wuffs_base__pixel_format src_format,\n                                    wuffs_base__slice_u8 src_palette);\n  inline uint64_t swizzle_interleaved(wuffs_base__slice_u8 dst,\n                                      wuffs_base__slice_u8 dst_palette,\n                                      wuffs_base__slice_u8 src) const;\n#endif  // __cplusplus\n\n} wuffs_base__pixel_swizzler;\n\nwuffs_base__status  //\nwuffs_base__pixel_swizzler__prepare(w" +
+	"uffs_base__pixel_swizzler* p,\n                                    wuffs_base__pixel_format dst_format,\n                                    wuffs_base__slice_u8 dst_palette,\n                                    wuffs_base__pixel_format src_format,\n                                    wuffs_base__slice_u8 src_palette);\n\nuint64_t  //\nwuffs_base__pixel_swizzler__swizzle_interleaved(\n    const wuffs_base__pixel_swizzler* p,\n    wuffs_base__slice_u8 dst,\n    wuffs_base__slice_u8 dst_palette,\n    wuffs_base__slice_u8 src);\n\n#ifdef __cplusplus\n\ninline wuffs_base__status  //\nwuffs_base__pixel_swizzler::prepare(wuffs_base__pixel_format dst_format,\n                                    wuffs_base__slice_u8 dst_palette,\n                                    wuffs_base__pixel_format src_format,\n                                    wuffs_base__slice_u8 src_palette) {\n  return wuffs_base__pixel_swizzler__prepare(this, dst_format, dst_palette,\n                                             src_format, src_palette);\n}\n\nuint64_t  //\nwu" +
+	"ffs_base__pixel_swizzler::swizzle_interleaved(\n    wuffs_base__slice_u8 dst,\n    wuffs_base__slice_u8 dst_palette,\n    wuffs_base__slice_u8 src) const {\n  return wuffs_base__pixel_swizzler__swizzle_interleaved(this, dst, dst_palette,\n                                                         src);\n}\n\n#endif  // __cplusplus\n" +
 	""
 
 const baseIOPrivateH = "" +
-	"// ---------------- I/O\n\nstatic inline bool  //\nwuffs_base__io_buffer__is_valid(wuffs_base__io_buffer buf) {\n  return (buf.data.ptr || (buf.data.len == 0)) &&\n         (buf.data.len >= buf.meta.wi) && (buf.meta.wi >= buf.meta.ri);\n}\n\n// TODO: wuffs_base__io_reader__is_eof is no longer used by Wuffs per se, but\n// it might be handy to programs that use Wuffs. Either delete it, or promote\n// it to the public API.\n//\n// If making this function public (i.e. moving it to base-header.h), it also\n// needs to allow NULL (i.e. implicit, callee-calculated) mark/limit.\n\nstatic inline bool  //\nwuffs_base__io_reader__is_eof(wuffs_base__io_reader o) {\n  wuffs_base__io_buffer* buf = o.private_impl.buf;\n  return buf && buf->meta.closed &&\n         (buf->data.ptr + buf->meta.wi == o.private_impl.limit);\n}\n\nstatic inline bool  //\nwuffs_base__io_reader__is_valid(wuffs_base__io_reader o) {\n  wuffs_base__io_buffer* buf = o.private_impl.buf;\n  // Note: if making this function public (i.e. moving it to base-header.h), it\n  // also " +
-	"needs to allow NULL (i.e. implicit, callee-calculated) mark/limit.\n  return buf ? ((buf->data.ptr <= o.private_impl.mark) &&\n                (o.private_impl.mark <= o.private_impl.limit) &&\n                (o.private_impl.limit <= buf->data.ptr + buf->data.len))\n             : ((o.private_impl.mark == NULL) &&\n                (o.private_impl.limit == NULL));\n}\n\nstatic inline bool  //\nwuffs_base__io_writer__is_valid(wuffs_base__io_writer o) {\n  wuffs_base__io_buffer* buf = o.private_impl.buf;\n  // Note: if making this function public (i.e. moving it to base-header.h), it\n  // also needs to allow NULL (i.e. implicit, callee-calculated) mark/limit.\n  return buf ? ((buf->data.ptr <= o.private_impl.mark) &&\n                (o.private_impl.mark <= o.private_impl.limit) &&\n                (o.private_impl.limit <= buf->data.ptr + buf->data.len))\n             : ((o.private_impl.mark == NULL) &&\n                (o.private_impl.limit == NULL));\n}\n\nstatic inline uint32_t  //\nwuffs_base__io_writer__copy_n_from_history(uin" +
-	"t8_t** ptr_iop_w,\n                                           uint8_t* io0_w,\n                                           uint8_t* io1_w,\n                                           uint32_t length,\n                                           uint32_t distance) {\n  if (!distance) {\n    return 0;\n  }\n  uint8_t* p = *ptr_iop_w;\n  if ((size_t)(p - io0_w) < (size_t)(distance)) {\n    return 0;\n  }\n  uint8_t* q = p - distance;\n  size_t n = (size_t)(io1_w - p);\n  if ((size_t)(length) > n) {\n    length = (uint32_t)(n);\n  } else {\n    n = (size_t)(length);\n  }\n  // TODO: unrolling by 3 seems best for the std/deflate benchmarks, but that\n  // is mostly because 3 is the minimum length for the deflate format. This\n  // function implementation shouldn't overfit to that one format. Perhaps the\n  // copy_n_from_history Wuffs method should also take an unroll hint argument,\n  // and the cgen can look if that argument is the constant expression '3'.\n  //\n  // See also wuffs_base__io_writer__copy_n_from_history_fast below.\n  //\n  " +
-	"// Alternatively, or additionally, have a sloppy_copy_n_from_history method\n  // that copies 8 bytes at a time, possibly writing more than length bytes?\n  for (; n >= 3; n -= 3) {\n    *p++ = *q++;\n    *p++ = *q++;\n    *p++ = *q++;\n  }\n  for (; n; n--) {\n    *p++ = *q++;\n  }\n  *ptr_iop_w = p;\n  return length;\n}\n\n// wuffs_base__io_writer__copy_n_from_history_fast is like the\n// wuffs_base__io_writer__copy_n_from_history function above, but has stronger\n// pre-conditions. The caller needs to prove that:\n//  - distance >  0\n//  - distance <= (*ptr_iop_w - io0_w)\n//  - length   <= (io1_w      - *ptr_iop_w)\nstatic inline uint32_t  //\nwuffs_base__io_writer__copy_n_from_history_fast(uint8_t** ptr_iop_w,\n                                                uint8_t* io0_w,\n                                                uint8_t* io1_w,\n                                                uint32_t length,\n                                                uint32_t distance) {\n  uint8_t* p = *ptr_iop_w;\n  uint8_t* q = p - distance;\n " +
-	" uint32_t n = length;\n  for (; n >= 3; n -= 3) {\n    *p++ = *q++;\n    *p++ = *q++;\n    *p++ = *q++;\n  }\n  for (; n; n--) {\n    *p++ = *q++;\n  }\n  *ptr_iop_w = p;\n  return length;\n}\n\nstatic inline uint32_t  //\nwuffs_base__io_writer__copy_n_from_reader(uint8_t** ptr_iop_w,\n                                          uint8_t* io1_w,\n                                          uint32_t length,\n                                          uint8_t** ptr_iop_r,\n                                          uint8_t* io1_r) {\n  uint8_t* iop_w = *ptr_iop_w;\n  size_t n = length;\n  if (n > ((size_t)(io1_w - iop_w))) {\n    n = (size_t)(io1_w - iop_w);\n  }\n  uint8_t* iop_r = *ptr_iop_r;\n  if (n > ((size_t)(io1_r - iop_r))) {\n    n = (size_t)(io1_r - iop_r);\n  }\n  if (n > 0) {\n    memmove(iop_w, iop_r, n);\n    *ptr_iop_w += n;\n    *ptr_iop_r += n;\n  }\n  return (uint32_t)(n);\n}\n\nstatic inline uint64_t  //\nwuffs_base__io_writer__copy_from_slice(uint8_t** ptr_iop_w,\n                                       uint8_t* io1_w,\n                 " +
-	"                      wuffs_base__slice_u8 src) {\n  uint8_t* iop_w = *ptr_iop_w;\n  size_t n = src.len;\n  if (n > ((size_t)(io1_w - iop_w))) {\n    n = (size_t)(io1_w - iop_w);\n  }\n  if (n > 0) {\n    memmove(iop_w, src.ptr, n);\n    *ptr_iop_w += n;\n  }\n  return (uint64_t)(n);\n}\n\nstatic inline uint32_t  //\nwuffs_base__io_writer__copy_n_from_slice(uint8_t** ptr_iop_w,\n                                         uint8_t* io1_w,\n                                         uint32_t length,\n                                         wuffs_base__slice_u8 src) {\n  uint8_t* iop_w = *ptr_iop_w;\n  size_t n = src.len;\n  if (n > length) {\n    n = length;\n  }\n  if (n > ((size_t)(io1_w - iop_w))) {\n    n = (size_t)(io1_w - iop_w);\n  }\n  if (n > 0) {\n    memmove(iop_w, src.ptr, n);\n    *ptr_iop_w += n;\n  }\n  return (uint32_t)(n);\n}\n\nstatic inline wuffs_base__empty_struct  //\nwuffs_base__io_reader__set(wuffs_base__io_reader* o,\n                           wuffs_base__io_buffer* b,\n                           uint8_t** ptr_iop_r,\n        " +
-	"                   uint8_t** ptr_io1_r,\n                           wuffs_base__slice_u8 data) {\n  b->data = data;\n  b->meta.wi = data.len;\n  b->meta.ri = 0;\n  b->meta.pos = 0;\n  b->meta.closed = false;\n\n  o->private_impl.buf = b;\n  o->private_impl.mark = data.ptr;\n  o->private_impl.limit = data.ptr + data.len;\n  *ptr_iop_r = data.ptr;\n  *ptr_io1_r = data.ptr + data.len;\n\n  wuffs_base__empty_struct ret;\n  ret.private_impl = 0;\n  return ret;\n}\n\nstatic inline wuffs_base__empty_struct  //\nwuffs_base__io_reader__set_limit(wuffs_base__io_reader* o,\n                                 uint8_t* iop_r,\n                                 uint64_t limit) {\n  if (o && (((size_t)(o->private_impl.limit - iop_r)) > limit)) {\n    o->private_impl.limit = iop_r + limit;\n  }\n\n  wuffs_base__empty_struct ret;\n  ret.private_impl = 0;\n  return ret;\n}\n\nstatic inline wuffs_base__empty_struct  //\nwuffs_base__io_reader__set_mark(wuffs_base__io_reader* o, uint8_t* mark) {\n  o->private_impl.mark = mark;\n\n  wuffs_base__empty_struct ret;\n  ret." +
-	"private_impl = 0;\n  return ret;\n}\n\nstatic inline wuffs_base__slice_u8  //\nwuffs_base__io_reader__take(uint8_t** ptr_iop_r, uint8_t* io1_r, uint64_t n) {\n  if (n <= ((size_t)(io1_r - *ptr_iop_r))) {\n    uint8_t* p = *ptr_iop_r;\n    *ptr_iop_r += n;\n    return wuffs_base__make_slice_u8(p, n);\n  }\n  return wuffs_base__make_slice_u8(NULL, 0);\n}\n\nstatic inline wuffs_base__empty_struct  //\nwuffs_base__io_writer__set(wuffs_base__io_writer* o,\n                           wuffs_base__io_buffer* b,\n                           uint8_t** ptr_iop_w,\n                           uint8_t** ptr_io1_w,\n                           wuffs_base__slice_u8 data) {\n  b->data = data;\n  b->meta.wi = 0;\n  b->meta.ri = 0;\n  b->meta.pos = 0;\n  b->meta.closed = false;\n\n  o->private_impl.buf = b;\n  o->private_impl.mark = data.ptr;\n  o->private_impl.limit = data.ptr + data.len;\n  *ptr_iop_w = data.ptr;\n  *ptr_io1_w = data.ptr + data.len;\n\n  wuffs_base__empty_struct ret;\n  ret.private_impl = 0;\n  return ret;\n}\n\nstatic inline wuffs_base__empty_str" +
-	"uct  //\nwuffs_base__io_writer__set_mark(wuffs_base__io_writer* o, uint8_t* mark) {\n  o->private_impl.mark = mark;\n\n  wuffs_base__empty_struct ret;\n  ret.private_impl = 0;\n  return ret;\n}\n\n" +
+	"// ---------------- I/O\n\n// \"Null\" as in \"/dev/null\", not as in \"nullptr\".\n//\n// TODO: ensure that this is zero-initialized.\nstatic wuffs_base__io_buffer wuffs_base__global__null_io_buffer;\n\nstatic inline wuffs_base__io_buffer*  //\nwuffs_base__null_io_reader() {\n  return &wuffs_base__global__null_io_buffer;\n}\n\nstatic inline wuffs_base__io_buffer*  //\nwuffs_base__null_io_writer() {\n  return &wuffs_base__global__null_io_buffer;\n}\n\nstatic inline uint64_t  //\nwuffs_base__io__count_since(uint64_t mark, uint64_t index) {\n  if (index >= mark) {\n    return index - mark;\n  }\n  return 0;\n}\n\nstatic inline wuffs_base__slice_u8  //\nwuffs_base__io__since(uint64_t mark, uint64_t index, uint8_t* ptr) {\n  if (index >= mark) {\n    return wuffs_base__make_slice_u8(ptr + mark, index - mark);\n  }\n  return wuffs_base__make_slice_u8(NULL, 0);\n}\n\nstatic inline uint32_t  //\nwuffs_base__io_writer__copy_n_from_history(uint8_t** ptr_iop_w,\n                                           uint8_t* io1_w,\n                                       " +
+	"    uint8_t* io2_w,\n                                           uint32_t length,\n                                           uint32_t distance) {\n  if (!distance) {\n    return 0;\n  }\n  uint8_t* p = *ptr_iop_w;\n  if ((size_t)(p - io1_w) < (size_t)(distance)) {\n    return 0;\n  }\n  uint8_t* q = p - distance;\n  size_t n = (size_t)(io2_w - p);\n  if ((size_t)(length) > n) {\n    length = (uint32_t)(n);\n  } else {\n    n = (size_t)(length);\n  }\n  // TODO: unrolling by 3 seems best for the std/deflate benchmarks, but that\n  // is mostly because 3 is the minimum length for the deflate format. This\n  // function implementation shouldn't overfit to that one format. Perhaps the\n  // copy_n_from_history Wuffs method should also take an unroll hint argument,\n  // and the cgen can look if that argument is the constant expression '3'.\n  //\n  // See also wuffs_base__io_writer__copy_n_from_history_fast below.\n  //\n  // Alternatively, or additionally, have a sloppy_copy_n_from_history method\n  // that copies 8 bytes at a time, poss" +
+	"ibly writing more than length bytes?\n  for (; n >= 3; n -= 3) {\n    *p++ = *q++;\n    *p++ = *q++;\n    *p++ = *q++;\n  }\n  for (; n; n--) {\n    *p++ = *q++;\n  }\n  *ptr_iop_w = p;\n  return length;\n}\n\n// wuffs_base__io_writer__copy_n_from_history_fast is like the\n// wuffs_base__io_writer__copy_n_from_history function above, but has stronger\n// pre-conditions. The caller needs to prove that:\n//  - distance >  0\n//  - distance <= (*ptr_iop_w - io1_w)\n//  - length   <= (io2_w      - *ptr_iop_w)\nstatic inline uint32_t  //\nwuffs_base__io_writer__copy_n_from_history_fast(uint8_t** ptr_iop_w,\n                                                uint8_t* io1_w,\n                                                uint8_t* io2_w,\n                                                uint32_t length,\n                                                uint32_t distance) {\n  uint8_t* p = *ptr_iop_w;\n  uint8_t* q = p - distance;\n  uint32_t n = length;\n  for (; n >= 3; n -= 3) {\n    *p++ = *q++;\n    *p++ = *q++;\n    *p++ = *q++;\n  }\n  for (; n; " +
+	"n--) {\n    *p++ = *q++;\n  }\n  *ptr_iop_w = p;\n  return length;\n}\n\nstatic inline uint32_t  //\nwuffs_base__io_writer__copy_n_from_reader(uint8_t** ptr_iop_w,\n                                          uint8_t* io2_w,\n                                          uint32_t length,\n                                          uint8_t** ptr_iop_r,\n                                          uint8_t* io2_r) {\n  uint8_t* iop_w = *ptr_iop_w;\n  size_t n = length;\n  if (n > ((size_t)(io2_w - iop_w))) {\n    n = (size_t)(io2_w - iop_w);\n  }\n  uint8_t* iop_r = *ptr_iop_r;\n  if (n > ((size_t)(io2_r - iop_r))) {\n    n = (size_t)(io2_r - iop_r);\n  }\n  if (n > 0) {\n    memmove(iop_w, iop_r, n);\n    *ptr_iop_w += n;\n    *ptr_iop_r += n;\n  }\n  return (uint32_t)(n);\n}\n\nstatic inline uint64_t  //\nwuffs_base__io_writer__copy_from_slice(uint8_t** ptr_iop_w,\n                                       uint8_t* io2_w,\n                                       wuffs_base__slice_u8 src) {\n  uint8_t* iop_w = *ptr_iop_w;\n  size_t n = src.len;\n  if (n > ((s" +
+	"ize_t)(io2_w - iop_w))) {\n    n = (size_t)(io2_w - iop_w);\n  }\n  if (n > 0) {\n    memmove(iop_w, src.ptr, n);\n    *ptr_iop_w += n;\n  }\n  return (uint64_t)(n);\n}\n\nstatic inline uint32_t  //\nwuffs_base__io_writer__copy_n_from_slice(uint8_t** ptr_iop_w,\n                                         uint8_t* io2_w,\n                                         uint32_t length,\n                                         wuffs_base__slice_u8 src) {\n  uint8_t* iop_w = *ptr_iop_w;\n  size_t n = src.len;\n  if (n > length) {\n    n = length;\n  }\n  if (n > ((size_t)(io2_w - iop_w))) {\n    n = (size_t)(io2_w - iop_w);\n  }\n  if (n > 0) {\n    memmove(iop_w, src.ptr, n);\n    *ptr_iop_w += n;\n  }\n  return (uint32_t)(n);\n}\n\nstatic inline wuffs_base__io_buffer*  //\nwuffs_base__io_reader__set(wuffs_base__io_buffer* b,\n                           uint8_t** ptr_iop_r,\n                           uint8_t** ptr_io0_r,\n                           uint8_t** ptr_io1_r,\n                           uint8_t** ptr_io2_r,\n                           wuffs_ba" +
+	"se__slice_u8 data) {\n  b->data = data;\n  b->meta.wi = data.len;\n  b->meta.ri = 0;\n  b->meta.pos = 0;\n  b->meta.closed = false;\n\n  *ptr_iop_r = data.ptr;\n  *ptr_io0_r = data.ptr;\n  *ptr_io1_r = data.ptr;\n  *ptr_io2_r = data.ptr + data.len;\n\n  return b;\n}\n\nstatic inline wuffs_base__slice_u8  //\nwuffs_base__io_reader__take(uint8_t** ptr_iop_r, uint8_t* io2_r, uint64_t n) {\n  if (n <= ((size_t)(io2_r - *ptr_iop_r))) {\n    uint8_t* p = *ptr_iop_r;\n    *ptr_iop_r += n;\n    return wuffs_base__make_slice_u8(p, n);\n  }\n  return wuffs_base__make_slice_u8(NULL, 0);\n}\n\nstatic inline wuffs_base__io_buffer*  //\nwuffs_base__io_writer__set(wuffs_base__io_buffer* b,\n                           uint8_t** ptr_iop_w,\n                           uint8_t** ptr_io0_w,\n                           uint8_t** ptr_io1_w,\n                           uint8_t** ptr_io2_w,\n                           wuffs_base__slice_u8 data) {\n  b->data = data;\n  b->meta.wi = 0;\n  b->meta.ri = 0;\n  b->meta.pos = 0;\n  b->meta.closed = false;\n\n  *ptr_iop_w = dat" +
+	"a.ptr;\n  *ptr_io0_w = data.ptr;\n  *ptr_io1_w = data.ptr;\n  *ptr_io2_w = data.ptr + data.len;\n\n  return b;\n}\n\n" +
 	"" +
 	"// ---------------- I/O (Utility)\n\n#define wuffs_base__utility__null_io_reader wuffs_base__null_io_reader\n#define wuffs_base__utility__null_io_writer wuffs_base__null_io_writer\n" +
 	""
 
 const baseIOPublicH = "" +
-	"// ---------------- I/O\n\nstruct wuffs_base__io_buffer__struct;\n\ntypedef struct {\n  // Do not access the private_impl's fields directly. There is no API/ABI\n  // compatibility or safety guarantee if you do so.\n  struct {\n    struct wuffs_base__io_buffer__struct* buf;\n    // The bounds values are typically NULL, when created by the Wuffs public\n    // API. NULL means that the callee substitutes the implicit bounds derived\n    // from buf.\n    uint8_t* mark;\n    uint8_t* limit;\n  } private_impl;\n} wuffs_base__io_reader;\n\ntypedef struct {\n  // Do not access the private_impl's fields directly. There is no API/ABI\n  // compatibility or safety guarantee if you do so.\n  struct {\n    struct wuffs_base__io_buffer__struct* buf;\n    // The bounds values are typically NULL, when created by the Wuffs public\n    // API. NULL means that the callee substitutes the implicit bounds derived\n    // from buf.\n    uint8_t* mark;\n    uint8_t* limit;\n  } private_impl;\n} wuffs_base__io_writer;\n\n// wuffs_base__io_buffer_meta is the met" +
-	"adata for a wuffs_base__io_buffer's\n// data.\ntypedef struct {\n  size_t wi;     // Write index. Invariant: wi <= len.\n  size_t ri;     // Read  index. Invariant: ri <= wi.\n  uint64_t pos;  // Position of the buffer start relative to the stream start.\n  bool closed;   // No further writes are expected.\n} wuffs_base__io_buffer_meta;\n\n// wuffs_base__io_buffer is a 1-dimensional buffer (a pointer and length) plus\n// additional metadata.\n//\n// A value with all fields zero is a valid, empty buffer.\ntypedef struct wuffs_base__io_buffer__struct {\n  wuffs_base__slice_u8 data;\n  wuffs_base__io_buffer_meta meta;\n\n#ifdef __cplusplus\n  inline void compact();\n  inline wuffs_base__io_reader reader();\n  inline wuffs_base__io_writer writer();\n  inline uint64_t reader_io_position();\n  inline uint64_t writer_io_position();\n#endif  // __cplusplus\n\n} wuffs_base__io_buffer;\n\nstatic inline wuffs_base__io_buffer  //\nwuffs_base__make_io_buffer(wuffs_base__slice_u8 data,\n                           wuffs_base__io_buffer_meta meta) {\n  w" +
-	"uffs_base__io_buffer ret;\n  ret.data = data;\n  ret.meta = meta;\n  return ret;\n}\n\nstatic inline wuffs_base__io_buffer_meta  //\nwuffs_base__make_io_buffer_meta(size_t wi,\n                                size_t ri,\n                                uint64_t pos,\n                                bool closed) {\n  wuffs_base__io_buffer_meta ret;\n  ret.wi = wi;\n  ret.ri = ri;\n  ret.pos = pos;\n  ret.closed = closed;\n  return ret;\n}\n\nstatic inline wuffs_base__io_buffer  //\nwuffs_base__null_io_buffer() {\n  wuffs_base__io_buffer ret;\n  ret.data.ptr = NULL;\n  ret.data.len = 0;\n  ret.meta.wi = 0;\n  ret.meta.ri = 0;\n  ret.meta.pos = 0;\n  ret.meta.closed = false;\n  return ret;\n}\n\nstatic inline wuffs_base__io_buffer_meta  //\nwuffs_base__null_io_buffer_meta() {\n  wuffs_base__io_buffer_meta ret;\n  ret.wi = 0;\n  ret.ri = 0;\n  ret.pos = 0;\n  ret.closed = false;\n  return ret;\n}\n\nstatic inline wuffs_base__io_reader  //\nwuffs_base__null_io_reader() {\n  wuffs_base__io_reader ret;\n  ret.private_impl.buf = NULL;\n  ret.private_impl.mark =" +
-	" NULL;\n  ret.private_impl.limit = NULL;\n  return ret;\n}\n\nstatic inline wuffs_base__io_writer  //\nwuffs_base__null_io_writer() {\n  wuffs_base__io_writer ret;\n  ret.private_impl.buf = NULL;\n  ret.private_impl.mark = NULL;\n  ret.private_impl.limit = NULL;\n  return ret;\n}\n\n// wuffs_base__io_buffer__compact moves any written but unread bytes to the\n// start of the buffer.\nstatic inline void  //\nwuffs_base__io_buffer__compact(wuffs_base__io_buffer* buf) {\n  if (!buf || (buf->meta.ri == 0)) {\n    return;\n  }\n  buf->meta.pos = wuffs_base__u64__sat_add(buf->meta.pos, buf->meta.ri);\n  size_t n = buf->meta.wi - buf->meta.ri;\n  if (n != 0) {\n    memmove(buf->data.ptr, buf->data.ptr + buf->meta.ri, n);\n  }\n  buf->meta.wi = n;\n  buf->meta.ri = 0;\n}\n\nstatic inline wuffs_base__io_reader  //\nwuffs_base__io_buffer__reader(wuffs_base__io_buffer* buf) {\n  wuffs_base__io_reader ret;\n  ret.private_impl.buf = buf;\n  ret.private_impl.mark = NULL;\n  ret.private_impl.limit = NULL;\n  return ret;\n}\n\nstatic inline wuffs_base__io_writer  " +
-	"//\nwuffs_base__io_buffer__writer(wuffs_base__io_buffer* buf) {\n  wuffs_base__io_writer ret;\n  ret.private_impl.buf = buf;\n  ret.private_impl.mark = NULL;\n  ret.private_impl.limit = NULL;\n  return ret;\n}\n\nstatic inline uint64_t  //\nwuffs_base__io_buffer__reader_io_position(wuffs_base__io_buffer* buf) {\n  return buf ? wuffs_base__u64__sat_add(buf->meta.pos, buf->meta.ri) : 0;\n}\n\nstatic inline uint64_t  //\nwuffs_base__io_buffer__writer_io_position(wuffs_base__io_buffer* buf) {\n  return buf ? wuffs_base__u64__sat_add(buf->meta.pos, buf->meta.wi) : 0;\n}\n\n#ifdef __cplusplus\n\ninline void  //\nwuffs_base__io_buffer__struct::compact() {\n  wuffs_base__io_buffer__compact(this);\n}\n\ninline wuffs_base__io_reader  //\nwuffs_base__io_buffer__struct::reader() {\n  return wuffs_base__io_buffer__reader(this);\n}\n\ninline wuffs_base__io_writer  //\nwuffs_base__io_buffer__struct::writer() {\n  return wuffs_base__io_buffer__writer(this);\n}\n\ninline uint64_t  //\nwuffs_base__io_buffer__struct::reader_io_position() {\n  return wuffs_base__io_" +
-	"buffer__reader_io_position(this);\n}\n\ninline uint64_t  //\nwuffs_base__io_buffer__struct::writer_io_position() {\n  return wuffs_base__io_buffer__writer_io_position(this);\n}\n\n#endif  // __cplusplus\n" +
+	"// ---------------- I/O\n//\n// See (/doc/note/io-input-output.md).\n\n// wuffs_base__io_buffer_meta is the metadata for a wuffs_base__io_buffer's\n// data.\ntypedef struct {\n  size_t wi;     // Write index. Invariant: wi <= len.\n  size_t ri;     // Read  index. Invariant: ri <= wi.\n  uint64_t pos;  // Position of the buffer start relative to the stream start.\n  bool closed;   // No further writes are expected.\n} wuffs_base__io_buffer_meta;\n\n// wuffs_base__io_buffer is a 1-dimensional buffer (a pointer and length) plus\n// additional metadata.\n//\n// A value with all fields zero is a valid, empty buffer.\ntypedef struct wuffs_base__io_buffer__struct {\n  wuffs_base__slice_u8 data;\n  wuffs_base__io_buffer_meta meta;\n\n#ifdef __cplusplus\n  inline void compact();\n  inline wuffs_base__io_buffer__struct* reader();  // Deprecated.\n  inline wuffs_base__io_buffer__struct* writer();  // Deprecated.\n  inline uint64_t reader_available() const;\n  inline uint64_t reader_io_position() const;\n  inline uint64_t writer_available() const" +
+	";\n  inline uint64_t writer_io_position() const;\n#endif  // __cplusplus\n\n} wuffs_base__io_buffer;\n\nstatic inline wuffs_base__io_buffer  //\nwuffs_base__make_io_buffer(wuffs_base__slice_u8 data,\n                           wuffs_base__io_buffer_meta meta) {\n  wuffs_base__io_buffer ret;\n  ret.data = data;\n  ret.meta = meta;\n  return ret;\n}\n\nstatic inline wuffs_base__io_buffer_meta  //\nwuffs_base__make_io_buffer_meta(size_t wi,\n                                size_t ri,\n                                uint64_t pos,\n                                bool closed) {\n  wuffs_base__io_buffer_meta ret;\n  ret.wi = wi;\n  ret.ri = ri;\n  ret.pos = pos;\n  ret.closed = closed;\n  return ret;\n}\n\nstatic inline wuffs_base__io_buffer  //\nwuffs_base__null_io_buffer() {\n  wuffs_base__io_buffer ret;\n  ret.data.ptr = NULL;\n  ret.data.len = 0;\n  ret.meta.wi = 0;\n  ret.meta.ri = 0;\n  ret.meta.pos = 0;\n  ret.meta.closed = false;\n  return ret;\n}\n\nstatic inline wuffs_base__io_buffer_meta  //\nwuffs_base__null_io_buffer_meta() {\n  wuffs_base__i" +
+	"o_buffer_meta ret;\n  ret.wi = 0;\n  ret.ri = 0;\n  ret.pos = 0;\n  ret.closed = false;\n  return ret;\n}\n\n// wuffs_base__io_buffer__compact moves any written but unread bytes to the\n// start of the buffer.\nstatic inline void  //\nwuffs_base__io_buffer__compact(wuffs_base__io_buffer* buf) {\n  if (!buf || (buf->meta.ri == 0)) {\n    return;\n  }\n  buf->meta.pos = wuffs_base__u64__sat_add(buf->meta.pos, buf->meta.ri);\n  size_t n = buf->meta.wi - buf->meta.ri;\n  if (n != 0) {\n    memmove(buf->data.ptr, buf->data.ptr + buf->meta.ri, n);\n  }\n  buf->meta.wi = n;\n  buf->meta.ri = 0;\n}\n\nstatic inline uint64_t  //\nwuffs_base__io_buffer__reader_available(const wuffs_base__io_buffer* buf) {\n  return buf ? buf->meta.wi - buf->meta.ri : 0;\n}\n\nstatic inline uint64_t  //\nwuffs_base__io_buffer__reader_io_position(const wuffs_base__io_buffer* buf) {\n  return buf ? wuffs_base__u64__sat_add(buf->meta.pos, buf->meta.ri) : 0;\n}\n\nstatic inline uint64_t  //\nwuffs_base__io_buffer__writer_available(const wuffs_base__io_buffer* buf) {\n  return" +
+	" buf ? buf->data.len - buf->meta.wi : 0;\n}\n\nstatic inline uint64_t  //\nwuffs_base__io_buffer__writer_io_position(const wuffs_base__io_buffer* buf) {\n  return buf ? wuffs_base__u64__sat_add(buf->meta.pos, buf->meta.wi) : 0;\n}\n\n#ifdef __cplusplus\n\ninline void  //\nwuffs_base__io_buffer__struct::compact() {\n  wuffs_base__io_buffer__compact(this);\n}\n\ninline wuffs_base__io_buffer*  //\nwuffs_base__io_buffer__struct::reader() {\n  return this;\n}\n\ninline wuffs_base__io_buffer*  //\nwuffs_base__io_buffer__struct::writer() {\n  return this;\n}\n\ninline uint64_t  //\nwuffs_base__io_buffer__struct::reader_available() const {\n  return wuffs_base__io_buffer__reader_available(this);\n}\n\ninline uint64_t  //\nwuffs_base__io_buffer__struct::reader_io_position() const {\n  return wuffs_base__io_buffer__reader_io_position(this);\n}\n\ninline uint64_t  //\nwuffs_base__io_buffer__struct::writer_available() const {\n  return wuffs_base__io_buffer__writer_available(this);\n}\n\ninline uint64_t  //\nwuffs_base__io_buffer__struct::writer_io_position() c" +
+	"onst {\n  return wuffs_base__io_buffer__writer_io_position(this);\n}\n\n#endif  // __cplusplus\n" +
 	""
 
 const baseRangePrivateH = "" +
@@ -186,39 +191,39 @@
 
 const baseRangePublicH = "" +
 	"// ---------------- Ranges and Rects\n\n// Ranges are either inclusive (\"range_ii\") or exclusive (\"range_ie\") on the\n// high end. Both the \"ii\" and \"ie\" flavors are useful in practice.\n//\n// The \"ei\" and \"ee\" flavors also exist in theory, but aren't widely used. In\n// Wuffs, the low end is always inclusive.\n//\n// The \"ii\" (closed interval) flavor is useful when refining e.g. \"the set of\n// all uint32_t values\" to a contiguous subset: \"uint32_t values in the closed\n// interval [M, N]\", for uint32_t values M and N. An unrefined type (in other\n// words, the set of all uint32_t values) is not representable in the \"ie\"\n// flavor because if N equals ((1<<32) - 1) then (N + 1) will overflow.\n//\n// On the other hand, the \"ie\" (half-open interval) flavor is recommended by\n// Dijkstra's \"Why numbering should start at zero\" at\n// http://www.cs.utexas.edu/users/EWD/ewd08xx/EWD831.PDF and a further\n// discussion of motivating rationale is at\n// https://www.quora.com/Why-are-Python-ranges-half-open-exclusive-instead-of-close" +
-	"d-inclusive\n//\n// For example, with \"ie\", the number of elements in \"uint32_t values in the\n// half-open interval [M, N)\" is equal to max(0, N-M). Furthermore, that number\n// of elements (in one dimension, a length, in two dimensions, a width or\n// height) is itself representable as a uint32_t without overflow, again for\n// uint32_t values M and N. In the contrasting \"ii\" flavor, the length of the\n// closed interval [0, (1<<32) - 1] is 1<<32, which cannot be represented as a\n// uint32_t. In Wuffs, because of this potential overflow, the \"ie\" flavor has\n// length / width / height methods, but the \"ii\" flavor does not.\n//\n// It is valid for min > max (for range_ii) or for min >= max (for range_ie),\n// in which case the range is empty. There are multiple representations of an\n// empty range.\n\ntypedef struct wuffs_base__range_ii_u32__struct {\n  uint32_t min_incl;\n  uint32_t max_incl;\n\n#ifdef __cplusplus\n  inline bool is_empty();\n  inline bool equals(wuffs_base__range_ii_u32__struct s);\n  inline wuffs_base__range_" +
-	"ii_u32__struct intersect(\n      wuffs_base__range_ii_u32__struct s);\n  inline wuffs_base__range_ii_u32__struct unite(\n      wuffs_base__range_ii_u32__struct s);\n  inline bool contains(uint32_t x);\n  inline bool contains_range(wuffs_base__range_ii_u32__struct s);\n#endif  // __cplusplus\n\n} wuffs_base__range_ii_u32;\n\nstatic inline wuffs_base__range_ii_u32  //\nwuffs_base__make_range_ii_u32(uint32_t min_incl, uint32_t max_incl) {\n  wuffs_base__range_ii_u32 ret;\n  ret.min_incl = min_incl;\n  ret.max_incl = max_incl;\n  return ret;\n}\n\nstatic inline bool  //\nwuffs_base__range_ii_u32__is_empty(wuffs_base__range_ii_u32* r) {\n  return r->min_incl > r->max_incl;\n}\n\nstatic inline bool  //\nwuffs_base__range_ii_u32__equals(wuffs_base__range_ii_u32* r,\n                                 wuffs_base__range_ii_u32 s) {\n  return (r->min_incl == s.min_incl && r->max_incl == s.max_incl) ||\n         (wuffs_base__range_ii_u32__is_empty(r) &&\n          wuffs_base__range_ii_u32__is_empty(&s));\n}\n\nstatic inline wuffs_base__range_ii_u32  //" +
-	"\nwuffs_base__range_ii_u32__intersect(wuffs_base__range_ii_u32* r,\n                                    wuffs_base__range_ii_u32 s) {\n  wuffs_base__range_ii_u32 t;\n  t.min_incl = wuffs_base__u32__max(r->min_incl, s.min_incl);\n  t.max_incl = wuffs_base__u32__min(r->max_incl, s.max_incl);\n  return t;\n}\n\nstatic inline wuffs_base__range_ii_u32  //\nwuffs_base__range_ii_u32__unite(wuffs_base__range_ii_u32* r,\n                                wuffs_base__range_ii_u32 s) {\n  if (wuffs_base__range_ii_u32__is_empty(r)) {\n    return s;\n  }\n  if (wuffs_base__range_ii_u32__is_empty(&s)) {\n    return *r;\n  }\n  wuffs_base__range_ii_u32 t;\n  t.min_incl = wuffs_base__u32__min(r->min_incl, s.min_incl);\n  t.max_incl = wuffs_base__u32__max(r->max_incl, s.max_incl);\n  return t;\n}\n\nstatic inline bool  //\nwuffs_base__range_ii_u32__contains(wuffs_base__range_ii_u32* r, uint32_t x) {\n  return (r->min_incl <= x) && (x <= r->max_incl);\n}\n\nstatic inline bool  //\nwuffs_base__range_ii_u32__contains_range(wuffs_base__range_ii_u32* r,\n        " +
-	"                                 wuffs_base__range_ii_u32 s) {\n  return wuffs_base__range_ii_u32__equals(\n      &s, wuffs_base__range_ii_u32__intersect(r, s));\n}\n\n#ifdef __cplusplus\n\ninline bool  //\nwuffs_base__range_ii_u32::is_empty() {\n  return wuffs_base__range_ii_u32__is_empty(this);\n}\n\ninline bool  //\nwuffs_base__range_ii_u32::equals(wuffs_base__range_ii_u32 s) {\n  return wuffs_base__range_ii_u32__equals(this, s);\n}\n\ninline wuffs_base__range_ii_u32  //\nwuffs_base__range_ii_u32::intersect(wuffs_base__range_ii_u32 s) {\n  return wuffs_base__range_ii_u32__intersect(this, s);\n}\n\ninline wuffs_base__range_ii_u32  //\nwuffs_base__range_ii_u32::unite(wuffs_base__range_ii_u32 s) {\n  return wuffs_base__range_ii_u32__unite(this, s);\n}\n\ninline bool  //\nwuffs_base__range_ii_u32::contains(uint32_t x) {\n  return wuffs_base__range_ii_u32__contains(this, x);\n}\n\ninline bool  //\nwuffs_base__range_ii_u32::contains_range(wuffs_base__range_ii_u32 s) {\n  return wuffs_base__range_ii_u32__contains_range(this, s);\n}\n\n#endif  // __c" +
-	"plusplus\n\n" +
+	"d-inclusive\n//\n// For example, with \"ie\", the number of elements in \"uint32_t values in the\n// half-open interval [M, N)\" is equal to max(0, N-M). Furthermore, that number\n// of elements (in one dimension, a length, in two dimensions, a width or\n// height) is itself representable as a uint32_t without overflow, again for\n// uint32_t values M and N. In the contrasting \"ii\" flavor, the length of the\n// closed interval [0, (1<<32) - 1] is 1<<32, which cannot be represented as a\n// uint32_t. In Wuffs, because of this potential overflow, the \"ie\" flavor has\n// length / width / height methods, but the \"ii\" flavor does not.\n//\n// It is valid for min > max (for range_ii) or for min >= max (for range_ie),\n// in which case the range is empty. There are multiple representations of an\n// empty range.\n\ntypedef struct wuffs_base__range_ii_u32__struct {\n  uint32_t min_incl;\n  uint32_t max_incl;\n\n#ifdef __cplusplus\n  inline bool is_empty() const;\n  inline bool equals(wuffs_base__range_ii_u32__struct s) const;\n  inline wuffs_" +
+	"base__range_ii_u32__struct intersect(\n      wuffs_base__range_ii_u32__struct s) const;\n  inline wuffs_base__range_ii_u32__struct unite(\n      wuffs_base__range_ii_u32__struct s) const;\n  inline bool contains(uint32_t x) const;\n  inline bool contains_range(wuffs_base__range_ii_u32__struct s) const;\n#endif  // __cplusplus\n\n} wuffs_base__range_ii_u32;\n\nstatic inline wuffs_base__range_ii_u32  //\nwuffs_base__make_range_ii_u32(uint32_t min_incl, uint32_t max_incl) {\n  wuffs_base__range_ii_u32 ret;\n  ret.min_incl = min_incl;\n  ret.max_incl = max_incl;\n  return ret;\n}\n\nstatic inline bool  //\nwuffs_base__range_ii_u32__is_empty(const wuffs_base__range_ii_u32* r) {\n  return r->min_incl > r->max_incl;\n}\n\nstatic inline bool  //\nwuffs_base__range_ii_u32__equals(const wuffs_base__range_ii_u32* r,\n                                 wuffs_base__range_ii_u32 s) {\n  return (r->min_incl == s.min_incl && r->max_incl == s.max_incl) ||\n         (wuffs_base__range_ii_u32__is_empty(r) &&\n          wuffs_base__range_ii_u32__is_empty(&s)" +
+	");\n}\n\nstatic inline wuffs_base__range_ii_u32  //\nwuffs_base__range_ii_u32__intersect(const wuffs_base__range_ii_u32* r,\n                                    wuffs_base__range_ii_u32 s) {\n  wuffs_base__range_ii_u32 t;\n  t.min_incl = wuffs_base__u32__max(r->min_incl, s.min_incl);\n  t.max_incl = wuffs_base__u32__min(r->max_incl, s.max_incl);\n  return t;\n}\n\nstatic inline wuffs_base__range_ii_u32  //\nwuffs_base__range_ii_u32__unite(const wuffs_base__range_ii_u32* r,\n                                wuffs_base__range_ii_u32 s) {\n  if (wuffs_base__range_ii_u32__is_empty(r)) {\n    return s;\n  }\n  if (wuffs_base__range_ii_u32__is_empty(&s)) {\n    return *r;\n  }\n  wuffs_base__range_ii_u32 t;\n  t.min_incl = wuffs_base__u32__min(r->min_incl, s.min_incl);\n  t.max_incl = wuffs_base__u32__max(r->max_incl, s.max_incl);\n  return t;\n}\n\nstatic inline bool  //\nwuffs_base__range_ii_u32__contains(const wuffs_base__range_ii_u32* r,\n                                   uint32_t x) {\n  return (r->min_incl <= x) && (x <= r->max_incl);\n}\n\n" +
+	"static inline bool  //\nwuffs_base__range_ii_u32__contains_range(const wuffs_base__range_ii_u32* r,\n                                         wuffs_base__range_ii_u32 s) {\n  return wuffs_base__range_ii_u32__equals(\n      &s, wuffs_base__range_ii_u32__intersect(r, s));\n}\n\n#ifdef __cplusplus\n\ninline bool  //\nwuffs_base__range_ii_u32::is_empty() const {\n  return wuffs_base__range_ii_u32__is_empty(this);\n}\n\ninline bool  //\nwuffs_base__range_ii_u32::equals(wuffs_base__range_ii_u32 s) const {\n  return wuffs_base__range_ii_u32__equals(this, s);\n}\n\ninline wuffs_base__range_ii_u32  //\nwuffs_base__range_ii_u32::intersect(wuffs_base__range_ii_u32 s) const {\n  return wuffs_base__range_ii_u32__intersect(this, s);\n}\n\ninline wuffs_base__range_ii_u32  //\nwuffs_base__range_ii_u32::unite(wuffs_base__range_ii_u32 s) const {\n  return wuffs_base__range_ii_u32__unite(this, s);\n}\n\ninline bool  //\nwuffs_base__range_ii_u32::contains(uint32_t x) const {\n  return wuffs_base__range_ii_u32__contains(this, x);\n}\n\ninline bool  //\nwuffs_base_" +
+	"_range_ii_u32::contains_range(wuffs_base__range_ii_u32 s) const {\n  return wuffs_base__range_ii_u32__contains_range(this, s);\n}\n\n#endif  // __cplusplus\n\n" +
 	"" +
-	"// --------\n\ntypedef struct wuffs_base__range_ie_u32__struct {\n  uint32_t min_incl;\n  uint32_t max_excl;\n\n#ifdef __cplusplus\n  inline bool is_empty();\n  inline bool equals(wuffs_base__range_ie_u32__struct s);\n  inline wuffs_base__range_ie_u32__struct intersect(\n      wuffs_base__range_ie_u32__struct s);\n  inline wuffs_base__range_ie_u32__struct unite(\n      wuffs_base__range_ie_u32__struct s);\n  inline bool contains(uint32_t x);\n  inline bool contains_range(wuffs_base__range_ie_u32__struct s);\n  inline uint32_t length();\n#endif  // __cplusplus\n\n} wuffs_base__range_ie_u32;\n\nstatic inline wuffs_base__range_ie_u32  //\nwuffs_base__make_range_ie_u32(uint32_t min_incl, uint32_t max_excl) {\n  wuffs_base__range_ie_u32 ret;\n  ret.min_incl = min_incl;\n  ret.max_excl = max_excl;\n  return ret;\n}\n\nstatic inline bool  //\nwuffs_base__range_ie_u32__is_empty(wuffs_base__range_ie_u32* r) {\n  return r->min_incl >= r->max_excl;\n}\n\nstatic inline bool  //\nwuffs_base__range_ie_u32__equals(wuffs_base__range_ie_u32* r,\n              " +
-	"                   wuffs_base__range_ie_u32 s) {\n  return (r->min_incl == s.min_incl && r->max_excl == s.max_excl) ||\n         (wuffs_base__range_ie_u32__is_empty(r) &&\n          wuffs_base__range_ie_u32__is_empty(&s));\n}\n\nstatic inline wuffs_base__range_ie_u32  //\nwuffs_base__range_ie_u32__intersect(wuffs_base__range_ie_u32* r,\n                                    wuffs_base__range_ie_u32 s) {\n  wuffs_base__range_ie_u32 t;\n  t.min_incl = wuffs_base__u32__max(r->min_incl, s.min_incl);\n  t.max_excl = wuffs_base__u32__min(r->max_excl, s.max_excl);\n  return t;\n}\n\nstatic inline wuffs_base__range_ie_u32  //\nwuffs_base__range_ie_u32__unite(wuffs_base__range_ie_u32* r,\n                                wuffs_base__range_ie_u32 s) {\n  if (wuffs_base__range_ie_u32__is_empty(r)) {\n    return s;\n  }\n  if (wuffs_base__range_ie_u32__is_empty(&s)) {\n    return *r;\n  }\n  wuffs_base__range_ie_u32 t;\n  t.min_incl = wuffs_base__u32__min(r->min_incl, s.min_incl);\n  t.max_excl = wuffs_base__u32__max(r->max_excl, s.max_excl);\n  retu" +
-	"rn t;\n}\n\nstatic inline bool  //\nwuffs_base__range_ie_u32__contains(wuffs_base__range_ie_u32* r, uint32_t x) {\n  return (r->min_incl <= x) && (x < r->max_excl);\n}\n\nstatic inline bool  //\nwuffs_base__range_ie_u32__contains_range(wuffs_base__range_ie_u32* r,\n                                         wuffs_base__range_ie_u32 s) {\n  return wuffs_base__range_ie_u32__equals(\n      &s, wuffs_base__range_ie_u32__intersect(r, s));\n}\n\nstatic inline uint32_t  //\nwuffs_base__range_ie_u32__length(wuffs_base__range_ie_u32* r) {\n  return wuffs_base__u32__sat_sub(r->max_excl, r->min_incl);\n}\n\n#ifdef __cplusplus\n\ninline bool  //\nwuffs_base__range_ie_u32::is_empty() {\n  return wuffs_base__range_ie_u32__is_empty(this);\n}\n\ninline bool  //\nwuffs_base__range_ie_u32::equals(wuffs_base__range_ie_u32 s) {\n  return wuffs_base__range_ie_u32__equals(this, s);\n}\n\ninline wuffs_base__range_ie_u32  //\nwuffs_base__range_ie_u32::intersect(wuffs_base__range_ie_u32 s) {\n  return wuffs_base__range_ie_u32__intersect(this, s);\n}\n\ninline wuffs_base__" +
-	"range_ie_u32  //\nwuffs_base__range_ie_u32::unite(wuffs_base__range_ie_u32 s) {\n  return wuffs_base__range_ie_u32__unite(this, s);\n}\n\ninline bool  //\nwuffs_base__range_ie_u32::contains(uint32_t x) {\n  return wuffs_base__range_ie_u32__contains(this, x);\n}\n\ninline bool  //\nwuffs_base__range_ie_u32::contains_range(wuffs_base__range_ie_u32 s) {\n  return wuffs_base__range_ie_u32__contains_range(this, s);\n}\n\ninline uint32_t  //\nwuffs_base__range_ie_u32::length() {\n  return wuffs_base__range_ie_u32__length(this);\n}\n\n#endif  // __cplusplus\n\n" +
+	"// --------\n\ntypedef struct wuffs_base__range_ie_u32__struct {\n  uint32_t min_incl;\n  uint32_t max_excl;\n\n#ifdef __cplusplus\n  inline bool is_empty() const;\n  inline bool equals(wuffs_base__range_ie_u32__struct s) const;\n  inline wuffs_base__range_ie_u32__struct intersect(\n      wuffs_base__range_ie_u32__struct s) const;\n  inline wuffs_base__range_ie_u32__struct unite(\n      wuffs_base__range_ie_u32__struct s) const;\n  inline bool contains(uint32_t x) const;\n  inline bool contains_range(wuffs_base__range_ie_u32__struct s) const;\n  inline uint32_t length() const;\n#endif  // __cplusplus\n\n} wuffs_base__range_ie_u32;\n\nstatic inline wuffs_base__range_ie_u32  //\nwuffs_base__make_range_ie_u32(uint32_t min_incl, uint32_t max_excl) {\n  wuffs_base__range_ie_u32 ret;\n  ret.min_incl = min_incl;\n  ret.max_excl = max_excl;\n  return ret;\n}\n\nstatic inline bool  //\nwuffs_base__range_ie_u32__is_empty(const wuffs_base__range_ie_u32* r) {\n  return r->min_incl >= r->max_excl;\n}\n\nstatic inline bool  //\nwuffs_base__range_ie_u32__eq" +
+	"uals(const wuffs_base__range_ie_u32* r,\n                                 wuffs_base__range_ie_u32 s) {\n  return (r->min_incl == s.min_incl && r->max_excl == s.max_excl) ||\n         (wuffs_base__range_ie_u32__is_empty(r) &&\n          wuffs_base__range_ie_u32__is_empty(&s));\n}\n\nstatic inline wuffs_base__range_ie_u32  //\nwuffs_base__range_ie_u32__intersect(const wuffs_base__range_ie_u32* r,\n                                    wuffs_base__range_ie_u32 s) {\n  wuffs_base__range_ie_u32 t;\n  t.min_incl = wuffs_base__u32__max(r->min_incl, s.min_incl);\n  t.max_excl = wuffs_base__u32__min(r->max_excl, s.max_excl);\n  return t;\n}\n\nstatic inline wuffs_base__range_ie_u32  //\nwuffs_base__range_ie_u32__unite(const wuffs_base__range_ie_u32* r,\n                                wuffs_base__range_ie_u32 s) {\n  if (wuffs_base__range_ie_u32__is_empty(r)) {\n    return s;\n  }\n  if (wuffs_base__range_ie_u32__is_empty(&s)) {\n    return *r;\n  }\n  wuffs_base__range_ie_u32 t;\n  t.min_incl = wuffs_base__u32__min(r->min_incl, s.min_incl);\n  " +
+	"t.max_excl = wuffs_base__u32__max(r->max_excl, s.max_excl);\n  return t;\n}\n\nstatic inline bool  //\nwuffs_base__range_ie_u32__contains(const wuffs_base__range_ie_u32* r,\n                                   uint32_t x) {\n  return (r->min_incl <= x) && (x < r->max_excl);\n}\n\nstatic inline bool  //\nwuffs_base__range_ie_u32__contains_range(const wuffs_base__range_ie_u32* r,\n                                         wuffs_base__range_ie_u32 s) {\n  return wuffs_base__range_ie_u32__equals(\n      &s, wuffs_base__range_ie_u32__intersect(r, s));\n}\n\nstatic inline uint32_t  //\nwuffs_base__range_ie_u32__length(const wuffs_base__range_ie_u32* r) {\n  return wuffs_base__u32__sat_sub(r->max_excl, r->min_incl);\n}\n\n#ifdef __cplusplus\n\ninline bool  //\nwuffs_base__range_ie_u32::is_empty() const {\n  return wuffs_base__range_ie_u32__is_empty(this);\n}\n\ninline bool  //\nwuffs_base__range_ie_u32::equals(wuffs_base__range_ie_u32 s) const {\n  return wuffs_base__range_ie_u32__equals(this, s);\n}\n\ninline wuffs_base__range_ie_u32  //\nwuffs_base__" +
+	"range_ie_u32::intersect(wuffs_base__range_ie_u32 s) const {\n  return wuffs_base__range_ie_u32__intersect(this, s);\n}\n\ninline wuffs_base__range_ie_u32  //\nwuffs_base__range_ie_u32::unite(wuffs_base__range_ie_u32 s) const {\n  return wuffs_base__range_ie_u32__unite(this, s);\n}\n\ninline bool  //\nwuffs_base__range_ie_u32::contains(uint32_t x) const {\n  return wuffs_base__range_ie_u32__contains(this, x);\n}\n\ninline bool  //\nwuffs_base__range_ie_u32::contains_range(wuffs_base__range_ie_u32 s) const {\n  return wuffs_base__range_ie_u32__contains_range(this, s);\n}\n\ninline uint32_t  //\nwuffs_base__range_ie_u32::length() const {\n  return wuffs_base__range_ie_u32__length(this);\n}\n\n#endif  // __cplusplus\n\n" +
 	"" +
-	"// --------\n\ntypedef struct wuffs_base__range_ii_u64__struct {\n  uint64_t min_incl;\n  uint64_t max_incl;\n\n#ifdef __cplusplus\n  inline bool is_empty();\n  inline bool equals(wuffs_base__range_ii_u64__struct s);\n  inline wuffs_base__range_ii_u64__struct intersect(\n      wuffs_base__range_ii_u64__struct s);\n  inline wuffs_base__range_ii_u64__struct unite(\n      wuffs_base__range_ii_u64__struct s);\n  inline bool contains(uint64_t x);\n  inline bool contains_range(wuffs_base__range_ii_u64__struct s);\n#endif  // __cplusplus\n\n} wuffs_base__range_ii_u64;\n\nstatic inline wuffs_base__range_ii_u64  //\nwuffs_base__make_range_ii_u64(uint64_t min_incl, uint64_t max_incl) {\n  wuffs_base__range_ii_u64 ret;\n  ret.min_incl = min_incl;\n  ret.max_incl = max_incl;\n  return ret;\n}\n\nstatic inline bool  //\nwuffs_base__range_ii_u64__is_empty(wuffs_base__range_ii_u64* r) {\n  return r->min_incl > r->max_incl;\n}\n\nstatic inline bool  //\nwuffs_base__range_ii_u64__equals(wuffs_base__range_ii_u64* r,\n                                 wuffs_base" +
-	"__range_ii_u64 s) {\n  return (r->min_incl == s.min_incl && r->max_incl == s.max_incl) ||\n         (wuffs_base__range_ii_u64__is_empty(r) &&\n          wuffs_base__range_ii_u64__is_empty(&s));\n}\n\nstatic inline wuffs_base__range_ii_u64  //\nwuffs_base__range_ii_u64__intersect(wuffs_base__range_ii_u64* r,\n                                    wuffs_base__range_ii_u64 s) {\n  wuffs_base__range_ii_u64 t;\n  t.min_incl = wuffs_base__u64__max(r->min_incl, s.min_incl);\n  t.max_incl = wuffs_base__u64__min(r->max_incl, s.max_incl);\n  return t;\n}\n\nstatic inline wuffs_base__range_ii_u64  //\nwuffs_base__range_ii_u64__unite(wuffs_base__range_ii_u64* r,\n                                wuffs_base__range_ii_u64 s) {\n  if (wuffs_base__range_ii_u64__is_empty(r)) {\n    return s;\n  }\n  if (wuffs_base__range_ii_u64__is_empty(&s)) {\n    return *r;\n  }\n  wuffs_base__range_ii_u64 t;\n  t.min_incl = wuffs_base__u64__min(r->min_incl, s.min_incl);\n  t.max_incl = wuffs_base__u64__max(r->max_incl, s.max_incl);\n  return t;\n}\n\nstatic inline bool  " +
-	"//\nwuffs_base__range_ii_u64__contains(wuffs_base__range_ii_u64* r, uint64_t x) {\n  return (r->min_incl <= x) && (x <= r->max_incl);\n}\n\nstatic inline bool  //\nwuffs_base__range_ii_u64__contains_range(wuffs_base__range_ii_u64* r,\n                                         wuffs_base__range_ii_u64 s) {\n  return wuffs_base__range_ii_u64__equals(\n      &s, wuffs_base__range_ii_u64__intersect(r, s));\n}\n\n#ifdef __cplusplus\n\ninline bool  //\nwuffs_base__range_ii_u64::is_empty() {\n  return wuffs_base__range_ii_u64__is_empty(this);\n}\n\ninline bool  //\nwuffs_base__range_ii_u64::equals(wuffs_base__range_ii_u64 s) {\n  return wuffs_base__range_ii_u64__equals(this, s);\n}\n\ninline wuffs_base__range_ii_u64  //\nwuffs_base__range_ii_u64::intersect(wuffs_base__range_ii_u64 s) {\n  return wuffs_base__range_ii_u64__intersect(this, s);\n}\n\ninline wuffs_base__range_ii_u64  //\nwuffs_base__range_ii_u64::unite(wuffs_base__range_ii_u64 s) {\n  return wuffs_base__range_ii_u64__unite(this, s);\n}\n\ninline bool  //\nwuffs_base__range_ii_u64::contains" +
-	"(uint64_t x) {\n  return wuffs_base__range_ii_u64__contains(this, x);\n}\n\ninline bool  //\nwuffs_base__range_ii_u64::contains_range(wuffs_base__range_ii_u64 s) {\n  return wuffs_base__range_ii_u64__contains_range(this, s);\n}\n\n#endif  // __cplusplus\n\n" +
+	"// --------\n\ntypedef struct wuffs_base__range_ii_u64__struct {\n  uint64_t min_incl;\n  uint64_t max_incl;\n\n#ifdef __cplusplus\n  inline bool is_empty() const;\n  inline bool equals(wuffs_base__range_ii_u64__struct s) const;\n  inline wuffs_base__range_ii_u64__struct intersect(\n      wuffs_base__range_ii_u64__struct s) const;\n  inline wuffs_base__range_ii_u64__struct unite(\n      wuffs_base__range_ii_u64__struct s) const;\n  inline bool contains(uint64_t x) const;\n  inline bool contains_range(wuffs_base__range_ii_u64__struct s) const;\n#endif  // __cplusplus\n\n} wuffs_base__range_ii_u64;\n\nstatic inline wuffs_base__range_ii_u64  //\nwuffs_base__make_range_ii_u64(uint64_t min_incl, uint64_t max_incl) {\n  wuffs_base__range_ii_u64 ret;\n  ret.min_incl = min_incl;\n  ret.max_incl = max_incl;\n  return ret;\n}\n\nstatic inline bool  //\nwuffs_base__range_ii_u64__is_empty(const wuffs_base__range_ii_u64* r) {\n  return r->min_incl > r->max_incl;\n}\n\nstatic inline bool  //\nwuffs_base__range_ii_u64__equals(const wuffs_base__range_ii_u64" +
+	"* r,\n                                 wuffs_base__range_ii_u64 s) {\n  return (r->min_incl == s.min_incl && r->max_incl == s.max_incl) ||\n         (wuffs_base__range_ii_u64__is_empty(r) &&\n          wuffs_base__range_ii_u64__is_empty(&s));\n}\n\nstatic inline wuffs_base__range_ii_u64  //\nwuffs_base__range_ii_u64__intersect(const wuffs_base__range_ii_u64* r,\n                                    wuffs_base__range_ii_u64 s) {\n  wuffs_base__range_ii_u64 t;\n  t.min_incl = wuffs_base__u64__max(r->min_incl, s.min_incl);\n  t.max_incl = wuffs_base__u64__min(r->max_incl, s.max_incl);\n  return t;\n}\n\nstatic inline wuffs_base__range_ii_u64  //\nwuffs_base__range_ii_u64__unite(const wuffs_base__range_ii_u64* r,\n                                wuffs_base__range_ii_u64 s) {\n  if (wuffs_base__range_ii_u64__is_empty(r)) {\n    return s;\n  }\n  if (wuffs_base__range_ii_u64__is_empty(&s)) {\n    return *r;\n  }\n  wuffs_base__range_ii_u64 t;\n  t.min_incl = wuffs_base__u64__min(r->min_incl, s.min_incl);\n  t.max_incl = wuffs_base__u64__max(r" +
+	"->max_incl, s.max_incl);\n  return t;\n}\n\nstatic inline bool  //\nwuffs_base__range_ii_u64__contains(const wuffs_base__range_ii_u64* r,\n                                   uint64_t x) {\n  return (r->min_incl <= x) && (x <= r->max_incl);\n}\n\nstatic inline bool  //\nwuffs_base__range_ii_u64__contains_range(const wuffs_base__range_ii_u64* r,\n                                         wuffs_base__range_ii_u64 s) {\n  return wuffs_base__range_ii_u64__equals(\n      &s, wuffs_base__range_ii_u64__intersect(r, s));\n}\n\n#ifdef __cplusplus\n\ninline bool  //\nwuffs_base__range_ii_u64::is_empty() const {\n  return wuffs_base__range_ii_u64__is_empty(this);\n}\n\ninline bool  //\nwuffs_base__range_ii_u64::equals(wuffs_base__range_ii_u64 s) const {\n  return wuffs_base__range_ii_u64__equals(this, s);\n}\n\ninline wuffs_base__range_ii_u64  //\nwuffs_base__range_ii_u64::intersect(wuffs_base__range_ii_u64 s) const {\n  return wuffs_base__range_ii_u64__intersect(this, s);\n}\n\ninline wuffs_base__range_ii_u64  //\nwuffs_base__range_ii_u64::unite(wuffs_bas" +
+	"e__range_ii_u64 s) const {\n  return wuffs_base__range_ii_u64__unite(this, s);\n}\n\ninline bool  //\nwuffs_base__range_ii_u64::contains(uint64_t x) const {\n  return wuffs_base__range_ii_u64__contains(this, x);\n}\n\ninline bool  //\nwuffs_base__range_ii_u64::contains_range(wuffs_base__range_ii_u64 s) const {\n  return wuffs_base__range_ii_u64__contains_range(this, s);\n}\n\n#endif  // __cplusplus\n\n" +
 	"" +
-	"// --------\n\ntypedef struct wuffs_base__range_ie_u64__struct {\n  uint64_t min_incl;\n  uint64_t max_excl;\n\n#ifdef __cplusplus\n  inline bool is_empty();\n  inline bool equals(wuffs_base__range_ie_u64__struct s);\n  inline wuffs_base__range_ie_u64__struct intersect(\n      wuffs_base__range_ie_u64__struct s);\n  inline wuffs_base__range_ie_u64__struct unite(\n      wuffs_base__range_ie_u64__struct s);\n  inline bool contains(uint64_t x);\n  inline bool contains_range(wuffs_base__range_ie_u64__struct s);\n  inline uint64_t length();\n#endif  // __cplusplus\n\n} wuffs_base__range_ie_u64;\n\nstatic inline wuffs_base__range_ie_u64  //\nwuffs_base__make_range_ie_u64(uint64_t min_incl, uint64_t max_excl) {\n  wuffs_base__range_ie_u64 ret;\n  ret.min_incl = min_incl;\n  ret.max_excl = max_excl;\n  return ret;\n}\n\nstatic inline bool  //\nwuffs_base__range_ie_u64__is_empty(wuffs_base__range_ie_u64* r) {\n  return r->min_incl >= r->max_excl;\n}\n\nstatic inline bool  //\nwuffs_base__range_ie_u64__equals(wuffs_base__range_ie_u64* r,\n              " +
-	"                   wuffs_base__range_ie_u64 s) {\n  return (r->min_incl == s.min_incl && r->max_excl == s.max_excl) ||\n         (wuffs_base__range_ie_u64__is_empty(r) &&\n          wuffs_base__range_ie_u64__is_empty(&s));\n}\n\nstatic inline wuffs_base__range_ie_u64  //\nwuffs_base__range_ie_u64__intersect(wuffs_base__range_ie_u64* r,\n                                    wuffs_base__range_ie_u64 s) {\n  wuffs_base__range_ie_u64 t;\n  t.min_incl = wuffs_base__u64__max(r->min_incl, s.min_incl);\n  t.max_excl = wuffs_base__u64__min(r->max_excl, s.max_excl);\n  return t;\n}\n\nstatic inline wuffs_base__range_ie_u64  //\nwuffs_base__range_ie_u64__unite(wuffs_base__range_ie_u64* r,\n                                wuffs_base__range_ie_u64 s) {\n  if (wuffs_base__range_ie_u64__is_empty(r)) {\n    return s;\n  }\n  if (wuffs_base__range_ie_u64__is_empty(&s)) {\n    return *r;\n  }\n  wuffs_base__range_ie_u64 t;\n  t.min_incl = wuffs_base__u64__min(r->min_incl, s.min_incl);\n  t.max_excl = wuffs_base__u64__max(r->max_excl, s.max_excl);\n  retu" +
-	"rn t;\n}\n\nstatic inline bool  //\nwuffs_base__range_ie_u64__contains(wuffs_base__range_ie_u64* r, uint64_t x) {\n  return (r->min_incl <= x) && (x < r->max_excl);\n}\n\nstatic inline bool  //\nwuffs_base__range_ie_u64__contains_range(wuffs_base__range_ie_u64* r,\n                                         wuffs_base__range_ie_u64 s) {\n  return wuffs_base__range_ie_u64__equals(\n      &s, wuffs_base__range_ie_u64__intersect(r, s));\n}\n\nstatic inline uint64_t  //\nwuffs_base__range_ie_u64__length(wuffs_base__range_ie_u64* r) {\n  return wuffs_base__u64__sat_sub(r->max_excl, r->min_incl);\n}\n\n#ifdef __cplusplus\n\ninline bool  //\nwuffs_base__range_ie_u64::is_empty() {\n  return wuffs_base__range_ie_u64__is_empty(this);\n}\n\ninline bool  //\nwuffs_base__range_ie_u64::equals(wuffs_base__range_ie_u64 s) {\n  return wuffs_base__range_ie_u64__equals(this, s);\n}\n\ninline wuffs_base__range_ie_u64  //\nwuffs_base__range_ie_u64::intersect(wuffs_base__range_ie_u64 s) {\n  return wuffs_base__range_ie_u64__intersect(this, s);\n}\n\ninline wuffs_base__" +
-	"range_ie_u64  //\nwuffs_base__range_ie_u64::unite(wuffs_base__range_ie_u64 s) {\n  return wuffs_base__range_ie_u64__unite(this, s);\n}\n\ninline bool  //\nwuffs_base__range_ie_u64::contains(uint64_t x) {\n  return wuffs_base__range_ie_u64__contains(this, x);\n}\n\ninline bool  //\nwuffs_base__range_ie_u64::contains_range(wuffs_base__range_ie_u64 s) {\n  return wuffs_base__range_ie_u64__contains_range(this, s);\n}\n\ninline uint64_t  //\nwuffs_base__range_ie_u64::length() {\n  return wuffs_base__range_ie_u64__length(this);\n}\n\n#endif  // __cplusplus\n\n" +
+	"// --------\n\ntypedef struct wuffs_base__range_ie_u64__struct {\n  uint64_t min_incl;\n  uint64_t max_excl;\n\n#ifdef __cplusplus\n  inline bool is_empty() const;\n  inline bool equals(wuffs_base__range_ie_u64__struct s) const;\n  inline wuffs_base__range_ie_u64__struct intersect(\n      wuffs_base__range_ie_u64__struct s) const;\n  inline wuffs_base__range_ie_u64__struct unite(\n      wuffs_base__range_ie_u64__struct s) const;\n  inline bool contains(uint64_t x) const;\n  inline bool contains_range(wuffs_base__range_ie_u64__struct s) const;\n  inline uint64_t length() const;\n#endif  // __cplusplus\n\n} wuffs_base__range_ie_u64;\n\nstatic inline wuffs_base__range_ie_u64  //\nwuffs_base__make_range_ie_u64(uint64_t min_incl, uint64_t max_excl) {\n  wuffs_base__range_ie_u64 ret;\n  ret.min_incl = min_incl;\n  ret.max_excl = max_excl;\n  return ret;\n}\n\nstatic inline bool  //\nwuffs_base__range_ie_u64__is_empty(const wuffs_base__range_ie_u64* r) {\n  return r->min_incl >= r->max_excl;\n}\n\nstatic inline bool  //\nwuffs_base__range_ie_u64__eq" +
+	"uals(const wuffs_base__range_ie_u64* r,\n                                 wuffs_base__range_ie_u64 s) {\n  return (r->min_incl == s.min_incl && r->max_excl == s.max_excl) ||\n         (wuffs_base__range_ie_u64__is_empty(r) &&\n          wuffs_base__range_ie_u64__is_empty(&s));\n}\n\nstatic inline wuffs_base__range_ie_u64  //\nwuffs_base__range_ie_u64__intersect(const wuffs_base__range_ie_u64* r,\n                                    wuffs_base__range_ie_u64 s) {\n  wuffs_base__range_ie_u64 t;\n  t.min_incl = wuffs_base__u64__max(r->min_incl, s.min_incl);\n  t.max_excl = wuffs_base__u64__min(r->max_excl, s.max_excl);\n  return t;\n}\n\nstatic inline wuffs_base__range_ie_u64  //\nwuffs_base__range_ie_u64__unite(const wuffs_base__range_ie_u64* r,\n                                wuffs_base__range_ie_u64 s) {\n  if (wuffs_base__range_ie_u64__is_empty(r)) {\n    return s;\n  }\n  if (wuffs_base__range_ie_u64__is_empty(&s)) {\n    return *r;\n  }\n  wuffs_base__range_ie_u64 t;\n  t.min_incl = wuffs_base__u64__min(r->min_incl, s.min_incl);\n  " +
+	"t.max_excl = wuffs_base__u64__max(r->max_excl, s.max_excl);\n  return t;\n}\n\nstatic inline bool  //\nwuffs_base__range_ie_u64__contains(const wuffs_base__range_ie_u64* r,\n                                   uint64_t x) {\n  return (r->min_incl <= x) && (x < r->max_excl);\n}\n\nstatic inline bool  //\nwuffs_base__range_ie_u64__contains_range(const wuffs_base__range_ie_u64* r,\n                                         wuffs_base__range_ie_u64 s) {\n  return wuffs_base__range_ie_u64__equals(\n      &s, wuffs_base__range_ie_u64__intersect(r, s));\n}\n\nstatic inline uint64_t  //\nwuffs_base__range_ie_u64__length(const wuffs_base__range_ie_u64* r) {\n  return wuffs_base__u64__sat_sub(r->max_excl, r->min_incl);\n}\n\n#ifdef __cplusplus\n\ninline bool  //\nwuffs_base__range_ie_u64::is_empty() const {\n  return wuffs_base__range_ie_u64__is_empty(this);\n}\n\ninline bool  //\nwuffs_base__range_ie_u64::equals(wuffs_base__range_ie_u64 s) const {\n  return wuffs_base__range_ie_u64__equals(this, s);\n}\n\ninline wuffs_base__range_ie_u64  //\nwuffs_base__" +
+	"range_ie_u64::intersect(wuffs_base__range_ie_u64 s) const {\n  return wuffs_base__range_ie_u64__intersect(this, s);\n}\n\ninline wuffs_base__range_ie_u64  //\nwuffs_base__range_ie_u64::unite(wuffs_base__range_ie_u64 s) const {\n  return wuffs_base__range_ie_u64__unite(this, s);\n}\n\ninline bool  //\nwuffs_base__range_ie_u64::contains(uint64_t x) const {\n  return wuffs_base__range_ie_u64__contains(this, x);\n}\n\ninline bool  //\nwuffs_base__range_ie_u64::contains_range(wuffs_base__range_ie_u64 s) const {\n  return wuffs_base__range_ie_u64__contains_range(this, s);\n}\n\ninline uint64_t  //\nwuffs_base__range_ie_u64::length() const {\n  return wuffs_base__range_ie_u64__length(this);\n}\n\n#endif  // __cplusplus\n\n" +
 	"" +
-	"// --------\n\n// wuffs_base__rect_ii_u32 is a rectangle (a 2-dimensional range) on the\n// integer grid. The \"ii\" means that the bounds are inclusive on the low end\n// and inclusive on the high end. It contains all points (x, y) such that\n// ((min_incl_x <= x) && (x <= max_incl_x)) and likewise for y.\n//\n// It is valid for min > max, in which case the rectangle is empty. There are\n// multiple representations of an empty rectangle.\n//\n// The X and Y axes increase right and down.\ntypedef struct wuffs_base__rect_ii_u32__struct {\n  uint32_t min_incl_x;\n  uint32_t min_incl_y;\n  uint32_t max_incl_x;\n  uint32_t max_incl_y;\n\n#ifdef __cplusplus\n  inline bool is_empty();\n  inline bool equals(wuffs_base__rect_ii_u32__struct s);\n  inline wuffs_base__rect_ii_u32__struct intersect(\n      wuffs_base__rect_ii_u32__struct s);\n  inline wuffs_base__rect_ii_u32__struct unite(\n      wuffs_base__rect_ii_u32__struct s);\n  inline bool contains(uint32_t x, uint32_t y);\n  inline bool contains_rect(wuffs_base__rect_ii_u32__struct s);\n#en" +
-	"dif  // __cplusplus\n\n} wuffs_base__rect_ii_u32;\n\nstatic inline wuffs_base__rect_ii_u32  //\nwuffs_base__make_rect_ii_u32(uint32_t min_incl_x,\n                             uint32_t min_incl_y,\n                             uint32_t max_incl_x,\n                             uint32_t max_incl_y) {\n  wuffs_base__rect_ii_u32 ret;\n  ret.min_incl_x = min_incl_x;\n  ret.min_incl_y = min_incl_y;\n  ret.max_incl_x = max_incl_x;\n  ret.max_incl_y = max_incl_y;\n  return ret;\n}\n\nstatic inline bool  //\nwuffs_base__rect_ii_u32__is_empty(wuffs_base__rect_ii_u32* r) {\n  return (r->min_incl_x > r->max_incl_x) || (r->min_incl_y > r->max_incl_y);\n}\n\nstatic inline bool  //\nwuffs_base__rect_ii_u32__equals(wuffs_base__rect_ii_u32* r,\n                                wuffs_base__rect_ii_u32 s) {\n  return (r->min_incl_x == s.min_incl_x && r->min_incl_y == s.min_incl_y &&\n          r->max_incl_x == s.max_incl_x && r->max_incl_y == s.max_incl_y) ||\n         (wuffs_base__rect_ii_u32__is_empty(r) &&\n          wuffs_base__rect_ii_u32__is_empty(&" +
-	"s));\n}\n\nstatic inline wuffs_base__rect_ii_u32  //\nwuffs_base__rect_ii_u32__intersect(wuffs_base__rect_ii_u32* r,\n                                   wuffs_base__rect_ii_u32 s) {\n  wuffs_base__rect_ii_u32 t;\n  t.min_incl_x = wuffs_base__u32__max(r->min_incl_x, s.min_incl_x);\n  t.min_incl_y = wuffs_base__u32__max(r->min_incl_y, s.min_incl_y);\n  t.max_incl_x = wuffs_base__u32__min(r->max_incl_x, s.max_incl_x);\n  t.max_incl_y = wuffs_base__u32__min(r->max_incl_y, s.max_incl_y);\n  return t;\n}\n\nstatic inline wuffs_base__rect_ii_u32  //\nwuffs_base__rect_ii_u32__unite(wuffs_base__rect_ii_u32* r,\n                               wuffs_base__rect_ii_u32 s) {\n  if (wuffs_base__rect_ii_u32__is_empty(r)) {\n    return s;\n  }\n  if (wuffs_base__rect_ii_u32__is_empty(&s)) {\n    return *r;\n  }\n  wuffs_base__rect_ii_u32 t;\n  t.min_incl_x = wuffs_base__u32__min(r->min_incl_x, s.min_incl_x);\n  t.min_incl_y = wuffs_base__u32__min(r->min_incl_y, s.min_incl_y);\n  t.max_incl_x = wuffs_base__u32__max(r->max_incl_x, s.max_incl_x);\n  t.max" +
-	"_incl_y = wuffs_base__u32__max(r->max_incl_y, s.max_incl_y);\n  return t;\n}\n\nstatic inline bool  //\nwuffs_base__rect_ii_u32__contains(wuffs_base__rect_ii_u32* r,\n                                  uint32_t x,\n                                  uint32_t y) {\n  return (r->min_incl_x <= x) && (x <= r->max_incl_x) && (r->min_incl_y <= y) &&\n         (y <= r->max_incl_y);\n}\n\nstatic inline bool  //\nwuffs_base__rect_ii_u32__contains_rect(wuffs_base__rect_ii_u32* r,\n                                       wuffs_base__rect_ii_u32 s) {\n  return wuffs_base__rect_ii_u32__equals(\n      &s, wuffs_base__rect_ii_u32__intersect(r, s));\n}\n\n#ifdef __cplusplus\n\ninline bool  //\nwuffs_base__rect_ii_u32::is_empty() {\n  return wuffs_base__rect_ii_u32__is_empty(this);\n}\n\ninline bool  //\nwuffs_base__rect_ii_u32::equals(wuffs_base__rect_ii_u32 s) {\n  return wuffs_base__rect_ii_u32__equals(this, s);\n}\n\ninline wuffs_base__rect_ii_u32  //\nwuffs_base__rect_ii_u32::intersect(wuffs_base__rect_ii_u32 s) {\n  return wuffs_base__rect_ii_u32__interse" +
-	"ct(this, s);\n}\n\ninline wuffs_base__rect_ii_u32  //\nwuffs_base__rect_ii_u32::unite(wuffs_base__rect_ii_u32 s) {\n  return wuffs_base__rect_ii_u32__unite(this, s);\n}\n\ninline bool  //\nwuffs_base__rect_ii_u32::contains(uint32_t x, uint32_t y) {\n  return wuffs_base__rect_ii_u32__contains(this, x, y);\n}\n\ninline bool  //\nwuffs_base__rect_ii_u32::contains_rect(wuffs_base__rect_ii_u32 s) {\n  return wuffs_base__rect_ii_u32__contains_rect(this, s);\n}\n\n#endif  // __cplusplus\n\n" +
+	"// --------\n\n// wuffs_base__rect_ii_u32 is a rectangle (a 2-dimensional range) on the\n// integer grid. The \"ii\" means that the bounds are inclusive on the low end\n// and inclusive on the high end. It contains all points (x, y) such that\n// ((min_incl_x <= x) && (x <= max_incl_x)) and likewise for y.\n//\n// It is valid for min > max, in which case the rectangle is empty. There are\n// multiple representations of an empty rectangle.\n//\n// The X and Y axes increase right and down.\ntypedef struct wuffs_base__rect_ii_u32__struct {\n  uint32_t min_incl_x;\n  uint32_t min_incl_y;\n  uint32_t max_incl_x;\n  uint32_t max_incl_y;\n\n#ifdef __cplusplus\n  inline bool is_empty() const;\n  inline bool equals(wuffs_base__rect_ii_u32__struct s) const;\n  inline wuffs_base__rect_ii_u32__struct intersect(\n      wuffs_base__rect_ii_u32__struct s) const;\n  inline wuffs_base__rect_ii_u32__struct unite(\n      wuffs_base__rect_ii_u32__struct s) const;\n  inline bool contains(uint32_t x, uint32_t y) const;\n  inline bool contains_rect(wuffs_bas" +
+	"e__rect_ii_u32__struct s) const;\n#endif  // __cplusplus\n\n} wuffs_base__rect_ii_u32;\n\nstatic inline wuffs_base__rect_ii_u32  //\nwuffs_base__make_rect_ii_u32(uint32_t min_incl_x,\n                             uint32_t min_incl_y,\n                             uint32_t max_incl_x,\n                             uint32_t max_incl_y) {\n  wuffs_base__rect_ii_u32 ret;\n  ret.min_incl_x = min_incl_x;\n  ret.min_incl_y = min_incl_y;\n  ret.max_incl_x = max_incl_x;\n  ret.max_incl_y = max_incl_y;\n  return ret;\n}\n\nstatic inline bool  //\nwuffs_base__rect_ii_u32__is_empty(const wuffs_base__rect_ii_u32* r) {\n  return (r->min_incl_x > r->max_incl_x) || (r->min_incl_y > r->max_incl_y);\n}\n\nstatic inline bool  //\nwuffs_base__rect_ii_u32__equals(const wuffs_base__rect_ii_u32* r,\n                                wuffs_base__rect_ii_u32 s) {\n  return (r->min_incl_x == s.min_incl_x && r->min_incl_y == s.min_incl_y &&\n          r->max_incl_x == s.max_incl_x && r->max_incl_y == s.max_incl_y) ||\n         (wuffs_base__rect_ii_u32__is_empty(r) " +
+	"&&\n          wuffs_base__rect_ii_u32__is_empty(&s));\n}\n\nstatic inline wuffs_base__rect_ii_u32  //\nwuffs_base__rect_ii_u32__intersect(const wuffs_base__rect_ii_u32* r,\n                                   wuffs_base__rect_ii_u32 s) {\n  wuffs_base__rect_ii_u32 t;\n  t.min_incl_x = wuffs_base__u32__max(r->min_incl_x, s.min_incl_x);\n  t.min_incl_y = wuffs_base__u32__max(r->min_incl_y, s.min_incl_y);\n  t.max_incl_x = wuffs_base__u32__min(r->max_incl_x, s.max_incl_x);\n  t.max_incl_y = wuffs_base__u32__min(r->max_incl_y, s.max_incl_y);\n  return t;\n}\n\nstatic inline wuffs_base__rect_ii_u32  //\nwuffs_base__rect_ii_u32__unite(const wuffs_base__rect_ii_u32* r,\n                               wuffs_base__rect_ii_u32 s) {\n  if (wuffs_base__rect_ii_u32__is_empty(r)) {\n    return s;\n  }\n  if (wuffs_base__rect_ii_u32__is_empty(&s)) {\n    return *r;\n  }\n  wuffs_base__rect_ii_u32 t;\n  t.min_incl_x = wuffs_base__u32__min(r->min_incl_x, s.min_incl_x);\n  t.min_incl_y = wuffs_base__u32__min(r->min_incl_y, s.min_incl_y);\n  t.max_incl_x " +
+	"= wuffs_base__u32__max(r->max_incl_x, s.max_incl_x);\n  t.max_incl_y = wuffs_base__u32__max(r->max_incl_y, s.max_incl_y);\n  return t;\n}\n\nstatic inline bool  //\nwuffs_base__rect_ii_u32__contains(const wuffs_base__rect_ii_u32* r,\n                                  uint32_t x,\n                                  uint32_t y) {\n  return (r->min_incl_x <= x) && (x <= r->max_incl_x) && (r->min_incl_y <= y) &&\n         (y <= r->max_incl_y);\n}\n\nstatic inline bool  //\nwuffs_base__rect_ii_u32__contains_rect(const wuffs_base__rect_ii_u32* r,\n                                       wuffs_base__rect_ii_u32 s) {\n  return wuffs_base__rect_ii_u32__equals(\n      &s, wuffs_base__rect_ii_u32__intersect(r, s));\n}\n\n#ifdef __cplusplus\n\ninline bool  //\nwuffs_base__rect_ii_u32::is_empty() const {\n  return wuffs_base__rect_ii_u32__is_empty(this);\n}\n\ninline bool  //\nwuffs_base__rect_ii_u32::equals(wuffs_base__rect_ii_u32 s) const {\n  return wuffs_base__rect_ii_u32__equals(this, s);\n}\n\ninline wuffs_base__rect_ii_u32  //\nwuffs_base__rect_ii_u" +
+	"32::intersect(wuffs_base__rect_ii_u32 s) const {\n  return wuffs_base__rect_ii_u32__intersect(this, s);\n}\n\ninline wuffs_base__rect_ii_u32  //\nwuffs_base__rect_ii_u32::unite(wuffs_base__rect_ii_u32 s) const {\n  return wuffs_base__rect_ii_u32__unite(this, s);\n}\n\ninline bool  //\nwuffs_base__rect_ii_u32::contains(uint32_t x, uint32_t y) const {\n  return wuffs_base__rect_ii_u32__contains(this, x, y);\n}\n\ninline bool  //\nwuffs_base__rect_ii_u32::contains_rect(wuffs_base__rect_ii_u32 s) const {\n  return wuffs_base__rect_ii_u32__contains_rect(this, s);\n}\n\n#endif  // __cplusplus\n\n" +
 	"" +
-	"// --------\n\n// wuffs_base__rect_ie_u32 is a rectangle (a 2-dimensional range) on the\n// integer grid. The \"ie\" means that the bounds are inclusive on the low end\n// and exclusive on the high end. It contains all points (x, y) such that\n// ((min_incl_x <= x) && (x < max_excl_x)) and likewise for y.\n//\n// It is valid for min >= max, in which case the rectangle is empty. There are\n// multiple representations of an empty rectangle, including a value with all\n// fields zero.\n//\n// The X and Y axes increase right and down.\ntypedef struct wuffs_base__rect_ie_u32__struct {\n  uint32_t min_incl_x;\n  uint32_t min_incl_y;\n  uint32_t max_excl_x;\n  uint32_t max_excl_y;\n\n#ifdef __cplusplus\n  inline bool is_empty();\n  inline bool equals(wuffs_base__rect_ie_u32__struct s);\n  inline wuffs_base__rect_ie_u32__struct intersect(\n      wuffs_base__rect_ie_u32__struct s);\n  inline wuffs_base__rect_ie_u32__struct unite(\n      wuffs_base__rect_ie_u32__struct s);\n  inline bool contains(uint32_t x, uint32_t y);\n  inline bool contains_r" +
-	"ect(wuffs_base__rect_ie_u32__struct s);\n  inline uint32_t width();\n  inline uint32_t height();\n#endif  // __cplusplus\n\n} wuffs_base__rect_ie_u32;\n\nstatic inline wuffs_base__rect_ie_u32  //\nwuffs_base__make_rect_ie_u32(uint32_t min_incl_x,\n                             uint32_t min_incl_y,\n                             uint32_t max_excl_x,\n                             uint32_t max_excl_y) {\n  wuffs_base__rect_ie_u32 ret;\n  ret.min_incl_x = min_incl_x;\n  ret.min_incl_y = min_incl_y;\n  ret.max_excl_x = max_excl_x;\n  ret.max_excl_y = max_excl_y;\n  return ret;\n}\n\nstatic inline bool  //\nwuffs_base__rect_ie_u32__is_empty(wuffs_base__rect_ie_u32* r) {\n  return (r->min_incl_x >= r->max_excl_x) || (r->min_incl_y >= r->max_excl_y);\n}\n\nstatic inline bool  //\nwuffs_base__rect_ie_u32__equals(wuffs_base__rect_ie_u32* r,\n                                wuffs_base__rect_ie_u32 s) {\n  return (r->min_incl_x == s.min_incl_x && r->min_incl_y == s.min_incl_y &&\n          r->max_excl_x == s.max_excl_x && r->max_excl_y == s.max_excl_y" +
-	") ||\n         (wuffs_base__rect_ie_u32__is_empty(r) &&\n          wuffs_base__rect_ie_u32__is_empty(&s));\n}\n\nstatic inline wuffs_base__rect_ie_u32  //\nwuffs_base__rect_ie_u32__intersect(wuffs_base__rect_ie_u32* r,\n                                   wuffs_base__rect_ie_u32 s) {\n  wuffs_base__rect_ie_u32 t;\n  t.min_incl_x = wuffs_base__u32__max(r->min_incl_x, s.min_incl_x);\n  t.min_incl_y = wuffs_base__u32__max(r->min_incl_y, s.min_incl_y);\n  t.max_excl_x = wuffs_base__u32__min(r->max_excl_x, s.max_excl_x);\n  t.max_excl_y = wuffs_base__u32__min(r->max_excl_y, s.max_excl_y);\n  return t;\n}\n\nstatic inline wuffs_base__rect_ie_u32  //\nwuffs_base__rect_ie_u32__unite(wuffs_base__rect_ie_u32* r,\n                               wuffs_base__rect_ie_u32 s) {\n  if (wuffs_base__rect_ie_u32__is_empty(r)) {\n    return s;\n  }\n  if (wuffs_base__rect_ie_u32__is_empty(&s)) {\n    return *r;\n  }\n  wuffs_base__rect_ie_u32 t;\n  t.min_incl_x = wuffs_base__u32__min(r->min_incl_x, s.min_incl_x);\n  t.min_incl_y = wuffs_base__u32__min(r->mi" +
-	"n_incl_y, s.min_incl_y);\n  t.max_excl_x = wuffs_base__u32__max(r->max_excl_x, s.max_excl_x);\n  t.max_excl_y = wuffs_base__u32__max(r->max_excl_y, s.max_excl_y);\n  return t;\n}\n\nstatic inline bool  //\nwuffs_base__rect_ie_u32__contains(wuffs_base__rect_ie_u32* r,\n                                  uint32_t x,\n                                  uint32_t y) {\n  return (r->min_incl_x <= x) && (x < r->max_excl_x) && (r->min_incl_y <= y) &&\n         (y < r->max_excl_y);\n}\n\nstatic inline bool  //\nwuffs_base__rect_ie_u32__contains_rect(wuffs_base__rect_ie_u32* r,\n                                       wuffs_base__rect_ie_u32 s) {\n  return wuffs_base__rect_ie_u32__equals(\n      &s, wuffs_base__rect_ie_u32__intersect(r, s));\n}\n\nstatic inline uint32_t  //\nwuffs_base__rect_ie_u32__width(wuffs_base__rect_ie_u32* r) {\n  return wuffs_base__u32__sat_sub(r->max_excl_x, r->min_incl_x);\n}\n\nstatic inline uint32_t  //\nwuffs_base__rect_ie_u32__height(wuffs_base__rect_ie_u32* r) {\n  return wuffs_base__u32__sat_sub(r->max_excl_y, r->min" +
-	"_incl_y);\n}\n\n#ifdef __cplusplus\n\ninline bool  //\nwuffs_base__rect_ie_u32::is_empty() {\n  return wuffs_base__rect_ie_u32__is_empty(this);\n}\n\ninline bool  //\nwuffs_base__rect_ie_u32::equals(wuffs_base__rect_ie_u32 s) {\n  return wuffs_base__rect_ie_u32__equals(this, s);\n}\n\ninline wuffs_base__rect_ie_u32  //\nwuffs_base__rect_ie_u32::intersect(wuffs_base__rect_ie_u32 s) {\n  return wuffs_base__rect_ie_u32__intersect(this, s);\n}\n\ninline wuffs_base__rect_ie_u32  //\nwuffs_base__rect_ie_u32::unite(wuffs_base__rect_ie_u32 s) {\n  return wuffs_base__rect_ie_u32__unite(this, s);\n}\n\ninline bool  //\nwuffs_base__rect_ie_u32::contains(uint32_t x, uint32_t y) {\n  return wuffs_base__rect_ie_u32__contains(this, x, y);\n}\n\ninline bool  //\nwuffs_base__rect_ie_u32::contains_rect(wuffs_base__rect_ie_u32 s) {\n  return wuffs_base__rect_ie_u32__contains_rect(this, s);\n}\n\ninline uint32_t  //\nwuffs_base__rect_ie_u32::width() {\n  return wuffs_base__rect_ie_u32__width(this);\n}\n\ninline uint32_t  //\nwuffs_base__rect_ie_u32::height() {\n  return" +
-	" wuffs_base__rect_ie_u32__height(this);\n}\n\n#endif  // __cplusplus\n" +
+	"// --------\n\n// wuffs_base__rect_ie_u32 is a rectangle (a 2-dimensional range) on the\n// integer grid. The \"ie\" means that the bounds are inclusive on the low end\n// and exclusive on the high end. It contains all points (x, y) such that\n// ((min_incl_x <= x) && (x < max_excl_x)) and likewise for y.\n//\n// It is valid for min >= max, in which case the rectangle is empty. There are\n// multiple representations of an empty rectangle, including a value with all\n// fields zero.\n//\n// The X and Y axes increase right and down.\ntypedef struct wuffs_base__rect_ie_u32__struct {\n  uint32_t min_incl_x;\n  uint32_t min_incl_y;\n  uint32_t max_excl_x;\n  uint32_t max_excl_y;\n\n#ifdef __cplusplus\n  inline bool is_empty() const;\n  inline bool equals(wuffs_base__rect_ie_u32__struct s) const;\n  inline wuffs_base__rect_ie_u32__struct intersect(\n      wuffs_base__rect_ie_u32__struct s) const;\n  inline wuffs_base__rect_ie_u32__struct unite(\n      wuffs_base__rect_ie_u32__struct s) const;\n  inline bool contains(uint32_t x, uint32_t y) c" +
+	"onst;\n  inline bool contains_rect(wuffs_base__rect_ie_u32__struct s) const;\n  inline uint32_t width() const;\n  inline uint32_t height() const;\n#endif  // __cplusplus\n\n} wuffs_base__rect_ie_u32;\n\nstatic inline wuffs_base__rect_ie_u32  //\nwuffs_base__make_rect_ie_u32(uint32_t min_incl_x,\n                             uint32_t min_incl_y,\n                             uint32_t max_excl_x,\n                             uint32_t max_excl_y) {\n  wuffs_base__rect_ie_u32 ret;\n  ret.min_incl_x = min_incl_x;\n  ret.min_incl_y = min_incl_y;\n  ret.max_excl_x = max_excl_x;\n  ret.max_excl_y = max_excl_y;\n  return ret;\n}\n\nstatic inline bool  //\nwuffs_base__rect_ie_u32__is_empty(const wuffs_base__rect_ie_u32* r) {\n  return (r->min_incl_x >= r->max_excl_x) || (r->min_incl_y >= r->max_excl_y);\n}\n\nstatic inline bool  //\nwuffs_base__rect_ie_u32__equals(const wuffs_base__rect_ie_u32* r,\n                                wuffs_base__rect_ie_u32 s) {\n  return (r->min_incl_x == s.min_incl_x && r->min_incl_y == s.min_incl_y &&\n          r-" +
+	">max_excl_x == s.max_excl_x && r->max_excl_y == s.max_excl_y) ||\n         (wuffs_base__rect_ie_u32__is_empty(r) &&\n          wuffs_base__rect_ie_u32__is_empty(&s));\n}\n\nstatic inline wuffs_base__rect_ie_u32  //\nwuffs_base__rect_ie_u32__intersect(const wuffs_base__rect_ie_u32* r,\n                                   wuffs_base__rect_ie_u32 s) {\n  wuffs_base__rect_ie_u32 t;\n  t.min_incl_x = wuffs_base__u32__max(r->min_incl_x, s.min_incl_x);\n  t.min_incl_y = wuffs_base__u32__max(r->min_incl_y, s.min_incl_y);\n  t.max_excl_x = wuffs_base__u32__min(r->max_excl_x, s.max_excl_x);\n  t.max_excl_y = wuffs_base__u32__min(r->max_excl_y, s.max_excl_y);\n  return t;\n}\n\nstatic inline wuffs_base__rect_ie_u32  //\nwuffs_base__rect_ie_u32__unite(const wuffs_base__rect_ie_u32* r,\n                               wuffs_base__rect_ie_u32 s) {\n  if (wuffs_base__rect_ie_u32__is_empty(r)) {\n    return s;\n  }\n  if (wuffs_base__rect_ie_u32__is_empty(&s)) {\n    return *r;\n  }\n  wuffs_base__rect_ie_u32 t;\n  t.min_incl_x = wuffs_base__u32__min(r" +
+	"->min_incl_x, s.min_incl_x);\n  t.min_incl_y = wuffs_base__u32__min(r->min_incl_y, s.min_incl_y);\n  t.max_excl_x = wuffs_base__u32__max(r->max_excl_x, s.max_excl_x);\n  t.max_excl_y = wuffs_base__u32__max(r->max_excl_y, s.max_excl_y);\n  return t;\n}\n\nstatic inline bool  //\nwuffs_base__rect_ie_u32__contains(const wuffs_base__rect_ie_u32* r,\n                                  uint32_t x,\n                                  uint32_t y) {\n  return (r->min_incl_x <= x) && (x < r->max_excl_x) && (r->min_incl_y <= y) &&\n         (y < r->max_excl_y);\n}\n\nstatic inline bool  //\nwuffs_base__rect_ie_u32__contains_rect(const wuffs_base__rect_ie_u32* r,\n                                       wuffs_base__rect_ie_u32 s) {\n  return wuffs_base__rect_ie_u32__equals(\n      &s, wuffs_base__rect_ie_u32__intersect(r, s));\n}\n\nstatic inline uint32_t  //\nwuffs_base__rect_ie_u32__width(const wuffs_base__rect_ie_u32* r) {\n  return wuffs_base__u32__sat_sub(r->max_excl_x, r->min_incl_x);\n}\n\nstatic inline uint32_t  //\nwuffs_base__rect_ie_u32__he" +
+	"ight(const wuffs_base__rect_ie_u32* r) {\n  return wuffs_base__u32__sat_sub(r->max_excl_y, r->min_incl_y);\n}\n\n#ifdef __cplusplus\n\ninline bool  //\nwuffs_base__rect_ie_u32::is_empty() const {\n  return wuffs_base__rect_ie_u32__is_empty(this);\n}\n\ninline bool  //\nwuffs_base__rect_ie_u32::equals(wuffs_base__rect_ie_u32 s) const {\n  return wuffs_base__rect_ie_u32__equals(this, s);\n}\n\ninline wuffs_base__rect_ie_u32  //\nwuffs_base__rect_ie_u32::intersect(wuffs_base__rect_ie_u32 s) const {\n  return wuffs_base__rect_ie_u32__intersect(this, s);\n}\n\ninline wuffs_base__rect_ie_u32  //\nwuffs_base__rect_ie_u32::unite(wuffs_base__rect_ie_u32 s) const {\n  return wuffs_base__rect_ie_u32__unite(this, s);\n}\n\ninline bool  //\nwuffs_base__rect_ie_u32::contains(uint32_t x, uint32_t y) const {\n  return wuffs_base__rect_ie_u32__contains(this, x, y);\n}\n\ninline bool  //\nwuffs_base__rect_ie_u32::contains_rect(wuffs_base__rect_ie_u32 s) const {\n  return wuffs_base__rect_ie_u32__contains_rect(this, s);\n}\n\ninline uint32_t  //\nwuffs_base__rect_" +
+	"ie_u32::width() const {\n  return wuffs_base__rect_ie_u32__width(this);\n}\n\ninline uint32_t  //\nwuffs_base__rect_ie_u32::height() const {\n  return wuffs_base__rect_ie_u32__height(this);\n}\n\n#endif  // __cplusplus\n" +
 	""
 
 const baseCopyright = "" +
diff --git a/internal/cgen/expr.go b/internal/cgen/expr.go
index 3315531..4a326c8 100644
--- a/internal/cgen/expr.go
+++ b/internal/cgen/expr.go
@@ -461,8 +461,8 @@
 	t.IDU32:      "uint32_t",
 	t.IDU64:      "uint64_t",
 	t.IDBool:     "bool",
-	t.IDIOReader: "wuffs_base__io_reader",
-	t.IDIOWriter: "wuffs_base__io_writer",
+	t.IDIOReader: "wuffs_base__io_buffer*",
+	t.IDIOWriter: "wuffs_base__io_buffer*",
 }
 
 const noSuchCOperator = " no_such_C_operator "
diff --git a/internal/cgen/func.go b/internal/cgen/func.go
index 1030f78..3f8daf5 100644
--- a/internal/cgen/func.go
+++ b/internal/cgen/func.go
@@ -368,10 +368,12 @@
 
 		b.writes("goto suspend;suspend:") // The goto avoids the "unused label" warning.
 
-		b.printf("self->private_impl.%s%s[0] = coro_susp_point;\n",
+		b.printf("self->private_impl.%s%s[0] = "+
+			"wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;\n",
 			pPrefix, g.currFunk.astFunc.FuncName().Str(g.tm))
 		if g.currFunk.astFunc.Public() {
-			b.printf("self->private_impl.active_coroutine = %d;\n", g.currFunk.coroID)
+			b.printf("self->private_impl.active_coroutine = "+
+				"wuffs_base__status__is_suspension(status) ? %d : 0;\n", g.currFunk.coroID)
 		}
 		if err := g.writeResumeSuspend(b, &g.currFunk, true); err != nil {
 			return err
@@ -418,13 +420,10 @@
 	for _, o := range n.In().Fields() {
 		o := o.AsField()
 		oTyp := o.XType()
-		if oTyp.Decorator() != t.IDPtr && !oTyp.IsRefined() {
-			// TODO: Also check elements, for array-typed arguments.
-			continue
-		}
 
+		// TODO: Also check elements, for array-typed arguments.
 		switch {
-		case oTyp.Decorator() == t.IDPtr:
+		case oTyp.IsIOType() || (oTyp.Decorator() == t.IDPtr):
 			checks = append(checks, fmt.Sprintf("!%s%s", aPrefix, o.Name().Str(g.tm)))
 
 		case oTyp.IsRefined():
diff --git a/internal/cgen/resume.go b/internal/cgen/resume.go
index 09bf05c..1ade456 100644
--- a/internal/cgen/resume.go
+++ b/internal/cgen/resume.go
@@ -236,8 +236,14 @@
 }
 
 func (h *resumabilityHelper) doAssign(r resumabilities, n *a.Assign, depth uint32) error {
-	if err := h.doExpr(r, n.RHS()); err != nil {
-		return err
+	if n.Operator() == t.IDEqQuestion {
+		if err := h.doExpr1(r, n.RHS(), subExprFilterNone, 0); err != nil {
+			return err
+		}
+	} else {
+		if err := h.doExpr(r, n.RHS()); err != nil {
+			return err
+		}
 	}
 
 	if n.LHS() == nil {
@@ -302,12 +308,14 @@
 				}
 				return nil
 			}
+			panic("TODO: unreachable; delete")
 			processOnlySubExprs = true
 
 		case subExprFilterAfterCoroutine:
 			if n.Effect().Coroutine() {
 				return nil
 			}
+			panic("TODO: unreachable; delete")
 		}
 	}
 
diff --git a/internal/cgen/statement.go b/internal/cgen/statement.go
index 6fca3e8..7a57e9d 100644
--- a/internal/cgen/statement.go
+++ b/internal/cgen/statement.go
@@ -267,8 +267,8 @@
 			cTyp = "writer"
 		}
 		name := e.Ident().Str(g.tm)
-		b.printf("wuffs_base__io_%s %s%d_%s%s = %s%s;\n",
-			cTyp, oPrefix, ioBindNum, prefix, name, prefix, name)
+		b.printf("wuffs_base__io_buffer* %s%d_%s%s = %s%s;\n",
+			oPrefix, ioBindNum, prefix, name, prefix, name)
 
 		// TODO: save / restore all iop vars, not just for local IO vars? How
 		// does this work if the io_bind body advances these pointers, either
@@ -277,25 +277,24 @@
 			b.printf("uint8_t *%s%d_%s%s%s = %s%s%s;\n",
 				oPrefix, ioBindNum, iopPrefix, prefix, name, iopPrefix, prefix, name)
 			b.printf("uint8_t *%s%d_%s%s%s = %s%s%s;\n",
+				oPrefix, ioBindNum, io0Prefix, prefix, name, io0Prefix, prefix, name)
+			b.printf("uint8_t *%s%d_%s%s%s = %s%s%s;\n",
 				oPrefix, ioBindNum, io1Prefix, prefix, name, io1Prefix, prefix, name)
+			b.printf("uint8_t *%s%d_%s%s%s = %s%s%s;\n",
+				oPrefix, ioBindNum, io2Prefix, prefix, name, io2Prefix, prefix, name)
 		}
 
 		if n.Keyword() == t.IDIOBind {
-			b.printf("wuffs_base__io_%s__set(&%s%s, &u_%s, &iop_%s%s, &io1_%s%s,",
-				cTyp, prefix, name, name, prefix, name, prefix, name)
+			b.printf("%s%s = wuffs_base__io_%s__set(&%s%s, &%s%s%s, &%s%s%s, &%s%s%s, &%s%s%s,",
+				prefix, name, cTyp, uPrefix, name, iopPrefix, prefix, name,
+				io0Prefix, prefix, name, io1Prefix, prefix, name, io2Prefix, prefix, name)
 			if err := g.writeExpr(b, n.Arg1(), 0); err != nil {
 				return err
 			}
 			b.writes(");\n")
 
 		} else {
-			// TODO: restrict (in the type checker or parser) that e is
-			// args.foo?
-			b.printf("wuffs_base__io_%s__set_limit(&%s%s, iop_%s%s,\n", cTyp, prefix, name, prefix, name)
-			if err := g.writeExpr(b, n.Arg1(), 0); err != nil {
-				return err
-			}
-			b.writes(");\n")
+			return fmt.Errorf("TODO: implement io_limit (or remove it from the parser)")
 		}
 	}
 
@@ -318,7 +317,11 @@
 			b.printf("%s%s%s = %s%d_%s%s%s;\n",
 				iopPrefix, prefix, name, oPrefix, ioBindNum, iopPrefix, prefix, name)
 			b.printf("%s%s%s = %s%d_%s%s%s;\n",
+				io0Prefix, prefix, name, oPrefix, ioBindNum, io0Prefix, prefix, name)
+			b.printf("%s%s%s = %s%d_%s%s%s;\n",
 				io1Prefix, prefix, name, oPrefix, ioBindNum, io1Prefix, prefix, name)
+			b.printf("%s%s%s = %s%d_%s%s%s;\n",
+				io2Prefix, prefix, name, oPrefix, ioBindNum, io2Prefix, prefix, name)
 		}
 	}
 	b.writes("}\n")
diff --git a/internal/cgen/var.go b/internal/cgen/var.go
index 5a2cb50..9263d1c 100644
--- a/internal/cgen/var.go
+++ b/internal/cgen/var.go
@@ -95,37 +95,33 @@
 	}
 
 	preName := prefix + name.Str(g.tm)
-	i0, i1 := "meta.ri", "meta.wi"
+	i1, i2 := "meta.ri", "meta.wi"
 	if typ.QID()[1] == t.IDIOWriter {
-		i0, i1 = "meta.wi", "data.len"
+		i1, i2 = "meta.wi", "data.len"
 	}
 
 	if header {
 		b.printf("uint8_t* %s%s = NULL;", iopPrefix, preName)
 		b.printf("uint8_t* %s%s WUFFS_BASE__POTENTIALLY_UNUSED = NULL;", io0Prefix, preName)
 		b.printf("uint8_t* %s%s WUFFS_BASE__POTENTIALLY_UNUSED = NULL;", io1Prefix, preName)
+		b.printf("uint8_t* %s%s WUFFS_BASE__POTENTIALLY_UNUSED = NULL;", io2Prefix, preName)
 	}
 
-	b.printf("if (%s.private_impl.buf) {", preName)
-
-	b.printf("%s%s = %s.private_impl.buf->data.ptr + %s.private_impl.buf->%s;",
-		iopPrefix, preName, preName, preName, i0)
+	b.printf("if (%s) {", preName)
 
 	if header {
-		b.printf("if (!%s.private_impl.mark) {", preName)
-		b.printf("%s.private_impl.mark = %s%s;", preName, iopPrefix, preName)
-		b.printf("%s.private_impl.limit = %s.private_impl.buf->data.ptr + %s.private_impl.buf->%s;",
-			preName, preName, preName, i1)
-		b.printf("}\n")
+		b.printf("%s%s = %s->data.ptr;", io0Prefix, preName, preName)
+		b.printf("%s%s = %s%s + %s->%s;", io1Prefix, preName, io0Prefix, preName, preName, i1)
+		b.printf("%s%s = %s%s;", iopPrefix, preName, io1Prefix, preName)
+		b.printf("%s%s = %s%s + %s->%s;", io2Prefix, preName, io0Prefix, preName, preName, i2)
 
 		if typ.QID()[1] == t.IDIOWriter {
-			b.printf("if (%s.private_impl.buf->meta.closed) {", preName)
-			b.printf("%s.private_impl.limit = %s%s;", preName, iopPrefix, preName)
+			b.printf("if (%s->meta.closed) {", preName)
+			b.printf("%s%s = %s%s;", io2Prefix, preName, iopPrefix, preName)
 			b.printf("}\n")
 		}
-
-		b.printf("%s%s = %s.private_impl.mark;", io0Prefix, preName, preName)
-		b.printf("%s%s = %s.private_impl.limit;", io1Prefix, preName, preName)
+	} else {
+		b.printf("%s%s = %s->data.ptr + %s->%s;", iopPrefix, preName, preName, preName, i1)
 	}
 
 	b.printf("}\n")
@@ -158,15 +154,13 @@
 	}
 
 	preName := prefix + name.Str(g.tm)
-	i0 := "ri"
+	index := "ri"
 	if typ.QID()[1] == t.IDIOWriter {
-		i0 = "wi"
+		index = "wi"
 	}
 
-	b.printf("if (%s.private_impl.buf) {", preName)
-	b.printf("%s.private_impl.buf->meta.%s = ((size_t)(%s%s - %s.private_impl.buf->data.ptr));",
-		preName, i0, iopPrefix, preName, preName)
-	b.printf("}\n")
+	b.printf("if (%s) { %s->meta.%s = ((size_t)(%s%s - %s->data.ptr)); }\n",
+		preName, preName, index, iopPrefix, preName, preName)
 	return nil
 }
 
@@ -278,6 +272,11 @@
 		}
 
 		name := n.Name().Str(g.tm)
+
+		if typ.IsIOType() {
+			b.printf("wuffs_base__io_buffer %s%s = wuffs_base__null_io_buffer();\n", uPrefix, name)
+		}
+
 		if err := g.writeCTypeName(b, typ, vPrefix, name); err != nil {
 			return err
 		}
@@ -290,22 +289,16 @@
 		} else if typ.IsStatus() {
 			b.writes(" = NULL;\n")
 		} else if typ.IsIOType() {
-			typName := "reader"
-			if typ.QID()[1] == t.IDIOWriter {
-				typName = "writer"
-			}
-			b.printf(" = wuffs_base__null_io_%s();\n", typName)
+			b.printf(" = &%s%s;\n", uPrefix, name)
 		} else {
 			b.writes(" = {0};\n")
 		}
 
 		if typ.IsIOType() {
-			b.printf("wuffs_base__io_buffer %s%s WUFFS_BASE__POTENTIALLY_UNUSED = "+
-				"wuffs_base__null_io_buffer();\n", uPrefix, name)
-			preName := vPrefix + name
-			// TODO: io0_etc variables?
-			b.printf("uint8_t* %s%s WUFFS_BASE__POTENTIALLY_UNUSED = NULL;\n", iopPrefix, preName)
-			b.printf("uint8_t* %s%s WUFFS_BASE__POTENTIALLY_UNUSED = NULL;\n", io1Prefix, preName)
+			b.printf("uint8_t* %s%s%s WUFFS_BASE__POTENTIALLY_UNUSED = NULL;\n", iopPrefix, vPrefix, name)
+			b.printf("uint8_t* %s%s%s WUFFS_BASE__POTENTIALLY_UNUSED = NULL;\n", io0Prefix, vPrefix, name)
+			b.printf("uint8_t* %s%s%s WUFFS_BASE__POTENTIALLY_UNUSED = NULL;\n", io1Prefix, vPrefix, name)
+			b.printf("uint8_t* %s%s%s WUFFS_BASE__POTENTIALLY_UNUSED = NULL;\n", io2Prefix, vPrefix, name)
 		}
 	}
 	return nil
diff --git a/internal/testcut/testcut.go b/internal/testcut/testcut.go
index ba2d0ca..f15b8a5 100644
--- a/internal/testcut/testcut.go
+++ b/internal/testcut/testcut.go
@@ -43,7 +43,7 @@
 
 func Test(t *testing.T,
 	smallestValidMaxEncodedLen int,
-	cut func([]byte, int) (int, int, error),
+	cut func(io.Writer, []byte, int) (int, int, error),
 	newReader func(io.Reader) (io.ReadCloser, error),
 	filenames []string) {
 
@@ -90,8 +90,9 @@
 			if maxEncodedLen < smallestValidMaxEncodedLen {
 				continue
 			}
+			w0 := &bytes.Buffer{}
 			encoded := clone(full)
-			encLen, decLen, err := cut(encoded, maxEncodedLen)
+			encLen, decLen, err := cut(w0, encoded, maxEncodedLen)
 			if err != nil {
 				t.Errorf("f=%q, mEL=%d: cut: %v", filename, maxEncodedLen, err)
 				continue
@@ -107,13 +108,13 @@
 				continue
 			}
 
-			w := &bytes.Buffer{}
+			w1 := &bytes.Buffer{}
 			r, err := newReader(bytes.NewReader(encoded[:encLen]))
 			if err != nil {
 				t.Errorf("f=%q, mEL=%d: newReader: %v", filename, maxEncodedLen, err)
 				continue
 			}
-			if n, err := io.Copy(w, r); err != nil {
+			if n, err := io.Copy(w1, r); err != nil {
 				t.Errorf("f=%q, mEL=%d: io.Copy: %v", filename, maxEncodedLen, err)
 				continue
 			} else if n != int64(decLen) {
@@ -122,6 +123,11 @@
 				continue
 			}
 
+			if !bytes.Equal(w0.Bytes(), w1.Bytes()) {
+				t.Errorf("f=%q, mEL=%d: decoded bytes were not equal", filename, maxEncodedLen)
+				continue
+			}
+
 			if (maxEncodedLen == len(encoded)) && (int64(decLen) != fullDecodedLen) {
 				t.Errorf("f=%q, mEL=%d: full decode: got %d, want %d",
 					filename, maxEncodedLen, decLen, fullDecodedLen)
@@ -138,7 +144,7 @@
 
 func Benchmark(b *testing.B,
 	smallestValidMaxEncodedLen int,
-	cut func([]byte, int) (int, int, error),
+	cut func(io.Writer, []byte, int) (int, int, error),
 	newReader func(io.Reader) (io.ReadCloser, error),
 	filename string,
 	trimPrefix int,
@@ -168,7 +174,7 @@
 				continue
 			}
 			encoded := clone(full)
-			if _, _, err := cut(encoded, maxEncodedLen); err != nil {
+			if _, _, err := cut(nil, encoded, maxEncodedLen); err != nil {
 				b.Fatalf("cut: %v", err)
 			}
 			if maxEncodedLen >= len(full) {
diff --git a/lang/builtin/builtin.go b/lang/builtin/builtin.go
index 47de929..919fe86 100644
--- a/lang/builtin/builtin.go
+++ b/lang/builtin/builtin.go
@@ -19,15 +19,22 @@
 	t "github.com/google/wuffs/lang/token"
 )
 
+var FourCCs = [...][2]string{
+	{"ICCP", "International Color Consortium Profile"},
+	{"XMP ", "Extensible Metadata Platform"},
+}
+
 var Statuses = [...]string{
 	// Warnings.
 	`"@end of data"`,
+	`"@metadata reported"`,
 
 	// Suspensions.
 	`"$short read"`,
 	`"$short write"`,
 
 	// Errors.
+	`"#bad I/O position"`,
 	`"#bad argument (length too short)"`,
 	`"#bad argument"`,
 	`"#bad call sequence"`,
@@ -42,6 +49,7 @@
 	`"#initialize not called"`,
 	`"#interleaved coroutine calls"`,
 	`"#not enough data"`,
+	`"#unsupported option"`,
 	`"#too much data"`,
 }
 
@@ -209,11 +217,10 @@
 	"io_reader.peek_u64le() u64",
 
 	"io_reader.available() u64",
+	"io_reader.count_since(mark u64) u64",
+	"io_reader.mark() u64",
 	"io_reader.position() u64",
-	"io_reader.set!(s slice u8, closed bool)", // TODO: remove, as it's no longer used?
-	"io_reader.set_limit!(l u64)",             // TODO: remove, as it's no longer used?
-	"io_reader.set_mark!()",
-	"io_reader.since_mark() slice u8",
+	"io_reader.since(mark u64) slice u8",
 	"io_reader.take!(n u64) slice u8",
 
 	"io_reader.skip?(n u32)",
@@ -264,11 +271,11 @@
 	"io_writer.write_fast_u64le!(x u64)",
 
 	"io_writer.available() u64",
+	"io_writer.count_since(mark u64) u64",
+	"io_writer.history_available() u64",
+	"io_writer.mark() u64",
 	"io_writer.position() u64",
-	"io_writer.set!(s slice u8)",  // TODO: remove, as it's no longer used?
-	"io_writer.set_limit!(l u64)", // TODO: remove, as it's no longer used?
-	"io_writer.set_mark!()",
-	"io_writer.since_mark() slice u8",
+	"io_writer.since(mark u64) slice u8",
 
 	"io_writer.copy_from_slice!(s slice u8) u64",
 	"io_writer.copy_n_from_history!(n u32, distance u32) u32",
@@ -300,7 +307,7 @@
 	"frame_config.io_position() u64",
 
 	"frame_config.update!(bounds rect_ie_u32, duration u64[..0x7FFFFFFFFFFFFFFF], " +
-		"index u64, io_position u64, blend u8, disposal u8)",
+		"index u64, io_position u64, blend u8, disposal u8, background_color u32)",
 
 	// ---- image_config
 
@@ -315,8 +322,8 @@
 
 	// ---- pixel_swizzler
 
-	"pixel_swizzler.prepare!(dst_pixfmt u32, dst_palette slice u8, src_pixfmt u32, src_palette slice u8)",
-	"pixel_swizzler.swizzle_packed!(dst slice u8, dst_palette slice u8, src slice u8) u64",
+	"pixel_swizzler.prepare!(dst_pixfmt u32, dst_palette slice u8, src_pixfmt u32, src_palette slice u8) status",
+	"pixel_swizzler.swizzle_interleaved!(dst slice u8, dst_palette slice u8, src slice u8) u64",
 }
 
 // The "T1" and "T2" types here are placeholders for generic "slice T" or
diff --git a/lang/check/bounds.go b/lang/check/bounds.go
index a2d36ef..6e3c7d9 100644
--- a/lang/check/bounds.go
+++ b/lang/check/bounds.go
@@ -1113,10 +1113,10 @@
 }
 
 func (q *checker) canCopyNFromHistoryFast(recv *a.Expr, args []*a.Node) error {
-	// As per cgen's base-private.h, there are three pre-conditions:
+	// As per cgen's io-private.h, there are three pre-conditions:
 	//  - n <= this.available()
 	//  - distance > 0
-	//  - distance <= this.since_mark().length()
+	//  - distance <= this.history_available()
 
 	if len(args) != 2 {
 		return fmt.Errorf("check: internal error: inconsistent copy_n_from_history_fast arguments")
@@ -1174,7 +1174,7 @@
 		return fmt.Errorf("check: could not prove distance > 0")
 	}
 
-	// Check "distance <= this.since_mark().length()".
+	// Check "distance <= this.history_available()".
 check2:
 	for {
 		for _, x := range q.facts {
@@ -1192,22 +1192,18 @@
 				continue
 			}
 
-			// Check that the RHS is "recv.since_mark().length()".
+			// Check that the RHS is "recv.history_available()".
 			y, method, yArgs := splitReceiverMethodArgs(x.RHS().AsExpr())
-			if method != t.IDLength || len(yArgs) != 0 {
+			if method != t.IDHistoryAvailable || len(yArgs) != 0 {
 				continue
 			}
-			z, method, zArgs := splitReceiverMethodArgs(y)
-			if method != t.IDSinceMark || len(zArgs) != 0 {
-				continue
-			}
-			if !z.Eq(recv) {
+			if !y.Eq(recv) {
 				continue
 			}
 
 			break check2
 		}
-		return fmt.Errorf("check: could not prove distance <= %s.since_mark().length()", recv.Str(q.tm))
+		return fmt.Errorf("check: could not prove distance <= %s.history_available()", recv.Str(q.tm))
 	}
 
 	return nil
diff --git a/lang/token/list.go b/lang/token/list.go
index 85cb5c5..765ef80 100644
--- a/lang/token/list.go
+++ b/lang/token/list.go
@@ -459,15 +459,15 @@
 
 	IDDecodeFrameOptions = ID(0x158)
 
-	IDCanUndoByte     = ID(0x160)
-	IDPosition        = ID(0x161)
-	IDSetLimit        = ID(0x162)
-	IDSetMark         = ID(0x163)
-	IDSinceMark       = ID(0x164)
-	IDSinceMarkLength = ID(0x165)
-	IDSkip            = ID(0x166)
-	IDSkipFast        = ID(0x167)
-	IDTake            = ID(0x168)
+	IDCanUndoByte      = ID(0x160)
+	IDCountSince       = ID(0x161)
+	IDHistoryAvailable = ID(0x162)
+	IDMark             = ID(0x163)
+	IDPosition         = ID(0x164)
+	IDSince            = ID(0x165)
+	IDSkip             = ID(0x166)
+	IDSkipFast         = ID(0x167)
+	IDTake             = ID(0x168)
 
 	IDCopyFromSlice        = ID(0x170)
 	IDCopyNFromHistory     = ID(0x171)
@@ -791,15 +791,15 @@
 
 	IDDecodeFrameOptions: "decode_frame_options",
 
-	IDCanUndoByte:     "can_undo_byte",
-	IDPosition:        "position",
-	IDSetLimit:        "set_limit",
-	IDSetMark:         "set_mark",
-	IDSinceMark:       "since_mark",
-	IDSinceMarkLength: "since_mark_length",
-	IDSkip:            "skip",
-	IDSkipFast:        "skip_fast",
-	IDTake:            "take",
+	IDCanUndoByte:      "can_undo_byte",
+	IDCountSince:       "count_since",
+	IDHistoryAvailable: "history_available",
+	IDMark:             "mark",
+	IDPosition:         "position",
+	IDSince:            "since",
+	IDSkip:             "skip",
+	IDSkipFast:         "skip_fast",
+	IDTake:             "take",
 
 	IDCopyFromSlice:        "copy_from_slice",
 	IDCopyNFromHistory:     "copy_n_from_history",
diff --git a/lib/flatecut/flatecut.go b/lib/flatecut/flatecut.go
index cc4e7fc..0557168 100644
--- a/lib/flatecut/flatecut.go
+++ b/lib/flatecut/flatecut.go
@@ -23,14 +23,19 @@
 package flatecut
 
 import (
+	"bytes"
+	"compress/flate"
 	"errors"
+	"io"
 )
 
 var (
 	errMaxEncodedLenTooSmall = errors.New("flatecut: maxEncodedLen is too small")
 
-	errInternalNoProgress   = errors.New("flatecut: internal: no progress")
-	errInternalSomeProgress = errors.New("flatecut: internal: some progress")
+	errInternalInconsistentDecodedLen = errors.New("flatecut: internal: inconsistent decodedLen")
+	errInternalNoProgress             = errors.New("flatecut: internal: no progress")
+	errInternalReplaceWithSingleBlock = errors.New("flatecut: internal: replace with single block")
+	errInternalSomeProgress           = errors.New("flatecut: internal: some progress")
 
 	errInvalidBadBlockLength = errors.New("flatecut: invalid input: bad block length")
 	errInvalidBadBlockType   = errors.New("flatecut: invalid input: bad block type")
@@ -84,6 +89,13 @@
 	mostNegativeInt32 = -0x80000000
 )
 
+func loadU64LE(b []byte) uint64 {
+	_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
+	return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
+		uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
+
+}
+
 type bitstream struct {
 	// bytes[index] is the next byte to load into the 'bits' field.
 	bytes []byte
@@ -91,7 +103,7 @@
 
 	// The low nBits bits of the 'bits' field hold the next bits (in LSB-first
 	// order).
-	bits  uint32
+	bits  uint64
 	nBits uint32
 }
 
@@ -100,13 +112,13 @@
 		if b.index >= len(b.bytes) {
 			return mostNegativeInt32
 		}
-		b.bits |= uint32(b.bytes[b.index]) << b.nBits
+		b.bits |= uint64(b.bytes[b.index]) << b.nBits
 		b.nBits += 8
 		b.index++
 	}
 
 	mask := ((uint32(1)) << nBits) - 1
-	ret := b.bits & mask
+	ret := uint32(b.bits) & mask
 	b.bits >>= nBits
 	b.nBits -= nBits
 	return int32(ret)
@@ -125,12 +137,15 @@
 // which results in:
 //
 // huffman{
-//   counts: []uint32{
+//   counts: [maxCodeBits + 1]uint32{
 //     2: 1, 3: 5, 4: 2,
 //   },
-//   symbols: []int32{
+//   symbols: [maxNumCodes]int32{
 //     0: 'F', 1: 'A', 2: 'B', 3: 'C', 4: 'D', 5: 'E', 6: 'G', 7: 'H',
 //   },
+//   lookUpTable: [256]uint32{
+//     etc,
+//   },
 // }
 //
 // Continuing the example from the RFC, decoding "1110" from the bitstream will
@@ -138,9 +153,49 @@
 type huffman struct {
 	counts  [maxCodeBits + 1]uint32
 	symbols [maxNumCodes]int32
+
+	// lookUpTable holds cached results of the slowDecode method.
+	//
+	// The uint8 key is the next 8 bits of input.
+	//
+	// The uint32 value's low 16 bits are the symbol, the high 16 bits are the
+	// number of bits used to encode that symbol.
+	//
+	// Zero means that the entry is invalid. For example, the encoded symbol
+	// might be longer than 8 bits.
+	lookUpTable [256]uint32
 }
 
 func (h *huffman) decode(b *bitstream) int32 {
+	if b.nBits >= 8 {
+		// No-op.
+	} else if b.index < (len(b.bytes) - 8) {
+		// This is "Variant 4" of
+		// https://fgiesen.wordpress.com/2018/02/20/reading-bits-in-far-too-many-ways-part-2/
+		u := loadU64LE(b.bytes[b.index:])
+		b.bits |= u << b.nBits
+		b.index += int((63 - b.nBits) >> 3)
+		b.nBits |= 56
+	} else if b.index < len(b.bytes) {
+		b.bits |= uint64(b.bytes[b.index]) << b.nBits
+		b.nBits += 8
+		b.index++
+	} else {
+		goto slow
+	}
+
+	if x := h.lookUpTable[b.bits&0xFF]; x != 0 {
+		n := x >> 16
+		b.bits >>= n
+		b.nBits -= n
+		return int32(x & 0xFFFF)
+	}
+
+slow:
+	return h.slowDecode(b)
+}
+
+func (h *huffman) slowDecode(b *bitstream) int32 {
 	code := uint32(0)     // The i bits from the bitstream.
 	first := uint32(0)    // The first Huffman code of length i.
 	symIndex := uint32(0) // How many elements of h.symbols we've gone past.
@@ -154,12 +209,12 @@
 			if b.index >= len(b.bytes) {
 				return mostNegativeInt32
 			}
-			b.bits = uint32(b.bytes[b.index])
+			b.bits = uint64(b.bytes[b.index])
 			b.nBits = 8
 			b.index++
 		}
 
-		code |= b.bits & 1
+		code |= uint32(b.bits & 1)
 		b.bits >>= 1
 		b.nBits -= 1
 
@@ -199,7 +254,7 @@
 	for _, x := range lengths {
 		h.counts[x]++
 	}
-	if h.counts[0] == uint32(len(lengths)) {
+	if h.counts[0] >= uint32(len(lengths)) {
 		return 0, 0, errInvalidBadHuffmanTree
 	}
 
@@ -230,7 +285,12 @@
 		}
 	}
 	if remaining != 0 {
-		return 0, 0, errInvalidBadHuffmanTree
+		if ((h.counts[0] + 1) == uint32(len(lengths))) && (h.counts[1] == 1) {
+			// No-op. We allow a degenerate Huffman tree with only one code (of
+			// length 1 bit).
+		} else {
+			return 0, 0, errInvalidBadHuffmanTree
+		}
 	}
 
 	offsets := [maxCodeBits + 1]uint32{}
@@ -244,18 +304,68 @@
 			offsets[length]++
 		}
 	}
+	h.constructLookUpTable()
 	return endCodeBits, endCodeNBits, nil
 }
 
-// cutEmpty sets encoding[:2] to contain valid DEFLATE-encoded data. Decoding
-// that data produces zero bytes.
-func cutEmpty(encoded []byte, maxEncodedLen int) (encodedLen int, decodedLen int, retErr error) {
+func (h *huffman) constructLookUpTable() {
+	b := bitstream{
+		bytes: []byte{0x00},
+	}
+	for i := range h.lookUpTable {
+		b.bytes[0] = uint8(i)
+		b.index = 0
+		b.bits = 0
+		b.nBits = 0
+		if x := h.slowDecode(&b); x >= 0 {
+			h.lookUpTable[i] = ((8 - b.nBits) << 16) | uint32(x)
+		} else {
+			h.lookUpTable[i] = 0
+		}
+	}
+}
+
+// cutSingleBlock is an implementation of Cut whose output consists of a single
+// DEFLATE block.
+//
+// If maxEncodedLen is sufficiently large, this will be a Stored block (i.e. a
+// header followed by literal bytes). Otherwise, it will set encoding[:2] so
+// that decoding produces zero bytes.
+//
+// A precondition is that maxEncodedLen >= SmallestValidMaxEncodedLen.
+func cutSingleBlock(encoded []byte, maxEncodedLen int) (encodedLen int, decodedLen int, retErr error) {
 	if maxEncodedLen < SmallestValidMaxEncodedLen {
 		panic("unreachable")
 	}
+
+	// Try re-encoding as a single Stored block: up to 0xFFFF literal bytes,
+	// with a 5 byte prefix.
+	if maxEncodedLen > 5 {
+		n := maxEncodedLen - 5
+		if n > 0xFFFF {
+			n = 0xFFFF
+		}
+
+		buf := make([]byte, n)
+		n, err := io.ReadFull(flate.NewReader(bytes.NewReader(encoded)), buf)
+		if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
+			return 0, 0, err
+		}
+
+		if n > 0 {
+			encoded[0] = 0x01 // finalBlock = true, blockType = 0 (Stored).
+			encoded[1] = uint8(n >> 0)
+			encoded[2] = uint8(n >> 8)
+			encoded[3] = ^encoded[1]
+			encoded[4] = ^encoded[2]
+			copy(encoded[5:], buf[:n])
+			return n + 5, n, nil
+		}
+	}
+
 	// Set encoded[:2] to hold:
 	//  - 1 bit   ...._...._...._...1  finalBlock   = true.
-	//  - 2 bits  ...._...._...._.01.  blockType    = 1 (static Huffman).
+	//  - 2 bits  ...._...._...._.01.  blockType    = 1 (Static Huffman).
 	//  - 7 bits  ...._..00_0000_0...  litLenSymbol = 256 (end-of-block).
 	//  - 6 bits  0000_00.._...._....  padding.
 	encoded[0] = 0x03
@@ -267,11 +377,16 @@
 // DEFLATE-compressed data, assuming that encoded starts off containing valid
 // DEFLATE-compressed data.
 //
+// If a nil error is returned, then encodedLen <= maxEncodedLen will hold.
+//
 // Decompressing that modified, shorter byte slice produces a prefix (of length
 // decodedLen) of the decompression of the original, longer byte slice.
 //
+// If w is non-nil, that prefix is also written to w. If a non-nil error is
+// returned, incomplete data might still be written to w.
+//
 // It does not necessarily return the largest possible decodedLen.
-func Cut(encoded []byte, maxEncodedLen int) (encodedLen int, decodedLen int, retErr error) {
+func Cut(w io.Writer, encoded []byte, maxEncodedLen int) (encodedLen int, decodedLen int, retErr error) {
 	if maxEncodedLen < SmallestValidMaxEncodedLen {
 		return 0, 0, errMaxEncodedLenTooSmall
 	}
@@ -292,7 +407,28 @@
 		},
 		maxEncodedLen: maxEncodedLen,
 	}
-	return c.cut()
+	encodedLen, decodedLen, err := c.cut()
+	if err != nil {
+		return 0, 0, err
+	}
+
+	if w != nil {
+		// TODO: writing to w directly, in cutter's doStored and doHuffman,
+		// might be faster than re-walking the bitstream with compress/flate.
+		r := flate.NewReader(bytes.NewReader(encoded[:encodedLen]))
+		if n, err := io.Copy(w, r); err != nil {
+			r.Close()
+			return 0, 0, err
+		} else if n != int64(decodedLen) {
+			r.Close()
+			return 0, 0, errInternalInconsistentDecodedLen
+		}
+		if err := r.Close(); err != nil {
+			return 0, 0, err
+		}
+	}
+
+	return encodedLen, decodedLen, nil
 }
 
 type cutter struct {
@@ -320,6 +456,10 @@
 
 		finalBlockIndex := c.bits.index
 		finalBlockNBits := c.bits.nBits
+		for finalBlockNBits >= 8 {
+			finalBlockIndex--
+			finalBlockNBits -= 8
+		}
 
 		blockType := c.bits.take(2)
 		if blockType < 0 {
@@ -331,13 +471,18 @@
 		case 0:
 			err = c.doStored()
 		case 1:
-			err = c.doStaticHuffman()
+			err = c.doStaticHuffman(prevFinalBlockIndex < 0)
 		case 2:
-			err = c.doDynamicHuffman()
+			err = c.doDynamicHuffman(prevFinalBlockIndex < 0)
 		case 3:
 			return 0, 0, errInvalidBadBlockType
 		}
 
+		for c.bits.nBits >= 8 {
+			c.bits.index--
+			c.bits.nBits -= 8
+		}
+
 		switch err {
 		case nil:
 			if finalBlock == 0 {
@@ -348,15 +493,15 @@
 
 		case errInternalNoProgress:
 			if prevFinalBlockIndex < 0 {
-				return cutEmpty(c.bits.bytes, c.maxEncodedLen)
+				return cutSingleBlock(c.bits.bytes, c.maxEncodedLen)
 			}
 
 			// Un-read to just before the finalBlock bit.
 			c.bits.index = finalBlockIndex
 			c.bits.nBits = finalBlockNBits + 1
-			if c.bits.nBits == 8 {
+			for c.bits.nBits >= 8 {
 				c.bits.index--
-				c.bits.nBits = 0
+				c.bits.nBits -= 8
 			}
 
 			finalBlockIndex = prevFinalBlockIndex
@@ -370,6 +515,9 @@
 			mask := uint32(1) << n
 			c.bits.bytes[finalBlockIndex-1] |= uint8(mask)
 
+		case errInternalReplaceWithSingleBlock:
+			return cutSingleBlock(c.bits.bytes, c.maxEncodedLen)
+
 		default:
 			return 0, 0, err
 
@@ -387,9 +535,14 @@
 }
 
 func (c *cutter) doStored() error {
-	if (c.maxEncodedLen - c.bits.index) < 4 {
+	for c.bits.nBits >= 8 {
+		c.bits.index--
+		c.bits.nBits -= 8
+	}
+	if (c.maxEncodedLen < c.bits.index) || ((c.maxEncodedLen - c.bits.index) < 4) {
 		return errInternalNoProgress
 	}
+
 	length := uint32(c.bits.bytes[c.bits.index+0]) | uint32(c.bits.bytes[c.bits.index+1])<<8
 	invLen := uint32(c.bits.bytes[c.bits.index+2]) | uint32(c.bits.bytes[c.bits.index+3])<<8
 	if length+invLen != 0xFFFF {
@@ -426,7 +579,7 @@
 	return errInternalSomeProgress
 }
 
-func (c *cutter) doStaticHuffman() error {
+func (c *cutter) doStaticHuffman(isFirstBlock bool) error {
 	const (
 		numLCodes = 288
 		numDCodes = 32
@@ -451,10 +604,10 @@
 		lengths[i] = 5
 	}
 
-	return c.doHuffman(lengths[:numLCodes], lengths[numLCodes:])
+	return c.doHuffman(isFirstBlock, lengths[:numLCodes], lengths[numLCodes:])
 }
 
-func (c *cutter) doDynamicHuffman() error {
+func (c *cutter) doDynamicHuffman(isFirstBlock bool) error {
 	numLCodes := 257 + c.bits.take(5)
 	if numLCodes < 0 {
 		return errInvalidNotEnoughData
@@ -527,10 +680,10 @@
 		}
 	}
 
-	return c.doHuffman(lengths[:numLCodes], lengths[numLCodes:])
+	return c.doHuffman(isFirstBlock, lengths[:numLCodes], lengths[numLCodes:])
 }
 
-func (c *cutter) doHuffman(lLengths []uint32, dLengths []uint32) error {
+func (c *cutter) doHuffman(isFirstBlock bool, lLengths []uint32, dLengths []uint32) error {
 	err := error(nil)
 	if c.endCodeBits, c.endCodeNBits, err = c.lHuff.construct(lLengths); err != nil {
 		return err
@@ -542,6 +695,10 @@
 		return err
 	}
 
+	for c.bits.nBits >= 8 {
+		c.bits.index--
+		c.bits.nBits -= 8
+	}
 	if c.bits.index > c.maxEncodedLen {
 		return errInternalNoProgress
 	}
@@ -609,8 +766,25 @@
 	if checkpointIndex < 0 {
 		return errInternalNoProgress
 	}
+
+	// If we're the first block in the DEFLATE stream, check if we would be
+	// better off replacing the Huffman block with a Stored block.
+	if isFirstBlock && (c.maxEncodedLen > 5) {
+		n := c.maxEncodedLen - 5
+		if n > 0xFFFF {
+			n = 0xFFFF
+		}
+		if c.decodedLen < int32(n) {
+			return errInternalReplaceWithSingleBlock
+		}
+	}
+
 	c.bits.index = checkpointIndex
 	c.bits.nBits = checkpointNBits
+	for c.bits.nBits >= 8 {
+		c.bits.index--
+		c.bits.nBits -= 8
+	}
 	c.writeEndCode()
 	return errInternalSomeProgress
 }
diff --git a/lib/flatecut/flatecut_test.go b/lib/flatecut/flatecut_test.go
index 7c649ca..5b0035e 100644
--- a/lib/flatecut/flatecut_test.go
+++ b/lib/flatecut/flatecut_test.go
@@ -15,8 +15,10 @@
 package flatecut
 
 import (
+	"bytes"
 	"compress/flate"
 	"io"
+	"io/ioutil"
 	"testing"
 
 	"github.com/google/wuffs/internal/testcut"
@@ -39,3 +41,191 @@
 func newReader(r io.Reader) (io.ReadCloser, error) {
 	return flate.NewReader(r), nil
 }
+
+func decodeFlate(src []byte) string {
+	dst, err := ioutil.ReadAll(flate.NewReader(bytes.NewReader(src)))
+	if err != nil {
+		return err.Error()
+	}
+	return string(dst)
+}
+
+func TestHuffmanDecode(t *testing.T) {
+	// This exercises the "ABCDEFGH" example from RFC 1951 section 3.2.2,
+	// discussed in the "type huffman" doc comment.
+
+	const src = "HEADFACE"
+	codes := []string{
+		'A': " 010",
+		'B': " 011",
+		'C': " 100",
+		'D': " 101",
+		'E': " 110",
+		'F': "  00",
+		'G': "1110",
+		'H': "1111",
+	}
+
+	encoded := []byte(nil)
+	encBits := uint32(0)
+	encNBits := uint32(0)
+	for _, s := range src {
+		for _, c := range codes[s] {
+			if c == ' ' {
+				continue
+			}
+
+			if c == '1' {
+				encBits |= 1 << encNBits
+			}
+			encNBits++
+
+			if encNBits == 8 {
+				encoded = append(encoded, uint8(encBits))
+				encBits = 0
+				encNBits = 0
+			}
+		}
+	}
+	if encNBits > 0 {
+		encoded = append(encoded, uint8(encBits))
+	}
+
+	h := huffman{
+		counts: [maxCodeBits + 1]uint32{
+			2: 1, 3: 5, 4: 2,
+		},
+		symbols: [maxNumCodes]int32{
+			0: 'F', 1: 'A', 2: 'B', 3: 'C', 4: 'D', 5: 'E', 6: 'G', 7: 'H',
+		},
+	}
+	h.constructLookUpTable()
+
+	b := &bitstream{
+		bytes: encoded,
+	}
+
+	decoded := []byte(nil)
+	for _ = range src {
+		c := h.decode(b)
+		if c < 0 {
+			decoded = append(decoded, '!')
+			break
+		}
+		decoded = append(decoded, uint8(c))
+	}
+
+	if got, want := string(decoded), src; got != want {
+		t.Fatalf("got %q, want %q", got, want)
+	}
+}
+
+// A DEFLATE encoding of the first 64 bytes of the AUTHORS file in the
+// repository root directory.
+//
+// The encoding uses a Dynamic Huffman block, but one whose H-D Huffman
+// distance tree is degenerate (there's a mapping for the "0" code but no
+// mapping for the "1" code) and unused.
+//
+// The first 25-30 bytes contain the encoded H-L and H-D Huffman trees. The
+// last 30-35 bytes contain the actual payload, encoded with H-L.
+//
+// The high 7 bits of the final byte is unused padding.
+const (
+	degenerateHuffmanDec = "# This is the official list of Wuffs authors for copyright purpo"
+	degenerateHuffmanEnc = "" +
+		"\x05\xc0\xc1\x09\xc5\x30\x0c\x03\xd0\x55\x04\x7f\xa5\x0f\x3d\x87" +
+		"\x50\xd5\x82\x80\x82\xed\x1c\xba\x7d\xdf\x0f\xff\x50\x41\x85\x8e" +
+		"\x1b\x26\x35\x35\x16\x96\xaa\x61\xe2\x3a\x64\x61\x9c\x0e\x67\x81" +
+		"\x4e\x4c\xef\x37\xf5\x44\x63\x9f\xdc\xfe\x00"
+)
+
+func TestReplaceHuffmanWithStored(t *testing.T) {
+	const dec = degenerateHuffmanDec
+	const enc = degenerateHuffmanEnc
+	if (len(dec) != 64) || (len(enc) != 59) {
+		panic("inconsistent const string lengths")
+	}
+	if got, want := decodeFlate([]byte(enc)), dec; got != want {
+		t.Fatalf("before Cut: got %q, want %q", got, want)
+	}
+
+	for i := 4; i <= 59; i += 5 {
+		b := []byte(enc)
+		encLen, decLen, err := Cut(nil, b, i)
+		if err != nil {
+			t.Errorf("i=%d: %v", i, err)
+			continue
+		}
+		if encLen < 1 {
+			t.Errorf("i=%d: encLen: got %d, want >= 1", i, encLen)
+			continue
+		} else if encLen > len(enc) {
+			t.Errorf("i=%d: encLen: got %d, want <= %d", i, encLen, len(enc))
+			continue
+		}
+		// If we can make some progress (decLen > 0), even if the input uses a
+		// Huffman block, one option is to re-encode to a single Stored block
+		// (for 5 bytes of overhead). It's single because len(dec) < 0xFFFF.
+		// Regardless of whether the cut form uses a Huffman or Stored block,
+		// we should be able to produce at least i-5 bytes of decoded output.
+		if (decLen > 0) && (i > 5) && (decLen < i-5) {
+			t.Errorf("i=%d: decLen: got %d, want >= %d", i, decLen, i-5)
+			continue
+		} else if decLen > len(dec) {
+			t.Errorf("i=%d: decLen: got %d, want <= %d", i, decLen, len(dec))
+			continue
+		}
+
+		if got, want := decodeFlate(b[:encLen]), dec[:decLen]; got != want {
+			t.Errorf("i=%d: after Cut: got %q, want %q", i, got, want)
+			continue
+		}
+
+		// Check that we are using a space-efficient block type.
+		{
+			got := (b[0] >> 1) & 3
+			want := uint8(0xFF)
+
+			switch i {
+			case 4:
+				want = 1 // Static Huffman, for a decLen of 0.
+			case 9:
+				want = 0 // Stored.
+			case 59:
+				want = 2 // Dynamic Huffman.
+			default:
+				continue
+			}
+
+			if got != want {
+				t.Errorf("i=%d: block type: got %d, want %d", i, got, want)
+			}
+		}
+	}
+}
+
+func TestDegenerateHuffmanUnused(t *testing.T) {
+	const dec = degenerateHuffmanDec
+	const enc = degenerateHuffmanEnc
+
+	// Cutting 1 byte off the end of the encoded form will lead to cutting n
+	// bytes off the decoded form. Coincidentally, n equals 1, even though each
+	// decoded byte (8 bits) is packed into smaller number of bits, as most of
+	// the final encoded byte's bits are unused padding.
+	const n = 1
+
+	b := []byte(enc)
+	encLen, decLen, err := Cut(nil, b, len(enc)-1)
+	if err != nil {
+		t.Fatalf("Cut: %v", err)
+	} else if encLen != len(enc)-1 {
+		t.Fatalf("encLen: got %d, want %d", encLen, len(enc)-1)
+	} else if decLen != len(dec)-n {
+		t.Fatalf("decLen: got %d, want %d", decLen, len(dec)-n)
+	}
+
+	if got, want := decodeFlate(b[:encLen]), dec[:decLen]; got != want {
+		t.Fatalf("after Cut: got %q, want %q", got, want)
+	}
+}
diff --git a/lib/rac/rac.go b/lib/rac/rac.go
index 905fb15..03b9ffb 100644
--- a/lib/rac/rac.go
+++ b/lib/rac/rac.go
@@ -26,7 +26,7 @@
 package rac
 
 const (
-	// MaxCSize is the maximum RAC file size (in both CSpace and DSpace).
+	// MaxSize is the maximum RAC file size (in both CSpace and DSpace).
 	MaxSize = (1 << 48) - 1
 
 	// invalidCOffsetCLength is ((1 << 64) - 1).
diff --git a/lib/rac/writer.go b/lib/rac/writer.go
index 002365f..b699120 100644
--- a/lib/rac/writer.go
+++ b/lib/rac/writer.go
@@ -21,6 +21,13 @@
 	"sort"
 )
 
+func btoi(b bool) int {
+	if b {
+		return 1
+	}
+	return 0
+}
+
 func isZeroOrAPowerOf2(x uint64) bool {
 	return (x & (x - 1)) == 0
 }
@@ -593,8 +600,18 @@
 	w.buffer[4] = uint8(checksum >> 0)
 	w.buffer[5] = uint8(checksum >> 8)
 
-	_, err := w.w.Write(w.buffer[:size])
-	return err
+	if _, err := w.w.Write(w.buffer[:size]); err != nil {
+		return err
+	}
+
+	for i, o := range n.children {
+		if len(o.children) != 0 {
+			if err := w.writeIndex(&n.children[i]); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
 }
 
 func calcCLength(primarySize int) uint64 {
@@ -640,37 +657,51 @@
 	}
 
 	resources := map[OptResource]bool{}
-	arity := 0
 
 	for {
-		i, j, newNodes := 0, 0, []node(nil)
+		i, j, arity, newNodes := 0, 0, 0, []node(nil)
 		for ; j < len(nodes); j++ {
 			o := &nodes[j]
 
-			arity++
-			if (o.secondary != 0) && !resources[o.secondary] {
-				resources[o.secondary] = true
-				arity++
-			}
-			if (o.tertiary != 0) && !resources[o.tertiary] {
-				resources[o.tertiary] = true
-				arity++
-			}
+			new2 := (o.secondary != 0) && !resources[o.secondary]
+			new3 := (o.tertiary != 0) && !resources[o.tertiary]
+			arity += 1 + btoi(new2) + btoi(new3)
 			if arity <= 0xFF {
+				if new2 {
+					resources[o.secondary] = true
+				}
+				if new3 {
+					resources[o.tertiary] = true
+				}
 				continue
 			}
 
 			newNodes = append(newNodes, makeBranch(nodes[i:j], resources))
-			i = j
-			arity = 0
 			if len(resources) != 0 {
 				resources = map[OptResource]bool{}
 			}
+
+			i = j
+			arity = 1
+			if o.secondary != 0 {
+				resources[o.secondary] = true
+				arity++
+			}
+			if o.tertiary != 0 {
+				resources[o.tertiary] = true
+				arity++
+			}
 		}
 
 		if i == 0 {
 			return makeBranch(nodes, resources)
 		}
+
+		newNodes = append(newNodes, makeBranch(nodes[i:], resources))
+		if len(resources) != 0 {
+			resources = map[OptResource]bool{}
+		}
+
 		nodes, newNodes = newNodes, nil
 	}
 }
diff --git a/lib/rac/writer_test.go b/lib/rac/writer_test.go
index 08982af..596c407 100644
--- a/lib/rac/writer_test.go
+++ b/lib/rac/writer_test.go
@@ -25,6 +25,10 @@
 	"testing"
 )
 
+const bytesPerHexDumpLine = 79
+
+const fakeCodec = Codec(0xEE)
+
 // Note that these exact bytes depends on the zlib encoder's algorithm, but
 // there is more than one valid zlib encoding of any given input. This "compare
 // to golden output" test is admittedly brittle, as the standard library's zlib
@@ -164,7 +168,6 @@
 
 func testWriter(iloc IndexLocation, tempFile io.ReadWriter, cPageSize uint64, empty bool) error {
 	buf := &bytes.Buffer{}
-	const fakeCodec = Codec(0xEE)
 	w := &Writer{
 		Writer:        buf,
 		Codec:         fakeCodec,
@@ -211,3 +214,127 @@
 	}
 	return nil
 }
+
+func TestWriterMultiLevelIndex(t *testing.T) {
+	buf := &bytes.Buffer{}
+	w := &Writer{
+		Writer:        buf,
+		Codec:         fakeCodec,
+		IndexLocation: IndexLocationAtStart,
+		TempFile:      &bytes.Buffer{},
+	}
+
+	// Write 260 chunks with 3 resources. With the current "func gather"
+	// algorithm, this results in a root node with two children, both of which
+	// are branch nodes. The first branch contains 252 chunks and refers to 3
+	// resources (so that its arity is 255). The second branch contains 8
+	// chunks and refers to 1 resource (so that its arity is 9).
+	xRes := OptResource(0)
+	yRes := OptResource(0)
+	zRes := OptResource(0)
+	for i := 0; i < 260; i++ {
+		secondary := OptResource(0)
+		tertiary := OptResource(0)
+
+		switch i {
+		case 3:
+			xRes, _ = w.AddResource([]byte("XX"))
+			yRes, _ = w.AddResource([]byte("YY"))
+			secondary = xRes
+			tertiary = yRes
+
+		case 4:
+			zRes, _ = w.AddResource([]byte("ZZ"))
+			secondary = yRes
+			tertiary = zRes
+
+		case 259:
+			secondary = yRes
+		}
+
+		primary := []byte(fmt.Sprintf("p%02x", i&0xFF))
+		_ = w.AddChunk(0x10000, primary, secondary, tertiary)
+	}
+
+	if err := w.Close(); err != nil {
+		t.Fatalf("Close: %v", err)
+	}
+
+	encoded := buf.Bytes()
+	if got, want := len(encoded), 0x13E2; got != want {
+		t.Fatalf("len(encoded): got 0x%X, want 0x%X", got, want)
+	}
+
+	got := hex.Dump(encoded)
+	got = "" +
+		got[0x000*bytesPerHexDumpLine:0x008*bytesPerHexDumpLine] +
+		"...\n" +
+		got[0x080*bytesPerHexDumpLine:0x088*bytesPerHexDumpLine] +
+		"...\n" +
+		got[0x100*bytesPerHexDumpLine:0x110*bytesPerHexDumpLine] +
+		"...\n" +
+		got[0x13C*bytesPerHexDumpLine:]
+
+	const want = "" +
+		"00000000  72 c3 63 02 a0 24 00 ff  00 00 fc 00 00 00 00 ff  |r.c..$..........|\n" +
+		"00000010  00 00 04 01 00 00 00 ee  30 00 00 00 00 00 04 ff  |........0.......|\n" +
+		"00000020  30 10 00 00 00 00 01 ff  e2 13 00 00 00 00 01 02  |0...............|\n" +
+		"00000030  72 c3 63 ff 4c be 00 ff  00 00 00 00 00 00 00 ff  |r.c.L...........|\n" +
+		"00000040  00 00 00 00 00 00 00 ff  00 00 00 00 00 00 00 ff  |................|\n" +
+		"00000050  00 00 01 00 00 00 00 ff  00 00 02 00 00 00 00 ff  |................|\n" +
+		"00000060  00 00 03 00 00 00 00 01  00 00 04 00 00 00 00 02  |................|\n" +
+		"00000070  00 00 05 00 00 00 00 ff  00 00 06 00 00 00 00 ff  |................|\n" +
+		"...\n" +
+		"00000800  00 00 f7 00 00 00 00 ff  00 00 f8 00 00 00 00 ff  |................|\n" +
+		"00000810  00 00 f9 00 00 00 00 ff  00 00 fa 00 00 00 00 ff  |................|\n" +
+		"00000820  00 00 fb 00 00 00 00 ff  00 00 fc 00 00 00 00 ee  |................|\n" +
+		"00000830  d9 10 00 00 00 00 01 ff  db 10 00 00 00 00 01 ff  |................|\n" +
+		"00000840  e0 10 00 00 00 00 01 ff  d0 10 00 00 00 00 01 ff  |................|\n" +
+		"00000850  d3 10 00 00 00 00 01 ff  d6 10 00 00 00 00 01 ff  |................|\n" +
+		"00000860  dd 10 00 00 00 00 01 00  e2 10 00 00 00 00 01 01  |................|\n" +
+		"00000870  e5 10 00 00 00 00 01 ff  e8 10 00 00 00 00 01 ff  |................|\n" +
+		"...\n" +
+		"00001000  bb 13 00 00 00 00 01 ff  be 13 00 00 00 00 01 ff  |................|\n" +
+		"00001010  c1 13 00 00 00 00 01 ff  c4 13 00 00 00 00 01 ff  |................|\n" +
+		"00001020  c7 13 00 00 00 00 01 ff  e2 13 00 00 00 00 01 ff  |................|\n" +
+		"00001030  72 c3 63 09 f0 09 00 ff  00 00 00 00 00 00 00 ff  |r.c.............|\n" +
+		"00001040  00 00 01 00 00 00 00 ff  00 00 02 00 00 00 00 ff  |................|\n" +
+		"00001050  00 00 03 00 00 00 00 ff  00 00 04 00 00 00 00 ff  |................|\n" +
+		"00001060  00 00 05 00 00 00 00 ff  00 00 06 00 00 00 00 ff  |................|\n" +
+		"00001070  00 00 07 00 00 00 00 ff  00 00 08 00 00 00 00 ee  |................|\n" +
+		"00001080  db 10 00 00 00 00 01 ff  ca 13 00 00 00 00 01 ff  |................|\n" +
+		"00001090  cd 13 00 00 00 00 01 ff  d0 13 00 00 00 00 01 ff  |................|\n" +
+		"000010a0  d3 13 00 00 00 00 01 ff  d6 13 00 00 00 00 01 ff  |................|\n" +
+		"000010b0  d9 13 00 00 00 00 01 ff  dc 13 00 00 00 00 01 ff  |................|\n" +
+		"000010c0  df 13 00 00 00 00 01 00  e2 13 00 00 00 00 01 09  |................|\n" +
+		"000010d0  70 30 30 70 30 31 70 30  32 58 58 59 59 70 30 33  |p00p01p02XXYYp03|\n" +
+		"000010e0  5a 5a 70 30 34 70 30 35  70 30 36 70 30 37 70 30  |ZZp04p05p06p07p0|\n" +
+		"000010f0  38 70 30 39 70 30 61 70  30 62 70 30 63 70 30 64  |8p09p0ap0bp0cp0d|\n" +
+		"...\n" +
+		"000013c0  38 70 66 39 70 66 61 70  66 62 70 66 63 70 66 64  |8pf9pfapfbpfcpfd|\n" +
+		"000013d0  70 66 65 70 66 66 70 30  30 70 30 31 70 30 32 70  |pfepffp00p01p02p|\n" +
+		"000013e0  30 33                                             |03|\n"
+
+	if got != want {
+		t.Fatalf("\ngot:\n%s\nwant:\n%s", got, want)
+	}
+}
+
+func TestWriter1000Chunks(t *testing.T) {
+	w := &Writer{
+		Writer: ioutil.Discard,
+		Codec:  fakeCodec,
+	}
+	data := make([]byte, 1)
+	res, _ := w.AddResource(data)
+	for i := 0; i < 1000; i++ {
+		if i == 2*255 {
+			_ = w.AddChunk(1, data, res, 0)
+		} else {
+			_ = w.AddChunk(1, data, 0, 0)
+		}
+	}
+	if err := w.Close(); err != nil {
+		t.Fatalf("Close: %v", err)
+	}
+}
diff --git a/lib/raczlib/raczlib.go b/lib/raczlib/raczlib.go
new file mode 100644
index 0000000..d41b8df
--- /dev/null
+++ b/lib/raczlib/raczlib.go
@@ -0,0 +1,41 @@
+// Copyright 2019 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.
+
+// ----------------
+
+// Package raczlib provides access to RAC (Random Access Compression) files
+// with the Zlib compression codec.
+//
+// The RAC specification is at
+// https://github.com/google/wuffs/blob/master/doc/spec/rac-spec.md
+package raczlib
+
+import (
+	"github.com/google/wuffs/lib/rac"
+)
+
+const (
+	// MaxSize is the maximum RAC file size (in both CSpace and DSpace).
+	MaxSize = rac.MaxSize
+)
+
+// IndexLocation is whether the index is at the start or end of the RAC file.
+//
+// See the RAC specification for further discussion.
+type IndexLocation = rac.IndexLocation
+
+const (
+	IndexLocationAtEnd   = rac.IndexLocationAtEnd
+	IndexLocationAtStart = rac.IndexLocationAtStart
+)
diff --git a/lib/raczlib/writer.go b/lib/raczlib/writer.go
new file mode 100644
index 0000000..00c4815
--- /dev/null
+++ b/lib/raczlib/writer.go
@@ -0,0 +1,470 @@
+// Copyright 2019 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.
+
+package raczlib
+
+// TODO: API for shared dictionaries.
+
+import (
+	"bytes"
+	"compress/zlib"
+	"errors"
+	"io"
+
+	"github.com/google/wuffs/lib/rac"
+	"github.com/google/wuffs/lib/zlibcut"
+)
+
+const (
+	defaultDChunkSize = 65536 // 64 KiB.
+
+	maxCChunkSize       = 1 << 30 // 1 GiB.
+	maxTargetDChunkSize = 1 << 31 // 2 GiB.
+)
+
+var (
+	errCChunkSizeIsTooSmall = errors.New("raczlib: CChunkSize is too small")
+	errInvalidWriter        = errors.New("raczlib: invalid Writer")
+	errWriterIsClosed       = errors.New("raczlib: Writer is closed")
+
+	errInternalShortCSize = errors.New("raczlib: internal error: short CSize")
+)
+
+func startingTargetDChunkSize(cChunkSize uint64) uint64 { return 2 * cChunkSize }
+
+// stripTrailingZeroes returns b shorn of any trailing '\x00' bytes. The RAC
+// file format will implicitly set any trailing zeroes.
+func stripTrailingZeroes(b []byte) []byte {
+	n := len(b)
+	for (n > 0) && (b[n-1] == 0) {
+		n--
+	}
+	return b[:n]
+}
+
+// writeBuffer is a sequence of bytes, split into two slices: prev[p:] and
+// curr. Together, they hold the tail (so far) of the stream of bytes given to
+// an io.Writer.
+//
+// Conceptually, it is the concatenation of those two slices, but the struct
+// holds two slices, not one, to minimize copying and memory allocation.
+//
+// The first slice, prev[p:], is those bytes from all previous Write calls that
+// have been copied to an internal buffer but not yet fully processed.
+//
+// The second slice, curr, is those bytes from the current Write call that have
+// not been processed.
+//
+// The underlying array that backs the prev slice is owned by the writeBuffer,
+// and is re-used to minimize memory allocations. The curr slice is a sub-slice
+// of the []byte passed to the Write method, and is owned by the caller, not by
+// the writeBuffer.
+//
+// As bytes are fully processed, prev[p:] shrinks (by incrementing "p += etc")
+// and after len(prev[p:]) hits zero, curr shrinks (by re-assigning "curr =
+// curr[etc:]"). The two mechanisms differ (e.g. there is a p int that offsets
+// prev but not a corresponding c int that offsets curr) because of the
+// difference in backing-array memory ownership, mentioned above.
+//
+// The intended usage is for an io.Writer's Write(data) method to first call
+// writeBuffer.extend(data), then a mixture of multiple writeBuffer.peek(n) and
+// writeBuffer.advance(n), and finally writeBuffer.compact(). Thus, before and
+// after every Write call, writeBuffer.curr is empty and writebuffer.p is 0.
+type writeBuffer struct {
+	prev []byte
+	curr []byte
+	p    int
+}
+
+// extend extends b's view of an io.Writer's incoming byte stream.
+func (b *writeBuffer) extend(curr []byte) {
+	if len(b.curr) != 0 {
+		panic("inconsistent writeBuffer state")
+	}
+	b.curr = curr
+}
+
+// length returns the total number of bytes available in b's buffers.
+func (b *writeBuffer) length() uint64 {
+	return uint64(len(b.prev)-b.p) + uint64(len(b.curr))
+}
+
+// peek returns the next m bytes in b's buffers, where m is the minimum of n
+// and b.length().
+//
+// The bytes are returned in two slices, not a single contiguous slice, in
+// order to minimize copying and memory allocation.
+func (b *writeBuffer) peek(n uint64) ([]byte, []byte) {
+	available := uint64(len(b.prev) - b.p)
+	if n <= available {
+		return b.prev[b.p : b.p+int(n)], nil
+	}
+	n -= available
+	if n <= uint64(len(b.curr)) {
+		return b.prev[b.p:], b.curr[:n]
+	}
+	return b.prev[b.p:], b.curr
+}
+
+// advance consumes the next n bytes.
+func (b *writeBuffer) advance(n uint64) {
+	available := uint64(len(b.prev) - b.p)
+	if n <= available {
+		b.p += int(n)
+		return
+	}
+	n -= available
+	b.curr = b.curr[n:]
+	b.p = len(b.prev)
+}
+
+// compact moves and copies any unprocessed bytes in b.prev and b.curr to be at
+// the start of b.prev.
+func (b *writeBuffer) compact() {
+	// Move any remaining bytes in prev to the front.
+	n := copy(b.prev, b.prev[b.p:])
+	b.prev = b.prev[:n]
+	b.p = 0
+	// Move any remaining bytes in curr to prev.
+	b.prev = append(b.prev, b.curr...)
+	b.curr = nil
+}
+
+// Writer provides a relatively simple way to write a RAC file (with the Zlib
+// compression codec) - one that is created starting from nothing, as opposed
+// to incrementally modifying an existing RAC file.
+//
+// Other packages may provide a more flexible (and more complicated) way to
+// write or append to RAC files, but that is out of scope of this package.
+//
+// Do not modify its exported fields after calling any of its methods.
+//
+// Writer implements the io.Writer interface.
+type Writer struct {
+	// Writer is where the RAC-encoded data is written to.
+	//
+	// Nil is an invalid value.
+	Writer io.Writer
+
+	// IndexLocation is whether the index is at the start or end of the RAC
+	// file.
+	//
+	// See the RAC specification for further discussion.
+	//
+	// The zero value of this field is IndexLocationAtEnd: a one pass encoding.
+	IndexLocation IndexLocation
+
+	// TempFile is a temporary file to use for a two pass encoding. The field
+	// name says "file" but it need not be a real file, in the operating system
+	// sense.
+	//
+	// For IndexLocationAtEnd, this must be nil. For IndexLocationAtStart, this
+	// must be non-nil.
+	//
+	// It does not need to implement io.Seeker, if it supports separate read
+	// and write positions (e.g. it is a bytes.Buffer). If it does implement
+	// io.Seeker (e.g. it is an os.File), its current position will be noted
+	// (via SeekCurrent), then it will be written to (via the
+	// raczlib.Writer.Write method), then its position will be reset (via
+	// SeekSet), then it will be read from (via the raczlib.Writer.Close
+	// method).
+	//
+	// The raczlib.Writer does not call TempFile.Close even if that method
+	// exists (e.g. the TempFile is an os.File). In that case, closing the
+	// temporary file (and deleting it) after the raczlib.Writer is closed is
+	// the responsibility of the raczlib.Writer caller, not the raczlib.Writer
+	// itself.
+	TempFile io.ReadWriter
+
+	// CPageSize guides padding the output to minimize the number of pages that
+	// each chunk occupies (in what the RAC spec calls CSpace).
+	//
+	// It must either be zero (which means no padding is inserted) or a power
+	// of 2, and no larger than MaxSize.
+	//
+	// For example, suppose that CSpace is partitioned into 1024-byte pages,
+	// that 1000 bytes have already been written to the output, and the next
+	// chunk is 1500 bytes long.
+	//
+	//   - With no padding (i.e. with CPageSize set to 0), this chunk will
+	//     occupy the half-open range [1000, 2500) in CSpace, which straddles
+	//     three 1024-byte pages: the page [0, 1024), the page [1024, 2048) and
+	//     the page [2048, 3072). Call those pages p0, p1 and p2.
+	//
+	//   - With padding (i.e. with CPageSize set to 1024), 24 bytes of zeroes
+	//     are first inserted so that this chunk occupies the half-open range
+	//     [1024, 2524) in CSpace, which straddles only two pages (p1 and p2).
+	//
+	// This concept is similar, but not identical, to alignment. Even with a
+	// non-zero CPageSize, chunk start offsets are not necessarily aligned to
+	// page boundaries. For example, suppose that the chunk size was only 1040
+	// bytes, not 1500 bytes. No padding will be inserted, as both [1000, 2040)
+	// and [1024, 2064) straddle two pages: either pages p0 and p1, or pages p1
+	// and p2.
+	//
+	// Nonetheless, if all chunks (or all but the final chunk) have a size
+	// equal to (or just under) a multiple of the page size, then in practice,
+	// each chunk's starting offset will be aligned to a page boundary.
+	CPageSize uint64
+
+	// CChunkSize is the compressed size (i.e. size in CSpace) of each chunk.
+	// The final chunk might be smaller than this.
+	//
+	// This field is ignored if DChunkSize is non-zero.
+	CChunkSize uint64
+
+	// DChunkSize is the uncompressed size (i.e. size in DSpace) of each chunk.
+	// The final chunk might be smaller than this.
+	//
+	// If both CChunkSize and DChunkSize are non-zero, DChunkSize takes
+	// precedence and CChunkSize is ignored.
+	//
+	// If both CChunkSize and DChunkSize are zero, a default DChunkSize value
+	// will be used.
+	DChunkSize uint64
+
+	// cChunkSize and dChunkSize are copies of CChunkSize and DChunkSize,
+	// validated during the initialize method. For example, if both CChunkSize
+	// and DChunkSize are zero, dChunkSize will be defaultDChunkSize.
+	cChunkSize uint64
+	dChunkSize uint64
+
+	// err is the first error encountered. It is sticky: once a non-nil error
+	// occurs, all public methods will return that error.
+	err error
+
+	racWriter  rac.Writer
+	zlibWriter *zlib.Writer
+
+	compressed   bytes.Buffer
+	uncompressed writeBuffer
+}
+
+func (w *Writer) initialize() error {
+	if w.err != nil {
+		return w.err
+	}
+	if w.racWriter.Writer != nil {
+		// We're already initialized.
+		return nil
+	}
+	if w.Writer == nil {
+		w.err = errInvalidWriter
+		return w.err
+	}
+
+	if w.DChunkSize > 0 {
+		w.dChunkSize = w.DChunkSize
+	} else if w.CChunkSize > 0 {
+		w.cChunkSize = w.CChunkSize
+		if w.cChunkSize > maxCChunkSize {
+			w.cChunkSize = maxCChunkSize
+		}
+	} else {
+		w.dChunkSize = defaultDChunkSize
+	}
+
+	w.racWriter.Writer = w.Writer
+	w.racWriter.Codec = rac.CodecZlib
+	w.racWriter.IndexLocation = w.IndexLocation
+	w.racWriter.TempFile = w.TempFile
+	w.racWriter.CPageSize = w.CPageSize
+
+	w.zlibWriter, w.err = zlib.NewWriterLevel(&w.compressed, zlib.BestCompression)
+	return w.err
+}
+
+func (w *Writer) compress(dBytes0 []byte, dBytes1 []byte) ([]byte, error) {
+	w.compressed.Reset()
+	w.zlibWriter.Reset(&w.compressed)
+	if len(dBytes0) > 0 {
+		if _, err := w.zlibWriter.Write(dBytes0); err != nil {
+			w.err = err
+			return nil, err
+		}
+	}
+	if len(dBytes1) > 0 {
+		if _, err := w.zlibWriter.Write(dBytes1); err != nil {
+			w.err = err
+			return nil, err
+		}
+	}
+	if err := w.zlibWriter.Close(); err != nil {
+		w.err = err
+		return nil, err
+	}
+	return w.compressed.Bytes(), nil
+}
+
+// Write implements io.Writer.
+func (w *Writer) Write(p []byte) (int, error) {
+	if err := w.initialize(); err != nil {
+		return 0, err
+	}
+	w.uncompressed.extend(p)
+	n, err := len(p), w.write(false)
+	w.uncompressed.compact()
+	if err != nil {
+		return 0, err
+	}
+	return n, nil
+}
+
+func (w *Writer) write(eof bool) error {
+	if w.dChunkSize > 0 {
+		return w.writeDChunks(eof)
+	}
+	return w.writeCChunks(eof)
+}
+
+func (w *Writer) writeDChunks(eof bool) error {
+	for {
+		peek0, peek1 := w.uncompressed.peek(w.dChunkSize)
+		dSize := uint64(len(peek0)) + uint64(len(peek1))
+		if dSize == 0 {
+			return nil
+		}
+		if !eof && (dSize < w.dChunkSize) {
+			return nil
+		}
+
+		peek1 = stripTrailingZeroes(peek1)
+		if len(peek1) == 0 {
+			peek0 = stripTrailingZeroes(peek0)
+		}
+		cBytes, err := w.compress(peek0, peek1)
+		if err != nil {
+			return err
+		}
+
+		if err := w.racWriter.AddChunk(dSize, cBytes, 0, 0); err != nil {
+			w.err = err
+			return err
+		}
+		w.uncompressed.advance(dSize)
+	}
+}
+
+func (w *Writer) writeCChunks(eof bool) error {
+	// Each outer loop iteration tries to write exactly one chunk.
+outer:
+	for {
+		// We don't know, a priori, how many w.uncompressed bytes are needed to
+		// produce a compressed chunk of size cChunkSize. We use a
+		// startingTargetDChunkSize that is a small multiple of cChunkSize, and
+		// keep doubling that until we build something at least as large as
+		// cChunkSize, then zlibcut it back to be exactly cChunkSize.
+		targetDChunkSize := uint64(maxTargetDChunkSize)
+		if !eof {
+			targetDChunkSize = startingTargetDChunkSize(w.cChunkSize)
+		}
+
+		if n := w.uncompressed.length(); n == 0 {
+			return nil
+		} else if !eof && (n < targetDChunkSize) {
+			return nil
+		}
+
+		// The inner loop keeps doubling the targetDChunkSize until it's big
+		// enough to produce at least cChunkSize bytes of compressed data.
+		for {
+			next := targetDChunkSize * 2
+			if next > maxTargetDChunkSize {
+				next = maxTargetDChunkSize
+			}
+			force := next <= targetDChunkSize
+
+			if err := w.tryCChunk(targetDChunkSize, force); err == nil {
+				continue outer
+			} else if err != errInternalShortCSize {
+				return err
+			}
+
+			if w.uncompressed.length() <= targetDChunkSize {
+				// Growing the targetDChunkSize isn't going to change anything.
+				return nil
+			}
+			targetDChunkSize = next
+		}
+	}
+}
+
+func (w *Writer) tryCChunk(targetDChunkSize uint64, force bool) error {
+	peek0, peek1 := w.uncompressed.peek(targetDChunkSize)
+	dSize := uint64(len(peek0)) + uint64(len(peek1))
+	cBytes, err := w.compress(peek0, peek1)
+	if err != nil {
+		return err
+	}
+
+	switch {
+	case uint64(len(cBytes)) < w.cChunkSize:
+		if !force {
+			return errInternalShortCSize
+		}
+		fallthrough
+	case uint64(len(cBytes)) == w.cChunkSize:
+		if err := w.racWriter.AddChunk(dSize, cBytes, 0, 0); err != nil {
+			w.err = err
+			return err
+		}
+		w.uncompressed.advance(dSize)
+		return nil
+	}
+
+	eLen, dLen, err := zlibcut.Cut(nil, cBytes, int(w.cChunkSize))
+	if err != nil {
+		w.err = err
+		return err
+	}
+	if dLen == 0 {
+		w.err = errCChunkSizeIsTooSmall
+		return w.err
+	}
+	if err := w.racWriter.AddChunk(uint64(dLen), cBytes[:eLen], 0, 0); err != nil {
+		w.err = err
+		return err
+	}
+	w.uncompressed.advance(uint64(dLen))
+	return nil
+}
+
+// Close writes the RAC index to w.Writer and marks that w accepts no further
+// method calls.
+//
+// For a one pass encoding, no further action is taken. For a two pass encoding
+// (i.e. IndexLocationAtStart), it then copies w.TempFile to w.Writer. Either
+// way, if this method returns nil error, the entirety of what was written to
+// w.Writer constitutes a valid RAC file.
+//
+// Closing the underlying w.Writer, w.TempFile or both is the responsibility of
+// the raczlib.Writer caller, not the raczlib.Writer itself.
+//
+// It is not necessary to call Close, e.g. if an earlier Write call returned a
+// non-nil error. Unlike an os.File, failing to call raczlib.Writer.Close will
+// not leak resources such as file descriptors.
+func (w *Writer) Close() error {
+	if err := w.initialize(); err != nil {
+		return err
+	}
+	if err := w.write(true); err != nil {
+		return err
+	}
+	if err := w.racWriter.Close(); err != nil {
+		w.err = err
+		return err
+	}
+	w.err = errWriterIsClosed
+	return nil
+}
diff --git a/lib/zlibcut/example_test.go b/lib/zlibcut/example_test.go
new file mode 100644
index 0000000..76f4f4f
--- /dev/null
+++ b/lib/zlibcut/example_test.go
@@ -0,0 +1,121 @@
+// Copyright 2019 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.
+
+package zlibcut_test
+
+import (
+	"bytes"
+	"compress/zlib"
+	"fmt"
+	"io/ioutil"
+	"strings"
+
+	"github.com/google/wuffs/lib/zlibcut"
+)
+
+func ExampleCut() {
+	const sonnet18 = "" +
+		"Shall I compare thee to a summer’s day?\n" +
+		"Thou art more lovely and more temperate.\n" +
+		"Rough winds do shake the darling buds of May,\n" +
+		"And summer’s lease hath all too short a date.\n" +
+		"Sometime too hot the eye of heaven shines,\n" +
+		"And often is his gold complexion dimmed;\n" +
+		"And every fair from fair sometime declines,\n" +
+		"By chance, or nature’s changing course, untrimmed;\n" +
+		"But thy eternal summer shall not fade,\n" +
+		"Nor lose possession of that fair thou ow’st,\n" +
+		"Nor shall death brag thou wand'rest in his shade,\n" +
+		"When in eternal lines to Time thou grow'st.\n" +
+		"So long as men can breathe, or eyes can see,\n" +
+		"So long lives this, and this gives life to thee.\n"
+
+	if n := len(sonnet18); n != 632 {
+		fmt.Printf("len(sonnet18): got %d, want 632", n)
+		return
+	}
+
+	// Compress the input text, sonnet18.
+	buffer := &bytes.Buffer{}
+	w := zlib.NewWriter(buffer)
+	w.Write([]byte(sonnet18))
+	w.Close()
+	compressed := buffer.Bytes()
+
+	// The exact length of the zlib-compressed form of sonnet18 depends on the
+	// compression algorithm used, which can change from version to version of
+	// the Go standard library. Nonetheless, for a 632 byte input, we expect
+	// the compressed form to be between 300 and 500 bytes.
+	if n := len(compressed); (n < 300) || (500 < n) {
+		fmt.Printf("len(compressed): got %d, want something in [300, 500]", n)
+		return
+	}
+
+	// Cut the 300-or-more bytes to be 200.
+	encodedLen, decodedLen, err := zlibcut.Cut(nil, compressed, 200)
+	if err != nil {
+		fmt.Printf("Cut: %v", err)
+		return
+	}
+
+	// The encodedLen should be equal to or just under the requested 200.
+	cut := compressed[:encodedLen]
+	if n := len(cut); (n < 190) || (200 < n) {
+		fmt.Printf("len(cut): got %d, want something in [190, 200]", n)
+		return
+	}
+
+	// At this point, a real program would write that cut slice somewhere. The
+	// rest of this example verifies that the cut data has the properties we
+	// expect, given the semantics of zlibcut.Cut.
+
+	// Uncompress the cut data. It should be a valid zlib-compressed stream, so
+	// no errors should be encountered.
+	r, err := zlib.NewReader(bytes.NewReader(cut))
+	if err != nil {
+		fmt.Printf("NewReader: %v", err)
+		return
+	}
+	uncompressed, err := ioutil.ReadAll(r)
+	if err != nil {
+		fmt.Printf("ReadAll: %v", err)
+		return
+	}
+	err = r.Close()
+	if err != nil {
+		fmt.Printf("Close: %v", err)
+		return
+	}
+
+	// The uncompressed form of the cut data should be a prefix (of length
+	// decodedLen) of the original input, sonnet18. Again, the exact length
+	// depends on the zlib compression algorithm, but uncompressing 200 or so
+	// bytes should give between 250 and 400 bytes.
+	if n := len(uncompressed); n != decodedLen {
+		fmt.Printf("len(uncompressed): got %d, want %d", n, decodedLen)
+		return
+	} else if (n < 250) || (400 < n) {
+		fmt.Printf("len(uncompressed): got %d, want something in [250, 400]", n)
+		return
+	} else if !strings.HasPrefix(sonnet18, string(uncompressed)) {
+		fmt.Printf("uncompressed was not a prefix of the original input")
+		return
+	}
+
+	// The first two lines of the sonnet take 83 bytes.
+	fmt.Println(string(uncompressed[:83]))
+	// Output:
+	// Shall I compare thee to a summer’s day?
+	// Thou art more lovely and more temperate.
+}
diff --git a/lib/zlibcut/zlibcut.go b/lib/zlibcut/zlibcut.go
index 4f7da1c..6554ea1 100644
--- a/lib/zlibcut/zlibcut.go
+++ b/lib/zlibcut/zlibcut.go
@@ -23,8 +23,6 @@
 package zlibcut
 
 import (
-	"bytes"
-	"compress/flate"
 	"errors"
 	"hash/adler32"
 	"io"
@@ -52,11 +50,16 @@
 // zlib-compressed data, assuming that encoded starts off containing valid
 // zlib-compressed data.
 //
+// If a nil error is returned, then encodedLen <= maxEncodedLen will hold.
+//
 // Decompressing that modified, shorter byte slice produces a prefix (of length
 // decodedLen) of the decompression of the original, longer byte slice.
 //
+// If w is non-nil, that prefix is also written to w. If a non-nil error is
+// returned, incomplete data might still be written to w.
+//
 // It does not necessarily return the largest possible decodedLen.
-func Cut(encoded []byte, maxEncodedLen int) (encodedLen int, decodedLen int, retErr error) {
+func Cut(w io.Writer, encoded []byte, maxEncodedLen int) (encodedLen int, decodedLen int, retErr error) {
 	if len(encoded) < 2 {
 		return 0, 0, errInvalidNotEnoughData
 	}
@@ -85,7 +88,15 @@
 		return 0, 0, errMaxEncodedLenTooSmall
 	}
 
+	hasher := adler32.New()
+	if w == nil {
+		w = hasher
+	} else {
+		w = io.MultiWriter(w, hasher)
+	}
+
 	encodedLen, decodedLen, err := flatecut.Cut(
+		w,
 		encoded[payloadStart:len(encoded)-4],
 		maxEncodedLen-payloadStart-4,
 	)
@@ -93,15 +104,7 @@
 		return 0, 0, err
 	}
 
-	w := adler32.New()
-	r := bytes.NewReader(encoded[payloadStart : payloadStart+encodedLen])
-	if n, err := io.Copy(w, flate.NewReader(r)); err != nil {
-		return 0, 0, err
-	} else if n != int64(decodedLen) {
-		return 0, 0, errInternalInconsistentDecodedLen
-	}
-
-	hash := w.Sum32()
+	hash := hasher.Sum32()
 	hashBytes := encoded[payloadStart+encodedLen : payloadStart+encodedLen+4]
 	hashBytes[0] = uint8(hash >> 24)
 	hashBytes[1] = uint8(hash >> 16)
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index dc00a84..964d7dd 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -191,8 +191,10 @@
 typedef const char* wuffs_base__status;
 
 extern const char* wuffs_base__warning__end_of_data;
+extern const char* wuffs_base__warning__metadata_reported;
 extern const char* wuffs_base__suspension__short_read;
 extern const char* wuffs_base__suspension__short_write;
+extern const char* wuffs_base__error__bad_i_o_position;
 extern const char* wuffs_base__error__bad_argument_length_too_short;
 extern const char* wuffs_base__error__bad_argument;
 extern const char* wuffs_base__error__bad_call_sequence;
@@ -207,6 +209,7 @@
 extern const char* wuffs_base__error__initialize_not_called;
 extern const char* wuffs_base__error__interleaved_coroutine_calls;
 extern const char* wuffs_base__error__not_enough_data;
+extern const char* wuffs_base__error__unsupported_option;
 extern const char* wuffs_base__error__too_much_data;
 
 static inline bool  //
@@ -236,6 +239,16 @@
 
 // --------
 
+// FourCC constants.
+
+// International Color Consortium Profile.
+#define WUFFS_BASE__FOURCC__ICCP 0x49434350
+
+// Extensible Metadata Platform.
+#define WUFFS_BASE__FOURCC__XMP 0x584D5020
+
+// --------
+
 // Flicks are a unit of time. One flick (frame-tick) is 1 / 705_600_000 of a
 // second. See https://github.com/OculusVR/Flicks
 typedef int64_t wuffs_base__flicks;
@@ -509,14 +522,14 @@
   uint32_t max_incl;
 
 #ifdef __cplusplus
-  inline bool is_empty();
-  inline bool equals(wuffs_base__range_ii_u32__struct s);
+  inline bool is_empty() const;
+  inline bool equals(wuffs_base__range_ii_u32__struct s) const;
   inline wuffs_base__range_ii_u32__struct intersect(
-      wuffs_base__range_ii_u32__struct s);
+      wuffs_base__range_ii_u32__struct s) const;
   inline wuffs_base__range_ii_u32__struct unite(
-      wuffs_base__range_ii_u32__struct s);
-  inline bool contains(uint32_t x);
-  inline bool contains_range(wuffs_base__range_ii_u32__struct s);
+      wuffs_base__range_ii_u32__struct s) const;
+  inline bool contains(uint32_t x) const;
+  inline bool contains_range(wuffs_base__range_ii_u32__struct s) const;
 #endif  // __cplusplus
 
 } wuffs_base__range_ii_u32;
@@ -530,12 +543,12 @@
 }
 
 static inline bool  //
-wuffs_base__range_ii_u32__is_empty(wuffs_base__range_ii_u32* r) {
+wuffs_base__range_ii_u32__is_empty(const wuffs_base__range_ii_u32* r) {
   return r->min_incl > r->max_incl;
 }
 
 static inline bool  //
-wuffs_base__range_ii_u32__equals(wuffs_base__range_ii_u32* r,
+wuffs_base__range_ii_u32__equals(const wuffs_base__range_ii_u32* r,
                                  wuffs_base__range_ii_u32 s) {
   return (r->min_incl == s.min_incl && r->max_incl == s.max_incl) ||
          (wuffs_base__range_ii_u32__is_empty(r) &&
@@ -543,7 +556,7 @@
 }
 
 static inline wuffs_base__range_ii_u32  //
-wuffs_base__range_ii_u32__intersect(wuffs_base__range_ii_u32* r,
+wuffs_base__range_ii_u32__intersect(const wuffs_base__range_ii_u32* r,
                                     wuffs_base__range_ii_u32 s) {
   wuffs_base__range_ii_u32 t;
   t.min_incl = wuffs_base__u32__max(r->min_incl, s.min_incl);
@@ -552,7 +565,7 @@
 }
 
 static inline wuffs_base__range_ii_u32  //
-wuffs_base__range_ii_u32__unite(wuffs_base__range_ii_u32* r,
+wuffs_base__range_ii_u32__unite(const wuffs_base__range_ii_u32* r,
                                 wuffs_base__range_ii_u32 s) {
   if (wuffs_base__range_ii_u32__is_empty(r)) {
     return s;
@@ -567,12 +580,13 @@
 }
 
 static inline bool  //
-wuffs_base__range_ii_u32__contains(wuffs_base__range_ii_u32* r, uint32_t x) {
+wuffs_base__range_ii_u32__contains(const wuffs_base__range_ii_u32* r,
+                                   uint32_t x) {
   return (r->min_incl <= x) && (x <= r->max_incl);
 }
 
 static inline bool  //
-wuffs_base__range_ii_u32__contains_range(wuffs_base__range_ii_u32* r,
+wuffs_base__range_ii_u32__contains_range(const wuffs_base__range_ii_u32* r,
                                          wuffs_base__range_ii_u32 s) {
   return wuffs_base__range_ii_u32__equals(
       &s, wuffs_base__range_ii_u32__intersect(r, s));
@@ -581,32 +595,32 @@
 #ifdef __cplusplus
 
 inline bool  //
-wuffs_base__range_ii_u32::is_empty() {
+wuffs_base__range_ii_u32::is_empty() const {
   return wuffs_base__range_ii_u32__is_empty(this);
 }
 
 inline bool  //
-wuffs_base__range_ii_u32::equals(wuffs_base__range_ii_u32 s) {
+wuffs_base__range_ii_u32::equals(wuffs_base__range_ii_u32 s) const {
   return wuffs_base__range_ii_u32__equals(this, s);
 }
 
 inline wuffs_base__range_ii_u32  //
-wuffs_base__range_ii_u32::intersect(wuffs_base__range_ii_u32 s) {
+wuffs_base__range_ii_u32::intersect(wuffs_base__range_ii_u32 s) const {
   return wuffs_base__range_ii_u32__intersect(this, s);
 }
 
 inline wuffs_base__range_ii_u32  //
-wuffs_base__range_ii_u32::unite(wuffs_base__range_ii_u32 s) {
+wuffs_base__range_ii_u32::unite(wuffs_base__range_ii_u32 s) const {
   return wuffs_base__range_ii_u32__unite(this, s);
 }
 
 inline bool  //
-wuffs_base__range_ii_u32::contains(uint32_t x) {
+wuffs_base__range_ii_u32::contains(uint32_t x) const {
   return wuffs_base__range_ii_u32__contains(this, x);
 }
 
 inline bool  //
-wuffs_base__range_ii_u32::contains_range(wuffs_base__range_ii_u32 s) {
+wuffs_base__range_ii_u32::contains_range(wuffs_base__range_ii_u32 s) const {
   return wuffs_base__range_ii_u32__contains_range(this, s);
 }
 
@@ -619,15 +633,15 @@
   uint32_t max_excl;
 
 #ifdef __cplusplus
-  inline bool is_empty();
-  inline bool equals(wuffs_base__range_ie_u32__struct s);
+  inline bool is_empty() const;
+  inline bool equals(wuffs_base__range_ie_u32__struct s) const;
   inline wuffs_base__range_ie_u32__struct intersect(
-      wuffs_base__range_ie_u32__struct s);
+      wuffs_base__range_ie_u32__struct s) const;
   inline wuffs_base__range_ie_u32__struct unite(
-      wuffs_base__range_ie_u32__struct s);
-  inline bool contains(uint32_t x);
-  inline bool contains_range(wuffs_base__range_ie_u32__struct s);
-  inline uint32_t length();
+      wuffs_base__range_ie_u32__struct s) const;
+  inline bool contains(uint32_t x) const;
+  inline bool contains_range(wuffs_base__range_ie_u32__struct s) const;
+  inline uint32_t length() const;
 #endif  // __cplusplus
 
 } wuffs_base__range_ie_u32;
@@ -641,12 +655,12 @@
 }
 
 static inline bool  //
-wuffs_base__range_ie_u32__is_empty(wuffs_base__range_ie_u32* r) {
+wuffs_base__range_ie_u32__is_empty(const wuffs_base__range_ie_u32* r) {
   return r->min_incl >= r->max_excl;
 }
 
 static inline bool  //
-wuffs_base__range_ie_u32__equals(wuffs_base__range_ie_u32* r,
+wuffs_base__range_ie_u32__equals(const wuffs_base__range_ie_u32* r,
                                  wuffs_base__range_ie_u32 s) {
   return (r->min_incl == s.min_incl && r->max_excl == s.max_excl) ||
          (wuffs_base__range_ie_u32__is_empty(r) &&
@@ -654,7 +668,7 @@
 }
 
 static inline wuffs_base__range_ie_u32  //
-wuffs_base__range_ie_u32__intersect(wuffs_base__range_ie_u32* r,
+wuffs_base__range_ie_u32__intersect(const wuffs_base__range_ie_u32* r,
                                     wuffs_base__range_ie_u32 s) {
   wuffs_base__range_ie_u32 t;
   t.min_incl = wuffs_base__u32__max(r->min_incl, s.min_incl);
@@ -663,7 +677,7 @@
 }
 
 static inline wuffs_base__range_ie_u32  //
-wuffs_base__range_ie_u32__unite(wuffs_base__range_ie_u32* r,
+wuffs_base__range_ie_u32__unite(const wuffs_base__range_ie_u32* r,
                                 wuffs_base__range_ie_u32 s) {
   if (wuffs_base__range_ie_u32__is_empty(r)) {
     return s;
@@ -678,56 +692,57 @@
 }
 
 static inline bool  //
-wuffs_base__range_ie_u32__contains(wuffs_base__range_ie_u32* r, uint32_t x) {
+wuffs_base__range_ie_u32__contains(const wuffs_base__range_ie_u32* r,
+                                   uint32_t x) {
   return (r->min_incl <= x) && (x < r->max_excl);
 }
 
 static inline bool  //
-wuffs_base__range_ie_u32__contains_range(wuffs_base__range_ie_u32* r,
+wuffs_base__range_ie_u32__contains_range(const wuffs_base__range_ie_u32* r,
                                          wuffs_base__range_ie_u32 s) {
   return wuffs_base__range_ie_u32__equals(
       &s, wuffs_base__range_ie_u32__intersect(r, s));
 }
 
 static inline uint32_t  //
-wuffs_base__range_ie_u32__length(wuffs_base__range_ie_u32* r) {
+wuffs_base__range_ie_u32__length(const wuffs_base__range_ie_u32* r) {
   return wuffs_base__u32__sat_sub(r->max_excl, r->min_incl);
 }
 
 #ifdef __cplusplus
 
 inline bool  //
-wuffs_base__range_ie_u32::is_empty() {
+wuffs_base__range_ie_u32::is_empty() const {
   return wuffs_base__range_ie_u32__is_empty(this);
 }
 
 inline bool  //
-wuffs_base__range_ie_u32::equals(wuffs_base__range_ie_u32 s) {
+wuffs_base__range_ie_u32::equals(wuffs_base__range_ie_u32 s) const {
   return wuffs_base__range_ie_u32__equals(this, s);
 }
 
 inline wuffs_base__range_ie_u32  //
-wuffs_base__range_ie_u32::intersect(wuffs_base__range_ie_u32 s) {
+wuffs_base__range_ie_u32::intersect(wuffs_base__range_ie_u32 s) const {
   return wuffs_base__range_ie_u32__intersect(this, s);
 }
 
 inline wuffs_base__range_ie_u32  //
-wuffs_base__range_ie_u32::unite(wuffs_base__range_ie_u32 s) {
+wuffs_base__range_ie_u32::unite(wuffs_base__range_ie_u32 s) const {
   return wuffs_base__range_ie_u32__unite(this, s);
 }
 
 inline bool  //
-wuffs_base__range_ie_u32::contains(uint32_t x) {
+wuffs_base__range_ie_u32::contains(uint32_t x) const {
   return wuffs_base__range_ie_u32__contains(this, x);
 }
 
 inline bool  //
-wuffs_base__range_ie_u32::contains_range(wuffs_base__range_ie_u32 s) {
+wuffs_base__range_ie_u32::contains_range(wuffs_base__range_ie_u32 s) const {
   return wuffs_base__range_ie_u32__contains_range(this, s);
 }
 
 inline uint32_t  //
-wuffs_base__range_ie_u32::length() {
+wuffs_base__range_ie_u32::length() const {
   return wuffs_base__range_ie_u32__length(this);
 }
 
@@ -740,14 +755,14 @@
   uint64_t max_incl;
 
 #ifdef __cplusplus
-  inline bool is_empty();
-  inline bool equals(wuffs_base__range_ii_u64__struct s);
+  inline bool is_empty() const;
+  inline bool equals(wuffs_base__range_ii_u64__struct s) const;
   inline wuffs_base__range_ii_u64__struct intersect(
-      wuffs_base__range_ii_u64__struct s);
+      wuffs_base__range_ii_u64__struct s) const;
   inline wuffs_base__range_ii_u64__struct unite(
-      wuffs_base__range_ii_u64__struct s);
-  inline bool contains(uint64_t x);
-  inline bool contains_range(wuffs_base__range_ii_u64__struct s);
+      wuffs_base__range_ii_u64__struct s) const;
+  inline bool contains(uint64_t x) const;
+  inline bool contains_range(wuffs_base__range_ii_u64__struct s) const;
 #endif  // __cplusplus
 
 } wuffs_base__range_ii_u64;
@@ -761,12 +776,12 @@
 }
 
 static inline bool  //
-wuffs_base__range_ii_u64__is_empty(wuffs_base__range_ii_u64* r) {
+wuffs_base__range_ii_u64__is_empty(const wuffs_base__range_ii_u64* r) {
   return r->min_incl > r->max_incl;
 }
 
 static inline bool  //
-wuffs_base__range_ii_u64__equals(wuffs_base__range_ii_u64* r,
+wuffs_base__range_ii_u64__equals(const wuffs_base__range_ii_u64* r,
                                  wuffs_base__range_ii_u64 s) {
   return (r->min_incl == s.min_incl && r->max_incl == s.max_incl) ||
          (wuffs_base__range_ii_u64__is_empty(r) &&
@@ -774,7 +789,7 @@
 }
 
 static inline wuffs_base__range_ii_u64  //
-wuffs_base__range_ii_u64__intersect(wuffs_base__range_ii_u64* r,
+wuffs_base__range_ii_u64__intersect(const wuffs_base__range_ii_u64* r,
                                     wuffs_base__range_ii_u64 s) {
   wuffs_base__range_ii_u64 t;
   t.min_incl = wuffs_base__u64__max(r->min_incl, s.min_incl);
@@ -783,7 +798,7 @@
 }
 
 static inline wuffs_base__range_ii_u64  //
-wuffs_base__range_ii_u64__unite(wuffs_base__range_ii_u64* r,
+wuffs_base__range_ii_u64__unite(const wuffs_base__range_ii_u64* r,
                                 wuffs_base__range_ii_u64 s) {
   if (wuffs_base__range_ii_u64__is_empty(r)) {
     return s;
@@ -798,12 +813,13 @@
 }
 
 static inline bool  //
-wuffs_base__range_ii_u64__contains(wuffs_base__range_ii_u64* r, uint64_t x) {
+wuffs_base__range_ii_u64__contains(const wuffs_base__range_ii_u64* r,
+                                   uint64_t x) {
   return (r->min_incl <= x) && (x <= r->max_incl);
 }
 
 static inline bool  //
-wuffs_base__range_ii_u64__contains_range(wuffs_base__range_ii_u64* r,
+wuffs_base__range_ii_u64__contains_range(const wuffs_base__range_ii_u64* r,
                                          wuffs_base__range_ii_u64 s) {
   return wuffs_base__range_ii_u64__equals(
       &s, wuffs_base__range_ii_u64__intersect(r, s));
@@ -812,32 +828,32 @@
 #ifdef __cplusplus
 
 inline bool  //
-wuffs_base__range_ii_u64::is_empty() {
+wuffs_base__range_ii_u64::is_empty() const {
   return wuffs_base__range_ii_u64__is_empty(this);
 }
 
 inline bool  //
-wuffs_base__range_ii_u64::equals(wuffs_base__range_ii_u64 s) {
+wuffs_base__range_ii_u64::equals(wuffs_base__range_ii_u64 s) const {
   return wuffs_base__range_ii_u64__equals(this, s);
 }
 
 inline wuffs_base__range_ii_u64  //
-wuffs_base__range_ii_u64::intersect(wuffs_base__range_ii_u64 s) {
+wuffs_base__range_ii_u64::intersect(wuffs_base__range_ii_u64 s) const {
   return wuffs_base__range_ii_u64__intersect(this, s);
 }
 
 inline wuffs_base__range_ii_u64  //
-wuffs_base__range_ii_u64::unite(wuffs_base__range_ii_u64 s) {
+wuffs_base__range_ii_u64::unite(wuffs_base__range_ii_u64 s) const {
   return wuffs_base__range_ii_u64__unite(this, s);
 }
 
 inline bool  //
-wuffs_base__range_ii_u64::contains(uint64_t x) {
+wuffs_base__range_ii_u64::contains(uint64_t x) const {
   return wuffs_base__range_ii_u64__contains(this, x);
 }
 
 inline bool  //
-wuffs_base__range_ii_u64::contains_range(wuffs_base__range_ii_u64 s) {
+wuffs_base__range_ii_u64::contains_range(wuffs_base__range_ii_u64 s) const {
   return wuffs_base__range_ii_u64__contains_range(this, s);
 }
 
@@ -850,15 +866,15 @@
   uint64_t max_excl;
 
 #ifdef __cplusplus
-  inline bool is_empty();
-  inline bool equals(wuffs_base__range_ie_u64__struct s);
+  inline bool is_empty() const;
+  inline bool equals(wuffs_base__range_ie_u64__struct s) const;
   inline wuffs_base__range_ie_u64__struct intersect(
-      wuffs_base__range_ie_u64__struct s);
+      wuffs_base__range_ie_u64__struct s) const;
   inline wuffs_base__range_ie_u64__struct unite(
-      wuffs_base__range_ie_u64__struct s);
-  inline bool contains(uint64_t x);
-  inline bool contains_range(wuffs_base__range_ie_u64__struct s);
-  inline uint64_t length();
+      wuffs_base__range_ie_u64__struct s) const;
+  inline bool contains(uint64_t x) const;
+  inline bool contains_range(wuffs_base__range_ie_u64__struct s) const;
+  inline uint64_t length() const;
 #endif  // __cplusplus
 
 } wuffs_base__range_ie_u64;
@@ -872,12 +888,12 @@
 }
 
 static inline bool  //
-wuffs_base__range_ie_u64__is_empty(wuffs_base__range_ie_u64* r) {
+wuffs_base__range_ie_u64__is_empty(const wuffs_base__range_ie_u64* r) {
   return r->min_incl >= r->max_excl;
 }
 
 static inline bool  //
-wuffs_base__range_ie_u64__equals(wuffs_base__range_ie_u64* r,
+wuffs_base__range_ie_u64__equals(const wuffs_base__range_ie_u64* r,
                                  wuffs_base__range_ie_u64 s) {
   return (r->min_incl == s.min_incl && r->max_excl == s.max_excl) ||
          (wuffs_base__range_ie_u64__is_empty(r) &&
@@ -885,7 +901,7 @@
 }
 
 static inline wuffs_base__range_ie_u64  //
-wuffs_base__range_ie_u64__intersect(wuffs_base__range_ie_u64* r,
+wuffs_base__range_ie_u64__intersect(const wuffs_base__range_ie_u64* r,
                                     wuffs_base__range_ie_u64 s) {
   wuffs_base__range_ie_u64 t;
   t.min_incl = wuffs_base__u64__max(r->min_incl, s.min_incl);
@@ -894,7 +910,7 @@
 }
 
 static inline wuffs_base__range_ie_u64  //
-wuffs_base__range_ie_u64__unite(wuffs_base__range_ie_u64* r,
+wuffs_base__range_ie_u64__unite(const wuffs_base__range_ie_u64* r,
                                 wuffs_base__range_ie_u64 s) {
   if (wuffs_base__range_ie_u64__is_empty(r)) {
     return s;
@@ -909,56 +925,57 @@
 }
 
 static inline bool  //
-wuffs_base__range_ie_u64__contains(wuffs_base__range_ie_u64* r, uint64_t x) {
+wuffs_base__range_ie_u64__contains(const wuffs_base__range_ie_u64* r,
+                                   uint64_t x) {
   return (r->min_incl <= x) && (x < r->max_excl);
 }
 
 static inline bool  //
-wuffs_base__range_ie_u64__contains_range(wuffs_base__range_ie_u64* r,
+wuffs_base__range_ie_u64__contains_range(const wuffs_base__range_ie_u64* r,
                                          wuffs_base__range_ie_u64 s) {
   return wuffs_base__range_ie_u64__equals(
       &s, wuffs_base__range_ie_u64__intersect(r, s));
 }
 
 static inline uint64_t  //
-wuffs_base__range_ie_u64__length(wuffs_base__range_ie_u64* r) {
+wuffs_base__range_ie_u64__length(const wuffs_base__range_ie_u64* r) {
   return wuffs_base__u64__sat_sub(r->max_excl, r->min_incl);
 }
 
 #ifdef __cplusplus
 
 inline bool  //
-wuffs_base__range_ie_u64::is_empty() {
+wuffs_base__range_ie_u64::is_empty() const {
   return wuffs_base__range_ie_u64__is_empty(this);
 }
 
 inline bool  //
-wuffs_base__range_ie_u64::equals(wuffs_base__range_ie_u64 s) {
+wuffs_base__range_ie_u64::equals(wuffs_base__range_ie_u64 s) const {
   return wuffs_base__range_ie_u64__equals(this, s);
 }
 
 inline wuffs_base__range_ie_u64  //
-wuffs_base__range_ie_u64::intersect(wuffs_base__range_ie_u64 s) {
+wuffs_base__range_ie_u64::intersect(wuffs_base__range_ie_u64 s) const {
   return wuffs_base__range_ie_u64__intersect(this, s);
 }
 
 inline wuffs_base__range_ie_u64  //
-wuffs_base__range_ie_u64::unite(wuffs_base__range_ie_u64 s) {
+wuffs_base__range_ie_u64::unite(wuffs_base__range_ie_u64 s) const {
   return wuffs_base__range_ie_u64__unite(this, s);
 }
 
 inline bool  //
-wuffs_base__range_ie_u64::contains(uint64_t x) {
+wuffs_base__range_ie_u64::contains(uint64_t x) const {
   return wuffs_base__range_ie_u64__contains(this, x);
 }
 
 inline bool  //
-wuffs_base__range_ie_u64::contains_range(wuffs_base__range_ie_u64 s) {
+wuffs_base__range_ie_u64::contains_range(wuffs_base__range_ie_u64 s) const {
   return wuffs_base__range_ie_u64__contains_range(this, s);
 }
 
 inline uint64_t  //
-wuffs_base__range_ie_u64::length() {
+wuffs_base__range_ie_u64::length() const {
   return wuffs_base__range_ie_u64__length(this);
 }
 
@@ -982,14 +999,14 @@
   uint32_t max_incl_y;
 
 #ifdef __cplusplus
-  inline bool is_empty();
-  inline bool equals(wuffs_base__rect_ii_u32__struct s);
+  inline bool is_empty() const;
+  inline bool equals(wuffs_base__rect_ii_u32__struct s) const;
   inline wuffs_base__rect_ii_u32__struct intersect(
-      wuffs_base__rect_ii_u32__struct s);
+      wuffs_base__rect_ii_u32__struct s) const;
   inline wuffs_base__rect_ii_u32__struct unite(
-      wuffs_base__rect_ii_u32__struct s);
-  inline bool contains(uint32_t x, uint32_t y);
-  inline bool contains_rect(wuffs_base__rect_ii_u32__struct s);
+      wuffs_base__rect_ii_u32__struct s) const;
+  inline bool contains(uint32_t x, uint32_t y) const;
+  inline bool contains_rect(wuffs_base__rect_ii_u32__struct s) const;
 #endif  // __cplusplus
 
 } wuffs_base__rect_ii_u32;
@@ -1008,12 +1025,12 @@
 }
 
 static inline bool  //
-wuffs_base__rect_ii_u32__is_empty(wuffs_base__rect_ii_u32* r) {
+wuffs_base__rect_ii_u32__is_empty(const wuffs_base__rect_ii_u32* r) {
   return (r->min_incl_x > r->max_incl_x) || (r->min_incl_y > r->max_incl_y);
 }
 
 static inline bool  //
-wuffs_base__rect_ii_u32__equals(wuffs_base__rect_ii_u32* r,
+wuffs_base__rect_ii_u32__equals(const wuffs_base__rect_ii_u32* r,
                                 wuffs_base__rect_ii_u32 s) {
   return (r->min_incl_x == s.min_incl_x && r->min_incl_y == s.min_incl_y &&
           r->max_incl_x == s.max_incl_x && r->max_incl_y == s.max_incl_y) ||
@@ -1022,7 +1039,7 @@
 }
 
 static inline wuffs_base__rect_ii_u32  //
-wuffs_base__rect_ii_u32__intersect(wuffs_base__rect_ii_u32* r,
+wuffs_base__rect_ii_u32__intersect(const wuffs_base__rect_ii_u32* r,
                                    wuffs_base__rect_ii_u32 s) {
   wuffs_base__rect_ii_u32 t;
   t.min_incl_x = wuffs_base__u32__max(r->min_incl_x, s.min_incl_x);
@@ -1033,7 +1050,7 @@
 }
 
 static inline wuffs_base__rect_ii_u32  //
-wuffs_base__rect_ii_u32__unite(wuffs_base__rect_ii_u32* r,
+wuffs_base__rect_ii_u32__unite(const wuffs_base__rect_ii_u32* r,
                                wuffs_base__rect_ii_u32 s) {
   if (wuffs_base__rect_ii_u32__is_empty(r)) {
     return s;
@@ -1050,7 +1067,7 @@
 }
 
 static inline bool  //
-wuffs_base__rect_ii_u32__contains(wuffs_base__rect_ii_u32* r,
+wuffs_base__rect_ii_u32__contains(const wuffs_base__rect_ii_u32* r,
                                   uint32_t x,
                                   uint32_t y) {
   return (r->min_incl_x <= x) && (x <= r->max_incl_x) && (r->min_incl_y <= y) &&
@@ -1058,7 +1075,7 @@
 }
 
 static inline bool  //
-wuffs_base__rect_ii_u32__contains_rect(wuffs_base__rect_ii_u32* r,
+wuffs_base__rect_ii_u32__contains_rect(const wuffs_base__rect_ii_u32* r,
                                        wuffs_base__rect_ii_u32 s) {
   return wuffs_base__rect_ii_u32__equals(
       &s, wuffs_base__rect_ii_u32__intersect(r, s));
@@ -1067,32 +1084,32 @@
 #ifdef __cplusplus
 
 inline bool  //
-wuffs_base__rect_ii_u32::is_empty() {
+wuffs_base__rect_ii_u32::is_empty() const {
   return wuffs_base__rect_ii_u32__is_empty(this);
 }
 
 inline bool  //
-wuffs_base__rect_ii_u32::equals(wuffs_base__rect_ii_u32 s) {
+wuffs_base__rect_ii_u32::equals(wuffs_base__rect_ii_u32 s) const {
   return wuffs_base__rect_ii_u32__equals(this, s);
 }
 
 inline wuffs_base__rect_ii_u32  //
-wuffs_base__rect_ii_u32::intersect(wuffs_base__rect_ii_u32 s) {
+wuffs_base__rect_ii_u32::intersect(wuffs_base__rect_ii_u32 s) const {
   return wuffs_base__rect_ii_u32__intersect(this, s);
 }
 
 inline wuffs_base__rect_ii_u32  //
-wuffs_base__rect_ii_u32::unite(wuffs_base__rect_ii_u32 s) {
+wuffs_base__rect_ii_u32::unite(wuffs_base__rect_ii_u32 s) const {
   return wuffs_base__rect_ii_u32__unite(this, s);
 }
 
 inline bool  //
-wuffs_base__rect_ii_u32::contains(uint32_t x, uint32_t y) {
+wuffs_base__rect_ii_u32::contains(uint32_t x, uint32_t y) const {
   return wuffs_base__rect_ii_u32__contains(this, x, y);
 }
 
 inline bool  //
-wuffs_base__rect_ii_u32::contains_rect(wuffs_base__rect_ii_u32 s) {
+wuffs_base__rect_ii_u32::contains_rect(wuffs_base__rect_ii_u32 s) const {
   return wuffs_base__rect_ii_u32__contains_rect(this, s);
 }
 
@@ -1117,16 +1134,16 @@
   uint32_t max_excl_y;
 
 #ifdef __cplusplus
-  inline bool is_empty();
-  inline bool equals(wuffs_base__rect_ie_u32__struct s);
+  inline bool is_empty() const;
+  inline bool equals(wuffs_base__rect_ie_u32__struct s) const;
   inline wuffs_base__rect_ie_u32__struct intersect(
-      wuffs_base__rect_ie_u32__struct s);
+      wuffs_base__rect_ie_u32__struct s) const;
   inline wuffs_base__rect_ie_u32__struct unite(
-      wuffs_base__rect_ie_u32__struct s);
-  inline bool contains(uint32_t x, uint32_t y);
-  inline bool contains_rect(wuffs_base__rect_ie_u32__struct s);
-  inline uint32_t width();
-  inline uint32_t height();
+      wuffs_base__rect_ie_u32__struct s) const;
+  inline bool contains(uint32_t x, uint32_t y) const;
+  inline bool contains_rect(wuffs_base__rect_ie_u32__struct s) const;
+  inline uint32_t width() const;
+  inline uint32_t height() const;
 #endif  // __cplusplus
 
 } wuffs_base__rect_ie_u32;
@@ -1145,12 +1162,12 @@
 }
 
 static inline bool  //
-wuffs_base__rect_ie_u32__is_empty(wuffs_base__rect_ie_u32* r) {
+wuffs_base__rect_ie_u32__is_empty(const wuffs_base__rect_ie_u32* r) {
   return (r->min_incl_x >= r->max_excl_x) || (r->min_incl_y >= r->max_excl_y);
 }
 
 static inline bool  //
-wuffs_base__rect_ie_u32__equals(wuffs_base__rect_ie_u32* r,
+wuffs_base__rect_ie_u32__equals(const wuffs_base__rect_ie_u32* r,
                                 wuffs_base__rect_ie_u32 s) {
   return (r->min_incl_x == s.min_incl_x && r->min_incl_y == s.min_incl_y &&
           r->max_excl_x == s.max_excl_x && r->max_excl_y == s.max_excl_y) ||
@@ -1159,7 +1176,7 @@
 }
 
 static inline wuffs_base__rect_ie_u32  //
-wuffs_base__rect_ie_u32__intersect(wuffs_base__rect_ie_u32* r,
+wuffs_base__rect_ie_u32__intersect(const wuffs_base__rect_ie_u32* r,
                                    wuffs_base__rect_ie_u32 s) {
   wuffs_base__rect_ie_u32 t;
   t.min_incl_x = wuffs_base__u32__max(r->min_incl_x, s.min_incl_x);
@@ -1170,7 +1187,7 @@
 }
 
 static inline wuffs_base__rect_ie_u32  //
-wuffs_base__rect_ie_u32__unite(wuffs_base__rect_ie_u32* r,
+wuffs_base__rect_ie_u32__unite(const wuffs_base__rect_ie_u32* r,
                                wuffs_base__rect_ie_u32 s) {
   if (wuffs_base__rect_ie_u32__is_empty(r)) {
     return s;
@@ -1187,7 +1204,7 @@
 }
 
 static inline bool  //
-wuffs_base__rect_ie_u32__contains(wuffs_base__rect_ie_u32* r,
+wuffs_base__rect_ie_u32__contains(const wuffs_base__rect_ie_u32* r,
                                   uint32_t x,
                                   uint32_t y) {
   return (r->min_incl_x <= x) && (x < r->max_excl_x) && (r->min_incl_y <= y) &&
@@ -1195,95 +1212,69 @@
 }
 
 static inline bool  //
-wuffs_base__rect_ie_u32__contains_rect(wuffs_base__rect_ie_u32* r,
+wuffs_base__rect_ie_u32__contains_rect(const wuffs_base__rect_ie_u32* r,
                                        wuffs_base__rect_ie_u32 s) {
   return wuffs_base__rect_ie_u32__equals(
       &s, wuffs_base__rect_ie_u32__intersect(r, s));
 }
 
 static inline uint32_t  //
-wuffs_base__rect_ie_u32__width(wuffs_base__rect_ie_u32* r) {
+wuffs_base__rect_ie_u32__width(const wuffs_base__rect_ie_u32* r) {
   return wuffs_base__u32__sat_sub(r->max_excl_x, r->min_incl_x);
 }
 
 static inline uint32_t  //
-wuffs_base__rect_ie_u32__height(wuffs_base__rect_ie_u32* r) {
+wuffs_base__rect_ie_u32__height(const wuffs_base__rect_ie_u32* r) {
   return wuffs_base__u32__sat_sub(r->max_excl_y, r->min_incl_y);
 }
 
 #ifdef __cplusplus
 
 inline bool  //
-wuffs_base__rect_ie_u32::is_empty() {
+wuffs_base__rect_ie_u32::is_empty() const {
   return wuffs_base__rect_ie_u32__is_empty(this);
 }
 
 inline bool  //
-wuffs_base__rect_ie_u32::equals(wuffs_base__rect_ie_u32 s) {
+wuffs_base__rect_ie_u32::equals(wuffs_base__rect_ie_u32 s) const {
   return wuffs_base__rect_ie_u32__equals(this, s);
 }
 
 inline wuffs_base__rect_ie_u32  //
-wuffs_base__rect_ie_u32::intersect(wuffs_base__rect_ie_u32 s) {
+wuffs_base__rect_ie_u32::intersect(wuffs_base__rect_ie_u32 s) const {
   return wuffs_base__rect_ie_u32__intersect(this, s);
 }
 
 inline wuffs_base__rect_ie_u32  //
-wuffs_base__rect_ie_u32::unite(wuffs_base__rect_ie_u32 s) {
+wuffs_base__rect_ie_u32::unite(wuffs_base__rect_ie_u32 s) const {
   return wuffs_base__rect_ie_u32__unite(this, s);
 }
 
 inline bool  //
-wuffs_base__rect_ie_u32::contains(uint32_t x, uint32_t y) {
+wuffs_base__rect_ie_u32::contains(uint32_t x, uint32_t y) const {
   return wuffs_base__rect_ie_u32__contains(this, x, y);
 }
 
 inline bool  //
-wuffs_base__rect_ie_u32::contains_rect(wuffs_base__rect_ie_u32 s) {
+wuffs_base__rect_ie_u32::contains_rect(wuffs_base__rect_ie_u32 s) const {
   return wuffs_base__rect_ie_u32__contains_rect(this, s);
 }
 
 inline uint32_t  //
-wuffs_base__rect_ie_u32::width() {
+wuffs_base__rect_ie_u32::width() const {
   return wuffs_base__rect_ie_u32__width(this);
 }
 
 inline uint32_t  //
-wuffs_base__rect_ie_u32::height() {
+wuffs_base__rect_ie_u32::height() const {
   return wuffs_base__rect_ie_u32__height(this);
 }
 
 #endif  // __cplusplus
 
 // ---------------- I/O
-
-struct wuffs_base__io_buffer__struct;
-
-typedef struct {
-  // Do not access the private_impl's fields directly. There is no API/ABI
-  // compatibility or safety guarantee if you do so.
-  struct {
-    struct wuffs_base__io_buffer__struct* buf;
-    // The bounds values are typically NULL, when created by the Wuffs public
-    // API. NULL means that the callee substitutes the implicit bounds derived
-    // from buf.
-    uint8_t* mark;
-    uint8_t* limit;
-  } private_impl;
-} wuffs_base__io_reader;
-
-typedef struct {
-  // Do not access the private_impl's fields directly. There is no API/ABI
-  // compatibility or safety guarantee if you do so.
-  struct {
-    struct wuffs_base__io_buffer__struct* buf;
-    // The bounds values are typically NULL, when created by the Wuffs public
-    // API. NULL means that the callee substitutes the implicit bounds derived
-    // from buf.
-    uint8_t* mark;
-    uint8_t* limit;
-  } private_impl;
-} wuffs_base__io_writer;
+//
+// See (/doc/note/io-input-output.md).
 
 // wuffs_base__io_buffer_meta is the metadata for a wuffs_base__io_buffer's
 // data.
@@ -1304,10 +1295,12 @@
 
 #ifdef __cplusplus
   inline void compact();
-  inline wuffs_base__io_reader reader();
-  inline wuffs_base__io_writer writer();
-  inline uint64_t reader_io_position();
-  inline uint64_t writer_io_position();
+  inline wuffs_base__io_buffer__struct* reader();  // Deprecated.
+  inline wuffs_base__io_buffer__struct* writer();  // Deprecated.
+  inline uint64_t reader_available() const;
+  inline uint64_t reader_io_position() const;
+  inline uint64_t writer_available() const;
+  inline uint64_t writer_io_position() const;
 #endif  // __cplusplus
 
 } wuffs_base__io_buffer;
@@ -1356,24 +1349,6 @@
   return ret;
 }
 
-static inline wuffs_base__io_reader  //
-wuffs_base__null_io_reader() {
-  wuffs_base__io_reader ret;
-  ret.private_impl.buf = NULL;
-  ret.private_impl.mark = NULL;
-  ret.private_impl.limit = NULL;
-  return ret;
-}
-
-static inline wuffs_base__io_writer  //
-wuffs_base__null_io_writer() {
-  wuffs_base__io_writer ret;
-  ret.private_impl.buf = NULL;
-  ret.private_impl.mark = NULL;
-  ret.private_impl.limit = NULL;
-  return ret;
-}
-
 // wuffs_base__io_buffer__compact moves any written but unread bytes to the
 // start of the buffer.
 static inline void  //
@@ -1390,31 +1365,23 @@
   buf->meta.ri = 0;
 }
 
-static inline wuffs_base__io_reader  //
-wuffs_base__io_buffer__reader(wuffs_base__io_buffer* buf) {
-  wuffs_base__io_reader ret;
-  ret.private_impl.buf = buf;
-  ret.private_impl.mark = NULL;
-  ret.private_impl.limit = NULL;
-  return ret;
-}
-
-static inline wuffs_base__io_writer  //
-wuffs_base__io_buffer__writer(wuffs_base__io_buffer* buf) {
-  wuffs_base__io_writer ret;
-  ret.private_impl.buf = buf;
-  ret.private_impl.mark = NULL;
-  ret.private_impl.limit = NULL;
-  return ret;
+static inline uint64_t  //
+wuffs_base__io_buffer__reader_available(const wuffs_base__io_buffer* buf) {
+  return buf ? buf->meta.wi - buf->meta.ri : 0;
 }
 
 static inline uint64_t  //
-wuffs_base__io_buffer__reader_io_position(wuffs_base__io_buffer* buf) {
+wuffs_base__io_buffer__reader_io_position(const wuffs_base__io_buffer* buf) {
   return buf ? wuffs_base__u64__sat_add(buf->meta.pos, buf->meta.ri) : 0;
 }
 
 static inline uint64_t  //
-wuffs_base__io_buffer__writer_io_position(wuffs_base__io_buffer* buf) {
+wuffs_base__io_buffer__writer_available(const wuffs_base__io_buffer* buf) {
+  return buf ? buf->data.len - buf->meta.wi : 0;
+}
+
+static inline uint64_t  //
+wuffs_base__io_buffer__writer_io_position(const wuffs_base__io_buffer* buf) {
   return buf ? wuffs_base__u64__sat_add(buf->meta.pos, buf->meta.wi) : 0;
 }
 
@@ -1425,23 +1392,33 @@
   wuffs_base__io_buffer__compact(this);
 }
 
-inline wuffs_base__io_reader  //
+inline wuffs_base__io_buffer*  //
 wuffs_base__io_buffer__struct::reader() {
-  return wuffs_base__io_buffer__reader(this);
+  return this;
 }
 
-inline wuffs_base__io_writer  //
+inline wuffs_base__io_buffer*  //
 wuffs_base__io_buffer__struct::writer() {
-  return wuffs_base__io_buffer__writer(this);
+  return this;
 }
 
 inline uint64_t  //
-wuffs_base__io_buffer__struct::reader_io_position() {
+wuffs_base__io_buffer__struct::reader_available() const {
+  return wuffs_base__io_buffer__reader_available(this);
+}
+
+inline uint64_t  //
+wuffs_base__io_buffer__struct::reader_io_position() const {
   return wuffs_base__io_buffer__reader_io_position(this);
 }
 
 inline uint64_t  //
-wuffs_base__io_buffer__struct::writer_io_position() {
+wuffs_base__io_buffer__struct::writer_available() const {
+  return wuffs_base__io_buffer__writer_available(this);
+}
+
+inline uint64_t  //
+wuffs_base__io_buffer__struct::writer_io_position() const {
   return wuffs_base__io_buffer__writer_io_position(this);
 }
 
@@ -1525,9 +1502,9 @@
 //  - bit        20 indicates big-endian/MSB-first (as opposed to little/LSB).
 //  - bit        19 indicates floating point (as opposed to integer).
 //  - bit        18 indicates palette-indexed. The number-of-planes (the next
-//                  field) will be zero, as the format is considered packed,
+//                  field) will be 0, as the format is considered interleaved,
 //                  but the 8-bit N-BGRA color data is stored in plane 3.
-//  - bits 17 .. 16 are the number of planes, minus 1. Zero means packed.
+//  - bits 17 .. 16 are the number of planes, minus 1. Zero means interleaved.
 //  - bits 15 .. 12 encodes the number of bits (depth) in the 3rd channel.
 //  - bits 11 ..  8 encodes the number of bits (depth) in the 2nd channel.
 //  - bits  7 ..  4 encodes the number of bits (depth) in the 1st channel.
@@ -1544,14 +1521,14 @@
 // channels: cyan, magenta, yellow, black).
 //
 // For direct formats with N > 1 channels, those channels can be laid out in
-// either 1 (packed) or N (planar) planes. For example, RGBA data is usually
-// packed, but YCbCr data is usually planar, due to chroma subsampling (for
-// details, see the wuffs_base__pixel_subsampling type).
+// either 1 (interleaved) or N (planar) planes. For example, RGBA data is
+// usually interleaved, but YCbCr data is usually planar, due to chroma
+// subsampling (for details, see the wuffs_base__pixel_subsampling type).
 //
 // For indexed formats, the palette (always 256 × 4 bytes) holds 8 bits per
 // channel non-alpha-premultiplied BGRA color data. There is only 1 plane (for
-// the index), as the format is considered packed. Plane 0 holds the per-pixel
-// indices. Plane 3 is re-purposed to hold the per-index colors.
+// the index), as the format is considered interleaved. Plane 0 holds the
+// per-pixel indices. Plane 3 is re-purposed to hold the per-index colors.
 //
 // The color field is encoded in 3 bits:
 //  - 0 means                   A (Alpha).
@@ -1565,8 +1542,8 @@
 //
 // In Wuffs, channels are given in memory order (also known as byte order),
 // regardless of endianness, since the C type for the pixel data is an array of
-// bytes, not an array of uint32_t. For example, packed BGRA with 8 bits per
-// channel means that the bytes in memory are always Blue, Green, Red then
+// bytes, not an array of uint32_t. For example, interleaved BGRA with 8 bits
+// per channel means that the bytes in memory are always Blue, Green, Red then
 // Alpha. On big-endian systems, that is the uint32_t 0xBBGGRRAA. On
 // little-endian, 0xAARRGGBB.
 //
@@ -1599,15 +1576,15 @@
 //
 // For example, wuffs_base__pixel_format 0x5510BBBB is a natural format for
 // decoding a PNG image - network byte order (also known as big-endian),
-// packed, non-premultiplied alpha - that happens to be 16-bit-depth truecolor
-// with alpha (RGBA). In memory order:
+// interleaved, non-premultiplied alpha - that happens to be 16-bit-depth
+// truecolor with alpha (RGBA). In memory order:
 //
 //  ptr+0  ptr+1  ptr+2  ptr+3  ptr+4  ptr+5  ptr+6  ptr+7
 //  Rhi    Rlo    Ghi    Glo    Bhi    Blo    Ahi    Alo
 //
 // For example, the value wuffs_base__pixel_format 0x40000565 means BGR with no
-// alpha or padding, 5/6/5 bits for blue/green/red, packed 2 bytes per pixel,
-// laid out LSB-first in memory order:
+// alpha or padding, 5/6/5 bits for blue/green/red, interleaved 2 bytes per
+// pixel, laid out LSB-first in memory order:
 //
 //  ptr+0...........  ptr+1...........
 //  MSB          LSB  MSB          LSB
@@ -1692,8 +1669,8 @@
   return f != 0;
 }
 
-// wuffs_base__pixel_format__bits_per_pixel returns, for packed pixel formats,
-// the number of bits per pixel. It returns 0 for planar pixel formats.
+// wuffs_base__pixel_format__bits_per_pixel returns the number of bits per
+// pixel for interleaved pixel formats, and returns 0 for planar pixel formats.
 static inline uint32_t  //
 wuffs_base__pixel_format__bits_per_pixel(wuffs_base__pixel_format f) {
   if (((f >> 16) & 0x03) != 0) {
@@ -1711,7 +1688,7 @@
 }
 
 static inline bool  //
-wuffs_base__pixel_format__is_packed(wuffs_base__pixel_format f) {
+wuffs_base__pixel_format__is_interleaved(wuffs_base__pixel_format f) {
   return ((f >> 16) & 0x03) == 0;
 }
 
@@ -1737,7 +1714,7 @@
 // plane p. For a depth of 8 bits (1 byte), the p'th plane's sample starts at
 // (planes[p].ptr + (j * planes[p].stride) + i).
 //
-// For packed pixel formats, the mapping is trivial: i = x and j = y. For
+// For interleaved pixel formats, the mapping is trivial: i = x and j = y. For
 // planar pixel formats, the mapping can differ due to chroma subsampling. For
 // example, consider a three plane YCbCr pixel format with 4:2:2 subsampling.
 // For the luma (Y) channel, there is one sample for every pixel, but for the
@@ -1828,13 +1805,13 @@
                   uint32_t width,
                   uint32_t height);
   inline void invalidate();
-  inline bool is_valid();
-  inline wuffs_base__pixel_format pixel_format();
-  inline wuffs_base__pixel_subsampling pixel_subsampling();
-  inline wuffs_base__rect_ie_u32 bounds();
-  inline uint32_t width();
-  inline uint32_t height();
-  inline uint64_t pixbuf_len();
+  inline bool is_valid() const;
+  inline wuffs_base__pixel_format pixel_format() const;
+  inline wuffs_base__pixel_subsampling pixel_subsampling() const;
+  inline wuffs_base__rect_ie_u32 bounds() const;
+  inline uint32_t width() const;
+  inline uint32_t height() const;
+  inline uint64_t pixbuf_len() const;
 #endif  // __cplusplus
 
 } wuffs_base__pixel_config;
@@ -1888,22 +1865,22 @@
 }
 
 static inline bool  //
-wuffs_base__pixel_config__is_valid(wuffs_base__pixel_config* c) {
+wuffs_base__pixel_config__is_valid(const wuffs_base__pixel_config* c) {
   return c && c->private_impl.pixfmt;
 }
 
 static inline wuffs_base__pixel_format  //
-wuffs_base__pixel_config__pixel_format(wuffs_base__pixel_config* c) {
+wuffs_base__pixel_config__pixel_format(const wuffs_base__pixel_config* c) {
   return c ? c->private_impl.pixfmt : 0;
 }
 
 static inline wuffs_base__pixel_subsampling  //
-wuffs_base__pixel_config__pixel_subsampling(wuffs_base__pixel_config* c) {
+wuffs_base__pixel_config__pixel_subsampling(const wuffs_base__pixel_config* c) {
   return c ? c->private_impl.pixsub : 0;
 }
 
 static inline wuffs_base__rect_ie_u32  //
-wuffs_base__pixel_config__bounds(wuffs_base__pixel_config* c) {
+wuffs_base__pixel_config__bounds(const wuffs_base__pixel_config* c) {
   if (c) {
     wuffs_base__rect_ie_u32 ret;
     ret.min_incl_x = 0;
@@ -1922,20 +1899,20 @@
 }
 
 static inline uint32_t  //
-wuffs_base__pixel_config__width(wuffs_base__pixel_config* c) {
+wuffs_base__pixel_config__width(const wuffs_base__pixel_config* c) {
   return c ? c->private_impl.width : 0;
 }
 
 static inline uint32_t  //
-wuffs_base__pixel_config__height(wuffs_base__pixel_config* c) {
+wuffs_base__pixel_config__height(const wuffs_base__pixel_config* c) {
   return c ? c->private_impl.height : 0;
 }
 
-// TODO: this is the right API for planar (not packed) pixbufs? Should it allow
-// decoding into a color model different from the format's intrinsic one? For
-// example, decoding a JPEG image straight to RGBA instead of to YCbCr?
+// TODO: this is the right API for planar (not interleaved) pixbufs? Should it
+// allow decoding into a color model different from the format's intrinsic one?
+// For example, decoding a JPEG image straight to RGBA instead of to YCbCr?
 static inline uint64_t  //
-wuffs_base__pixel_config__pixbuf_len(wuffs_base__pixel_config* c) {
+wuffs_base__pixel_config__pixbuf_len(const wuffs_base__pixel_config* c) {
   if (!c) {
     return 0;
   }
@@ -1984,37 +1961,37 @@
 }
 
 inline bool  //
-wuffs_base__pixel_config::is_valid() {
+wuffs_base__pixel_config::is_valid() const {
   return wuffs_base__pixel_config__is_valid(this);
 }
 
 inline wuffs_base__pixel_format  //
-wuffs_base__pixel_config::pixel_format() {
+wuffs_base__pixel_config::pixel_format() const {
   return wuffs_base__pixel_config__pixel_format(this);
 }
 
 inline wuffs_base__pixel_subsampling  //
-wuffs_base__pixel_config::pixel_subsampling() {
+wuffs_base__pixel_config::pixel_subsampling() const {
   return wuffs_base__pixel_config__pixel_subsampling(this);
 }
 
 inline wuffs_base__rect_ie_u32  //
-wuffs_base__pixel_config::bounds() {
+wuffs_base__pixel_config::bounds() const {
   return wuffs_base__pixel_config__bounds(this);
 }
 
 inline uint32_t  //
-wuffs_base__pixel_config::width() {
+wuffs_base__pixel_config::width() const {
   return wuffs_base__pixel_config__width(this);
 }
 
 inline uint32_t  //
-wuffs_base__pixel_config::height() {
+wuffs_base__pixel_config::height() const {
   return wuffs_base__pixel_config__height(this);
 }
 
 inline uint64_t  //
-wuffs_base__pixel_config::pixbuf_len() {
+wuffs_base__pixel_config::pixbuf_len() const {
   return wuffs_base__pixel_config__pixbuf_len(this);
 }
 
@@ -2040,9 +2017,9 @@
                   uint64_t first_frame_io_position,
                   bool first_frame_is_opaque);
   inline void invalidate();
-  inline bool is_valid();
-  inline uint64_t first_frame_io_position();
-  inline bool first_frame_is_opaque();
+  inline bool is_valid() const;
+  inline uint64_t first_frame_io_position() const;
+  inline bool first_frame_is_opaque() const;
 #endif  // __cplusplus
 
 } wuffs_base__image_config;
@@ -2099,17 +2076,19 @@
 }
 
 static inline bool  //
-wuffs_base__image_config__is_valid(wuffs_base__image_config* c) {
+wuffs_base__image_config__is_valid(const wuffs_base__image_config* c) {
   return c && wuffs_base__pixel_config__is_valid(&(c->pixcfg));
 }
 
 static inline uint64_t  //
-wuffs_base__image_config__first_frame_io_position(wuffs_base__image_config* c) {
+wuffs_base__image_config__first_frame_io_position(
+    const wuffs_base__image_config* c) {
   return c ? c->private_impl.first_frame_io_position : 0;
 }
 
 static inline bool  //
-wuffs_base__image_config__first_frame_is_opaque(wuffs_base__image_config* c) {
+wuffs_base__image_config__first_frame_is_opaque(
+    const wuffs_base__image_config* c) {
   return c ? c->private_impl.first_frame_is_opaque : false;
 }
 
@@ -2132,17 +2111,17 @@
 }
 
 inline bool  //
-wuffs_base__image_config::is_valid() {
+wuffs_base__image_config::is_valid() const {
   return wuffs_base__image_config__is_valid(this);
 }
 
 inline uint64_t  //
-wuffs_base__image_config::first_frame_io_position() {
+wuffs_base__image_config::first_frame_io_position() const {
   return wuffs_base__image_config__first_frame_io_position(this);
 }
 
 inline bool  //
-wuffs_base__image_config::first_frame_is_opaque() {
+wuffs_base__image_config::first_frame_is_opaque() const {
   return wuffs_base__image_config__first_frame_is_opaque(this);
 }
 
@@ -2198,6 +2177,7 @@
     uint64_t io_position;
     wuffs_base__animation_blend blend;
     wuffs_base__animation_disposal disposal;
+    wuffs_base__color_u32_argb_premul background_color;
   } private_impl;
 
 #ifdef __cplusplus
@@ -2206,15 +2186,17 @@
                      uint64_t index,
                      uint64_t io_position,
                      wuffs_base__animation_blend blend,
-                     wuffs_base__animation_disposal disposal);
-  inline wuffs_base__rect_ie_u32 bounds();
-  inline uint32_t width();
-  inline uint32_t height();
-  inline wuffs_base__flicks duration();
-  inline uint64_t index();
-  inline uint64_t io_position();
-  inline wuffs_base__animation_blend blend();
-  inline wuffs_base__animation_disposal disposal();
+                     wuffs_base__animation_disposal disposal,
+                     wuffs_base__color_u32_argb_premul background_color);
+  inline wuffs_base__rect_ie_u32 bounds() const;
+  inline uint32_t width() const;
+  inline uint32_t height() const;
+  inline wuffs_base__flicks duration() const;
+  inline uint64_t index() const;
+  inline uint64_t io_position() const;
+  inline wuffs_base__animation_blend blend() const;
+  inline wuffs_base__animation_disposal disposal() const;
+  inline wuffs_base__color_u32_argb_premul background_color() const;
 #endif  // __cplusplus
 
 } wuffs_base__frame_config;
@@ -2232,13 +2214,15 @@
 }
 
 static inline void  //
-wuffs_base__frame_config__update(wuffs_base__frame_config* c,
-                                 wuffs_base__rect_ie_u32 bounds,
-                                 wuffs_base__flicks duration,
-                                 uint64_t index,
-                                 uint64_t io_position,
-                                 wuffs_base__animation_blend blend,
-                                 wuffs_base__animation_disposal disposal) {
+wuffs_base__frame_config__update(
+    wuffs_base__frame_config* c,
+    wuffs_base__rect_ie_u32 bounds,
+    wuffs_base__flicks duration,
+    uint64_t index,
+    uint64_t io_position,
+    wuffs_base__animation_blend blend,
+    wuffs_base__animation_disposal disposal,
+    wuffs_base__color_u32_argb_premul background_color) {
   if (!c) {
     return;
   }
@@ -2249,10 +2233,11 @@
   c->private_impl.io_position = io_position;
   c->private_impl.blend = blend;
   c->private_impl.disposal = disposal;
+  c->private_impl.background_color = background_color;
 }
 
 static inline wuffs_base__rect_ie_u32  //
-wuffs_base__frame_config__bounds(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__bounds(const wuffs_base__frame_config* c) {
   if (c) {
     return c->private_impl.bounds;
   }
@@ -2266,103 +2251,115 @@
 }
 
 static inline uint32_t  //
-wuffs_base__frame_config__width(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__width(const wuffs_base__frame_config* c) {
   return c ? wuffs_base__rect_ie_u32__width(&c->private_impl.bounds) : 0;
 }
 
 static inline uint32_t  //
-wuffs_base__frame_config__height(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__height(const wuffs_base__frame_config* c) {
   return c ? wuffs_base__rect_ie_u32__height(&c->private_impl.bounds) : 0;
 }
 
 // wuffs_base__frame_config__duration returns the amount of time to display
 // this frame. Zero means to display forever - a still (non-animated) image.
 static inline wuffs_base__flicks  //
-wuffs_base__frame_config__duration(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__duration(const wuffs_base__frame_config* c) {
   return c ? c->private_impl.duration : 0;
 }
 
 // wuffs_base__frame_config__index returns the index of this frame. The first
 // frame in an image has index 0, the second frame has index 1, and so on.
 static inline uint64_t  //
-wuffs_base__frame_config__index(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__index(const wuffs_base__frame_config* c) {
   return c ? c->private_impl.index : 0;
 }
 
 // wuffs_base__frame_config__io_position returns the I/O stream position before
 // the frame config.
 static inline uint64_t  //
-wuffs_base__frame_config__io_position(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__io_position(const wuffs_base__frame_config* c) {
   return c ? c->private_impl.io_position : 0;
 }
 
 // wuffs_base__frame_config__blend returns, for an animated image, how to blend
 // the transparent pixels of this frame with the existing canvas.
 static inline wuffs_base__animation_blend  //
-wuffs_base__frame_config__blend(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__blend(const wuffs_base__frame_config* c) {
   return c ? c->private_impl.blend : 0;
 }
 
 // wuffs_base__frame_config__disposal returns, for an animated image, how to
 // dispose of this frame after displaying it.
 static inline wuffs_base__animation_disposal  //
-wuffs_base__frame_config__disposal(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__disposal(const wuffs_base__frame_config* c) {
   return c ? c->private_impl.disposal : 0;
 }
 
+static inline wuffs_base__color_u32_argb_premul  //
+wuffs_base__frame_config__background_color(const wuffs_base__frame_config* c) {
+  return c ? c->private_impl.background_color : 0;
+}
+
 #ifdef __cplusplus
 
 inline void  //
-wuffs_base__frame_config::update(wuffs_base__rect_ie_u32 bounds,
-                                 wuffs_base__flicks duration,
-                                 uint64_t index,
-                                 uint64_t io_position,
-                                 wuffs_base__animation_blend blend,
-                                 wuffs_base__animation_disposal disposal) {
+wuffs_base__frame_config::update(
+    wuffs_base__rect_ie_u32 bounds,
+    wuffs_base__flicks duration,
+    uint64_t index,
+    uint64_t io_position,
+    wuffs_base__animation_blend blend,
+    wuffs_base__animation_disposal disposal,
+    wuffs_base__color_u32_argb_premul background_color) {
   wuffs_base__frame_config__update(this, bounds, duration, index, io_position,
-                                   blend, disposal);
+                                   blend, disposal, background_color);
 }
 
 inline wuffs_base__rect_ie_u32  //
-wuffs_base__frame_config::bounds() {
+wuffs_base__frame_config::bounds() const {
   return wuffs_base__frame_config__bounds(this);
 }
 
 inline uint32_t  //
-wuffs_base__frame_config::width() {
+wuffs_base__frame_config::width() const {
   return wuffs_base__frame_config__width(this);
 }
 
 inline uint32_t  //
-wuffs_base__frame_config::height() {
+wuffs_base__frame_config::height() const {
   return wuffs_base__frame_config__height(this);
 }
 
 inline wuffs_base__flicks  //
-wuffs_base__frame_config::duration() {
+wuffs_base__frame_config::duration() const {
   return wuffs_base__frame_config__duration(this);
 }
 
 inline uint64_t  //
-wuffs_base__frame_config::index() {
+wuffs_base__frame_config::index() const {
   return wuffs_base__frame_config__index(this);
 }
 
 inline uint64_t  //
-wuffs_base__frame_config::io_position() {
+wuffs_base__frame_config::io_position() const {
   return wuffs_base__frame_config__io_position(this);
 }
 
 inline wuffs_base__animation_blend  //
-wuffs_base__frame_config::blend() {
+wuffs_base__frame_config::blend() const {
   return wuffs_base__frame_config__blend(this);
 }
 
 inline wuffs_base__animation_disposal  //
-wuffs_base__frame_config::disposal() {
+wuffs_base__frame_config::disposal() const {
   return wuffs_base__frame_config__disposal(this);
 }
 
+inline wuffs_base__color_u32_argb_premul  //
+wuffs_base__frame_config::background_color() const {
+  return wuffs_base__frame_config__background_color(this);
+}
+
 #endif  // __cplusplus
 
 // --------
@@ -2380,8 +2377,10 @@
 #ifdef __cplusplus
   inline wuffs_base__status set_from_slice(wuffs_base__pixel_config* pixcfg,
                                            wuffs_base__slice_u8 pixbuf_memory);
+  inline wuffs_base__status set_from_table(wuffs_base__pixel_config* pixcfg,
+                                           wuffs_base__table_u8 pixbuf_memory);
   inline wuffs_base__slice_u8 palette();
-  inline wuffs_base__pixel_format pixel_format();
+  inline wuffs_base__pixel_format pixel_format() const;
   inline wuffs_base__table_u8 plane(uint32_t p);
 #endif  // __cplusplus
 
@@ -2411,12 +2410,13 @@
   }
   if (wuffs_base__pixel_format__is_planar(pixcfg->private_impl.pixfmt)) {
     // TODO: support planar pixel formats, concious of pixel subsampling.
-    return wuffs_base__error__bad_argument;
+    return wuffs_base__error__unsupported_option;
   }
   uint32_t bits_per_pixel =
       wuffs_base__pixel_format__bits_per_pixel(pixcfg->private_impl.pixfmt);
   if ((bits_per_pixel == 0) || ((bits_per_pixel % 8) != 0)) {
-    return wuffs_base__error__bad_argument;
+    // TODO: support fraction-of-byte pixels, e.g. 1 bit per pixel?
+    return wuffs_base__error__unsupported_option;
   }
   uint64_t bytes_per_pixel = bits_per_pixel / 8;
 
@@ -2462,6 +2462,38 @@
   return NULL;
 }
 
+static inline wuffs_base__status  //
+wuffs_base__pixel_buffer__set_from_table(wuffs_base__pixel_buffer* b,
+                                         wuffs_base__pixel_config* pixcfg,
+                                         wuffs_base__table_u8 pixbuf_memory) {
+  if (!b) {
+    return wuffs_base__error__bad_receiver;
+  }
+  memset(b, 0, sizeof(*b));
+  if (!pixcfg ||
+      wuffs_base__pixel_format__is_planar(pixcfg->private_impl.pixfmt)) {
+    return wuffs_base__error__bad_argument;
+  }
+  uint32_t bits_per_pixel =
+      wuffs_base__pixel_format__bits_per_pixel(pixcfg->private_impl.pixfmt);
+  if ((bits_per_pixel == 0) || ((bits_per_pixel % 8) != 0)) {
+    // TODO: support fraction-of-byte pixels, e.g. 1 bit per pixel?
+    return wuffs_base__error__unsupported_option;
+  }
+  uint64_t bytes_per_pixel = bits_per_pixel / 8;
+
+  uint64_t width_in_bytes =
+      ((uint64_t)pixcfg->private_impl.width) * bytes_per_pixel;
+  if ((width_in_bytes > pixbuf_memory.width) ||
+      (pixcfg->private_impl.height > pixbuf_memory.height)) {
+    return wuffs_base__error__bad_argument;
+  }
+
+  b->pixcfg = *pixcfg;
+  b->private_impl.planes[0] = pixbuf_memory;
+  return NULL;
+}
+
 // wuffs_base__pixel_buffer__palette returns the palette color data. If
 // non-empty, it will have length 1024.
 static inline wuffs_base__slice_u8  //
@@ -2478,7 +2510,7 @@
 }
 
 static inline wuffs_base__pixel_format  //
-wuffs_base__pixel_buffer__pixel_format(wuffs_base__pixel_buffer* b) {
+wuffs_base__pixel_buffer__pixel_format(const wuffs_base__pixel_buffer* b) {
   if (b) {
     return b->pixcfg.private_impl.pixfmt;
   }
@@ -2507,13 +2539,19 @@
   return wuffs_base__pixel_buffer__set_from_slice(this, pixcfg, pixbuf_memory);
 }
 
+inline wuffs_base__status  //
+wuffs_base__pixel_buffer::set_from_table(wuffs_base__pixel_config* pixcfg,
+                                         wuffs_base__table_u8 pixbuf_memory) {
+  return wuffs_base__pixel_buffer__set_from_table(this, pixcfg, pixbuf_memory);
+}
+
 inline wuffs_base__slice_u8  //
 wuffs_base__pixel_buffer::palette() {
   return wuffs_base__pixel_buffer__palette(this);
 }
 
 inline wuffs_base__pixel_format  //
-wuffs_base__pixel_buffer::pixel_format() {
+wuffs_base__pixel_buffer::pixel_format() const {
   return wuffs_base__pixel_buffer__pixel_format(this);
 }
 
@@ -2555,20 +2593,18 @@
   } private_impl;
 
 #ifdef __cplusplus
-  inline void prepare(wuffs_base__pixel_format dst_format,
-                      wuffs_base__slice_u8 dst_palette,
-                      wuffs_base__pixel_format src_format,
-                      wuffs_base__slice_u8 src_palette);
-  inline uint64_t swizzle_packed(wuffs_base__slice_u8 dst,
-                                 wuffs_base__slice_u8 dst_palette,
-                                 wuffs_base__slice_u8 src);
+  inline wuffs_base__status prepare(wuffs_base__pixel_format dst_format,
+                                    wuffs_base__slice_u8 dst_palette,
+                                    wuffs_base__pixel_format src_format,
+                                    wuffs_base__slice_u8 src_palette);
+  inline uint64_t swizzle_interleaved(wuffs_base__slice_u8 dst,
+                                      wuffs_base__slice_u8 dst_palette,
+                                      wuffs_base__slice_u8 src) const;
 #endif  // __cplusplus
 
 } wuffs_base__pixel_swizzler;
 
-// TODO: should prepare (both the C and C++ methods) return a status?
-
-void  //
+wuffs_base__status  //
 wuffs_base__pixel_swizzler__prepare(wuffs_base__pixel_swizzler* p,
                                     wuffs_base__pixel_format dst_format,
                                     wuffs_base__slice_u8 dst_palette,
@@ -2576,28 +2612,30 @@
                                     wuffs_base__slice_u8 src_palette);
 
 uint64_t  //
-wuffs_base__pixel_swizzler__swizzle_packed(wuffs_base__pixel_swizzler* p,
-                                           wuffs_base__slice_u8 dst,
-                                           wuffs_base__slice_u8 dst_palette,
-                                           wuffs_base__slice_u8 src);
+wuffs_base__pixel_swizzler__swizzle_interleaved(
+    const wuffs_base__pixel_swizzler* p,
+    wuffs_base__slice_u8 dst,
+    wuffs_base__slice_u8 dst_palette,
+    wuffs_base__slice_u8 src);
 
 #ifdef __cplusplus
 
-inline void  //
+inline wuffs_base__status  //
 wuffs_base__pixel_swizzler::prepare(wuffs_base__pixel_format dst_format,
                                     wuffs_base__slice_u8 dst_palette,
                                     wuffs_base__pixel_format src_format,
                                     wuffs_base__slice_u8 src_palette) {
-  wuffs_base__pixel_swizzler__prepare(this, dst_format, dst_palette, src_format,
-                                      src_palette);
+  return wuffs_base__pixel_swizzler__prepare(this, dst_format, dst_palette,
+                                             src_format, src_palette);
 }
 
 uint64_t  //
-wuffs_base__pixel_swizzler::swizzle_packed(wuffs_base__slice_u8 dst,
-                                           wuffs_base__slice_u8 dst_palette,
-                                           wuffs_base__slice_u8 src) {
-  return wuffs_base__pixel_swizzler__swizzle_packed(this, dst, dst_palette,
-                                                    src);
+wuffs_base__pixel_swizzler::swizzle_interleaved(
+    wuffs_base__slice_u8 dst,
+    wuffs_base__slice_u8 dst_palette,
+    wuffs_base__slice_u8 src) const {
+  return wuffs_base__pixel_swizzler__swizzle_interleaved(this, dst, dst_palette,
+                                                         src);
 }
 
 #endif  // __cplusplus
@@ -2900,8 +2938,8 @@
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_deflate__decoder__decode_io_writer(wuffs_deflate__decoder* self,
-                                         wuffs_base__io_writer a_dst,
-                                         wuffs_base__io_reader a_src,
+                                         wuffs_base__io_buffer* a_dst,
+                                         wuffs_base__io_buffer* a_src,
                                          wuffs_base__slice_u8 a_workbuf);
 
 // ---------------- Struct Definitions
@@ -3019,8 +3057,8 @@
   }
 
   inline wuffs_base__status  //
-  decode_io_writer(wuffs_base__io_writer a_dst,
-                   wuffs_base__io_reader a_src,
+  decode_io_writer(wuffs_base__io_buffer* a_dst,
+                   wuffs_base__io_buffer* a_src,
                    wuffs_base__slice_u8 a_workbuf) {
     return wuffs_deflate__decoder__decode_io_writer(this, a_dst, a_src,
                                                     a_workbuf);
@@ -3091,8 +3129,8 @@
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_lzw__decoder__decode_io_writer(wuffs_lzw__decoder* self,
-                                     wuffs_base__io_writer a_dst,
-                                     wuffs_base__io_reader a_src,
+                                     wuffs_base__io_buffer* a_dst,
+                                     wuffs_base__io_buffer* a_src,
                                      wuffs_base__slice_u8 a_workbuf);
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__slice_u8  //
@@ -3189,8 +3227,8 @@
   }
 
   inline wuffs_base__status  //
-  decode_io_writer(wuffs_base__io_writer a_dst,
-                   wuffs_base__io_reader a_src,
+  decode_io_writer(wuffs_base__io_buffer* a_dst,
+                   wuffs_base__io_buffer* a_src,
                    wuffs_base__slice_u8 a_workbuf) {
     return wuffs_lzw__decoder__decode_io_writer(this, a_dst, a_src, a_workbuf);
   }
@@ -3225,9 +3263,11 @@
 
 extern const char* wuffs_gif__error__bad_block;
 extern const char* wuffs_gif__error__bad_extension_label;
+extern const char* wuffs_gif__error__bad_frame_size;
 extern const char* wuffs_gif__error__bad_graphic_control;
 extern const char* wuffs_gif__error__bad_header;
 extern const char* wuffs_gif__error__bad_literal_width;
+extern const char* wuffs_gif__error__bad_palette;
 
 // ---------------- Public Consts
 
@@ -3237,6 +3277,49 @@
     wuffs_gif__decoder_workbuf_len_max_incl_worst_case  //
         WUFFS_BASE__POTENTIALLY_UNUSED = 1;
 
+#define WUFFS_GIF__QUIRK_DELAY_NUM_DECODED_FRAMES 1041635328
+
+static const uint32_t                          //
+    wuffs_gif__quirk_delay_num_decoded_frames  //
+        WUFFS_BASE__POTENTIALLY_UNUSED = 1041635328;
+
+#define WUFFS_GIF__QUIRK_FIRST_FRAME_LOCAL_PALETTE_MEANS_BLACK_BACKGROUND \
+  1041635329
+
+static const uint32_t                                                  //
+    wuffs_gif__quirk_first_frame_local_palette_means_black_background  //
+        WUFFS_BASE__POTENTIALLY_UNUSED = 1041635329;
+
+#define WUFFS_GIF__QUIRK_HONOR_BACKGROUND_COLOR 1041635330
+
+static const uint32_t                        //
+    wuffs_gif__quirk_honor_background_color  //
+        WUFFS_BASE__POTENTIALLY_UNUSED = 1041635330;
+
+#define WUFFS_GIF__QUIRK_IGNORE_TOO_MUCH_PIXEL_DATA 1041635331
+
+static const uint32_t                            //
+    wuffs_gif__quirk_ignore_too_much_pixel_data  //
+        WUFFS_BASE__POTENTIALLY_UNUSED = 1041635331;
+
+#define WUFFS_GIF__QUIRK_IMAGE_BOUNDS_ARE_STRICT 1041635332
+
+static const uint32_t                         //
+    wuffs_gif__quirk_image_bounds_are_strict  //
+        WUFFS_BASE__POTENTIALLY_UNUSED = 1041635332;
+
+#define WUFFS_GIF__QUIRK_REJECT_EMPTY_FRAME 1041635333
+
+static const uint32_t                    //
+    wuffs_gif__quirk_reject_empty_frame  //
+        WUFFS_BASE__POTENTIALLY_UNUSED = 1041635333;
+
+#define WUFFS_GIF__QUIRK_REJECT_EMPTY_PALETTE 1041635334
+
+static const uint32_t                      //
+    wuffs_gif__quirk_reject_empty_palette  //
+        WUFFS_BASE__POTENTIALLY_UNUSED = 1041635334;
+
 // ---------------- Struct Declarations
 
 typedef struct wuffs_gif__decoder__struct wuffs_gif__decoder;
@@ -3260,10 +3343,30 @@
 
 // ---------------- Public Function Prototypes
 
+WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct  //
+wuffs_gif__decoder__set_quirk_enabled(wuffs_gif__decoder* self,
+                                      uint32_t a_quirk,
+                                      bool a_enabled);
+
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_gif__decoder__decode_image_config(wuffs_gif__decoder* self,
                                         wuffs_base__image_config* a_dst,
-                                        wuffs_base__io_reader a_src);
+                                        wuffs_base__io_buffer* a_src);
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct  //
+wuffs_gif__decoder__set_report_metadata(wuffs_gif__decoder* self,
+                                        uint32_t a_fourcc,
+                                        bool a_report);
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
+wuffs_gif__decoder__ack_metadata_chunk(wuffs_gif__decoder* self,
+                                       wuffs_base__io_buffer* a_src);
+
+WUFFS_BASE__MAYBE_STATIC uint32_t  //
+wuffs_gif__decoder__metadata_fourcc(const wuffs_gif__decoder* self);
+
+WUFFS_BASE__MAYBE_STATIC uint64_t  //
+wuffs_gif__decoder__metadata_chunk_length(const wuffs_gif__decoder* self);
 
 WUFFS_BASE__MAYBE_STATIC uint32_t  //
 wuffs_gif__decoder__num_animation_loops(const wuffs_gif__decoder* self);
@@ -3288,12 +3391,12 @@
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_gif__decoder__decode_frame_config(wuffs_gif__decoder* self,
                                         wuffs_base__frame_config* a_dst,
-                                        wuffs_base__io_reader a_src);
+                                        wuffs_base__io_buffer* a_src);
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_gif__decoder__decode_frame(wuffs_gif__decoder* self,
                                  wuffs_base__pixel_buffer* a_dst,
-                                 wuffs_base__io_reader a_src,
+                                 wuffs_base__io_buffer* a_src,
                                  wuffs_base__slice_u8 a_workbuf,
                                  wuffs_base__decode_frame_options* a_opts);
 
@@ -3323,14 +3426,29 @@
     uint32_t f_width;
     uint32_t f_height;
     uint8_t f_call_sequence;
+    bool f_ignore_metadata;
+    bool f_report_metadata_iccp;
+    bool f_report_metadata_xmp;
+    uint32_t f_metadata_fourcc_value;
+    uint64_t f_metadata_chunk_length_value;
+    uint64_t f_metadata_io_position;
+    bool f_quirk_enabled_delay_num_decoded_frames;
+    bool f_quirk_enabled_first_frame_local_palette_means_black_background;
+    bool f_quirk_enabled_honor_background_color;
+    bool f_quirk_enabled_ignore_too_much_pixel_data;
+    bool f_quirk_enabled_image_bounds_are_strict;
+    bool f_quirk_enabled_reject_empty_frame;
+    bool f_quirk_enabled_reject_empty_palette;
+    bool f_delayed_num_decoded_frames;
     bool f_end_of_data;
     bool f_restarted;
     bool f_previous_lzw_decode_ended_abruptly;
-    uint8_t f_which_palette;
+    bool f_has_global_palette;
     uint8_t f_interlace;
     bool f_seen_num_loops;
     uint32_t f_num_loops;
-    bool f_seen_graphic_control;
+    uint32_t f_background_color_u32_argb_premul;
+    uint32_t f_black_color_u32_argb_premul;
     bool f_gc_has_transparent_index;
     uint8_t f_gc_transparent_index;
     uint8_t f_gc_disposal;
@@ -3350,6 +3468,7 @@
     wuffs_base__pixel_swizzler f_swizzler;
 
     uint32_t p_decode_image_config[1];
+    uint32_t p_ack_metadata_chunk[1];
     uint32_t p_decode_frame_config[1];
     uint32_t p_skip_frame[1];
     uint32_t p_decode_frame[1];
@@ -3372,6 +3491,10 @@
     wuffs_lzw__decoder f_lzw;
 
     struct {
+      uint8_t v_blend;
+      uint32_t v_background_color;
+    } s_decode_frame_config[1];
+    struct {
       uint64_t scratch;
     } s_skip_frame[1];
     struct {
@@ -3380,6 +3503,7 @@
     } s_decode_header[1];
     struct {
       uint8_t v_flags;
+      uint8_t v_background_color_index;
       uint32_t v_num_palette_entries;
       uint32_t v_i;
       uint64_t scratch;
@@ -3389,8 +3513,10 @@
     } s_skip_blocks[1];
     struct {
       uint8_t v_block_size;
-      bool v_not_animexts;
-      bool v_not_netscape;
+      bool v_is_animexts;
+      bool v_is_netscape;
+      bool v_is_iccp;
+      bool v_is_xmp;
       uint64_t scratch;
     } s_decode_ae[1];
     struct {
@@ -3400,6 +3526,7 @@
       uint64_t scratch;
     } s_decode_id_part0[1];
     struct {
+      uint8_t v_which_palette;
       uint32_t v_num_palette_entries;
       uint32_t v_i;
       uint64_t scratch;
@@ -3444,12 +3571,37 @@
                                           initialize_flags);
   }
 
+  inline wuffs_base__empty_struct  //
+  set_quirk_enabled(uint32_t a_quirk, bool a_enabled) {
+    return wuffs_gif__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_reader a_src) {
+                      wuffs_base__io_buffer* a_src) {
     return wuffs_gif__decoder__decode_image_config(this, a_dst, a_src);
   }
 
+  inline wuffs_base__empty_struct  //
+  set_report_metadata(uint32_t a_fourcc, bool a_report) {
+    return wuffs_gif__decoder__set_report_metadata(this, a_fourcc, a_report);
+  }
+
+  inline wuffs_base__status  //
+  ack_metadata_chunk(wuffs_base__io_buffer* a_src) {
+    return wuffs_gif__decoder__ack_metadata_chunk(this, a_src);
+  }
+
+  inline uint32_t  //
+  metadata_fourcc() const {
+    return wuffs_gif__decoder__metadata_fourcc(this);
+  }
+
+  inline uint64_t  //
+  metadata_chunk_length() const {
+    return wuffs_gif__decoder__metadata_chunk_length(this);
+  }
+
   inline uint32_t  //
   num_animation_loops() const {
     return wuffs_gif__decoder__num_animation_loops(this);
@@ -3482,13 +3634,13 @@
 
   inline wuffs_base__status  //
   decode_frame_config(wuffs_base__frame_config* a_dst,
-                      wuffs_base__io_reader a_src) {
+                      wuffs_base__io_buffer* a_src) {
     return wuffs_gif__decoder__decode_frame_config(this, a_dst, a_src);
   }
 
   inline wuffs_base__status  //
   decode_frame(wuffs_base__pixel_buffer* a_dst,
-               wuffs_base__io_reader a_src,
+               wuffs_base__io_buffer* a_src,
                wuffs_base__slice_u8 a_workbuf,
                wuffs_base__decode_frame_options* a_opts) {
     return wuffs_gif__decoder__decode_frame(this, a_dst, a_src, a_workbuf,
@@ -3562,8 +3714,8 @@
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_gzip__decoder__decode_io_writer(wuffs_gzip__decoder* self,
-                                      wuffs_base__io_writer a_dst,
-                                      wuffs_base__io_reader a_src,
+                                      wuffs_base__io_buffer* a_dst,
+                                      wuffs_base__io_buffer* a_src,
                                       wuffs_base__slice_u8 a_workbuf);
 
 // ---------------- Struct Definitions
@@ -3650,8 +3802,8 @@
   }
 
   inline wuffs_base__status  //
-  decode_io_writer(wuffs_base__io_writer a_dst,
-                   wuffs_base__io_reader a_src,
+  decode_io_writer(wuffs_base__io_buffer* a_dst,
+                   wuffs_base__io_buffer* a_src,
                    wuffs_base__slice_u8 a_workbuf) {
     return wuffs_gzip__decoder__decode_io_writer(this, a_dst, a_src, a_workbuf);
   }
@@ -3723,8 +3875,8 @@
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_zlib__decoder__decode_io_writer(wuffs_zlib__decoder* self,
-                                      wuffs_base__io_writer a_dst,
-                                      wuffs_base__io_reader a_src,
+                                      wuffs_base__io_buffer* a_dst,
+                                      wuffs_base__io_buffer* a_src,
                                       wuffs_base__slice_u8 a_workbuf);
 
 // ---------------- Struct Definitions
@@ -3808,8 +3960,8 @@
   }
 
   inline wuffs_base__status  //
-  decode_io_writer(wuffs_base__io_writer a_dst,
-                   wuffs_base__io_reader a_src,
+  decode_io_writer(wuffs_base__io_buffer* a_dst,
+                   wuffs_base__io_buffer* a_src,
                    wuffs_base__slice_u8 a_workbuf) {
     return wuffs_zlib__decoder__decode_io_writer(this, a_dst, a_src, a_workbuf);
   }
@@ -4304,65 +4456,52 @@
 
 // ---------------- I/O
 
-static inline bool  //
-wuffs_base__io_buffer__is_valid(wuffs_base__io_buffer buf) {
-  return (buf.data.ptr || (buf.data.len == 0)) &&
-         (buf.data.len >= buf.meta.wi) && (buf.meta.wi >= buf.meta.ri);
-}
-
-// TODO: wuffs_base__io_reader__is_eof is no longer used by Wuffs per se, but
-// it might be handy to programs that use Wuffs. Either delete it, or promote
-// it to the public API.
+// "Null" as in "/dev/null", not as in "nullptr".
 //
-// If making this function public (i.e. moving it to base-header.h), it also
-// needs to allow NULL (i.e. implicit, callee-calculated) mark/limit.
+// TODO: ensure that this is zero-initialized.
+static wuffs_base__io_buffer wuffs_base__global__null_io_buffer;
 
-static inline bool  //
-wuffs_base__io_reader__is_eof(wuffs_base__io_reader o) {
-  wuffs_base__io_buffer* buf = o.private_impl.buf;
-  return buf && buf->meta.closed &&
-         (buf->data.ptr + buf->meta.wi == o.private_impl.limit);
+static inline wuffs_base__io_buffer*  //
+wuffs_base__null_io_reader() {
+  return &wuffs_base__global__null_io_buffer;
 }
 
-static inline bool  //
-wuffs_base__io_reader__is_valid(wuffs_base__io_reader o) {
-  wuffs_base__io_buffer* buf = o.private_impl.buf;
-  // Note: if making this function public (i.e. moving it to base-header.h), it
-  // also needs to allow NULL (i.e. implicit, callee-calculated) mark/limit.
-  return buf ? ((buf->data.ptr <= o.private_impl.mark) &&
-                (o.private_impl.mark <= o.private_impl.limit) &&
-                (o.private_impl.limit <= buf->data.ptr + buf->data.len))
-             : ((o.private_impl.mark == NULL) &&
-                (o.private_impl.limit == NULL));
+static inline wuffs_base__io_buffer*  //
+wuffs_base__null_io_writer() {
+  return &wuffs_base__global__null_io_buffer;
 }
 
-static inline bool  //
-wuffs_base__io_writer__is_valid(wuffs_base__io_writer o) {
-  wuffs_base__io_buffer* buf = o.private_impl.buf;
-  // Note: if making this function public (i.e. moving it to base-header.h), it
-  // also needs to allow NULL (i.e. implicit, callee-calculated) mark/limit.
-  return buf ? ((buf->data.ptr <= o.private_impl.mark) &&
-                (o.private_impl.mark <= o.private_impl.limit) &&
-                (o.private_impl.limit <= buf->data.ptr + buf->data.len))
-             : ((o.private_impl.mark == NULL) &&
-                (o.private_impl.limit == NULL));
+static inline uint64_t  //
+wuffs_base__io__count_since(uint64_t mark, uint64_t index) {
+  if (index >= mark) {
+    return index - mark;
+  }
+  return 0;
+}
+
+static inline wuffs_base__slice_u8  //
+wuffs_base__io__since(uint64_t mark, uint64_t index, uint8_t* ptr) {
+  if (index >= mark) {
+    return wuffs_base__make_slice_u8(ptr + mark, index - mark);
+  }
+  return wuffs_base__make_slice_u8(NULL, 0);
 }
 
 static inline uint32_t  //
 wuffs_base__io_writer__copy_n_from_history(uint8_t** ptr_iop_w,
-                                           uint8_t* io0_w,
                                            uint8_t* io1_w,
+                                           uint8_t* io2_w,
                                            uint32_t length,
                                            uint32_t distance) {
   if (!distance) {
     return 0;
   }
   uint8_t* p = *ptr_iop_w;
-  if ((size_t)(p - io0_w) < (size_t)(distance)) {
+  if ((size_t)(p - io1_w) < (size_t)(distance)) {
     return 0;
   }
   uint8_t* q = p - distance;
-  size_t n = (size_t)(io1_w - p);
+  size_t n = (size_t)(io2_w - p);
   if ((size_t)(length) > n) {
     length = (uint32_t)(n);
   } else {
@@ -4394,12 +4533,12 @@
 // wuffs_base__io_writer__copy_n_from_history function above, but has stronger
 // pre-conditions. The caller needs to prove that:
 //  - distance >  0
-//  - distance <= (*ptr_iop_w - io0_w)
-//  - length   <= (io1_w      - *ptr_iop_w)
+//  - distance <= (*ptr_iop_w - io1_w)
+//  - length   <= (io2_w      - *ptr_iop_w)
 static inline uint32_t  //
 wuffs_base__io_writer__copy_n_from_history_fast(uint8_t** ptr_iop_w,
-                                                uint8_t* io0_w,
                                                 uint8_t* io1_w,
+                                                uint8_t* io2_w,
                                                 uint32_t length,
                                                 uint32_t distance) {
   uint8_t* p = *ptr_iop_w;
@@ -4419,18 +4558,18 @@
 
 static inline uint32_t  //
 wuffs_base__io_writer__copy_n_from_reader(uint8_t** ptr_iop_w,
-                                          uint8_t* io1_w,
+                                          uint8_t* io2_w,
                                           uint32_t length,
                                           uint8_t** ptr_iop_r,
-                                          uint8_t* io1_r) {
+                                          uint8_t* io2_r) {
   uint8_t* iop_w = *ptr_iop_w;
   size_t n = length;
-  if (n > ((size_t)(io1_w - iop_w))) {
-    n = (size_t)(io1_w - iop_w);
+  if (n > ((size_t)(io2_w - iop_w))) {
+    n = (size_t)(io2_w - iop_w);
   }
   uint8_t* iop_r = *ptr_iop_r;
-  if (n > ((size_t)(io1_r - iop_r))) {
-    n = (size_t)(io1_r - iop_r);
+  if (n > ((size_t)(io2_r - iop_r))) {
+    n = (size_t)(io2_r - iop_r);
   }
   if (n > 0) {
     memmove(iop_w, iop_r, n);
@@ -4442,12 +4581,12 @@
 
 static inline uint64_t  //
 wuffs_base__io_writer__copy_from_slice(uint8_t** ptr_iop_w,
-                                       uint8_t* io1_w,
+                                       uint8_t* io2_w,
                                        wuffs_base__slice_u8 src) {
   uint8_t* iop_w = *ptr_iop_w;
   size_t n = src.len;
-  if (n > ((size_t)(io1_w - iop_w))) {
-    n = (size_t)(io1_w - iop_w);
+  if (n > ((size_t)(io2_w - iop_w))) {
+    n = (size_t)(io2_w - iop_w);
   }
   if (n > 0) {
     memmove(iop_w, src.ptr, n);
@@ -4458,7 +4597,7 @@
 
 static inline uint32_t  //
 wuffs_base__io_writer__copy_n_from_slice(uint8_t** ptr_iop_w,
-                                         uint8_t* io1_w,
+                                         uint8_t* io2_w,
                                          uint32_t length,
                                          wuffs_base__slice_u8 src) {
   uint8_t* iop_w = *ptr_iop_w;
@@ -4466,8 +4605,8 @@
   if (n > length) {
     n = length;
   }
-  if (n > ((size_t)(io1_w - iop_w))) {
-    n = (size_t)(io1_w - iop_w);
+  if (n > ((size_t)(io2_w - iop_w))) {
+    n = (size_t)(io2_w - iop_w);
   }
   if (n > 0) {
     memmove(iop_w, src.ptr, n);
@@ -4476,11 +4615,12 @@
   return (uint32_t)(n);
 }
 
-static inline wuffs_base__empty_struct  //
-wuffs_base__io_reader__set(wuffs_base__io_reader* o,
-                           wuffs_base__io_buffer* b,
+static inline wuffs_base__io_buffer*  //
+wuffs_base__io_reader__set(wuffs_base__io_buffer* b,
                            uint8_t** ptr_iop_r,
+                           uint8_t** ptr_io0_r,
                            uint8_t** ptr_io1_r,
+                           uint8_t** ptr_io2_r,
                            wuffs_base__slice_u8 data) {
   b->data = data;
   b->meta.wi = data.len;
@@ -4488,42 +4628,17 @@
   b->meta.pos = 0;
   b->meta.closed = false;
 
-  o->private_impl.buf = b;
-  o->private_impl.mark = data.ptr;
-  o->private_impl.limit = data.ptr + data.len;
   *ptr_iop_r = data.ptr;
-  *ptr_io1_r = data.ptr + data.len;
+  *ptr_io0_r = data.ptr;
+  *ptr_io1_r = data.ptr;
+  *ptr_io2_r = data.ptr + data.len;
 
-  wuffs_base__empty_struct ret;
-  ret.private_impl = 0;
-  return ret;
-}
-
-static inline wuffs_base__empty_struct  //
-wuffs_base__io_reader__set_limit(wuffs_base__io_reader* o,
-                                 uint8_t* iop_r,
-                                 uint64_t limit) {
-  if (o && (((size_t)(o->private_impl.limit - iop_r)) > limit)) {
-    o->private_impl.limit = iop_r + limit;
-  }
-
-  wuffs_base__empty_struct ret;
-  ret.private_impl = 0;
-  return ret;
-}
-
-static inline wuffs_base__empty_struct  //
-wuffs_base__io_reader__set_mark(wuffs_base__io_reader* o, uint8_t* mark) {
-  o->private_impl.mark = mark;
-
-  wuffs_base__empty_struct ret;
-  ret.private_impl = 0;
-  return ret;
+  return b;
 }
 
 static inline wuffs_base__slice_u8  //
-wuffs_base__io_reader__take(uint8_t** ptr_iop_r, uint8_t* io1_r, uint64_t n) {
-  if (n <= ((size_t)(io1_r - *ptr_iop_r))) {
+wuffs_base__io_reader__take(uint8_t** ptr_iop_r, uint8_t* io2_r, uint64_t n) {
+  if (n <= ((size_t)(io2_r - *ptr_iop_r))) {
     uint8_t* p = *ptr_iop_r;
     *ptr_iop_r += n;
     return wuffs_base__make_slice_u8(p, n);
@@ -4531,11 +4646,12 @@
   return wuffs_base__make_slice_u8(NULL, 0);
 }
 
-static inline wuffs_base__empty_struct  //
-wuffs_base__io_writer__set(wuffs_base__io_writer* o,
-                           wuffs_base__io_buffer* b,
+static inline wuffs_base__io_buffer*  //
+wuffs_base__io_writer__set(wuffs_base__io_buffer* b,
                            uint8_t** ptr_iop_w,
+                           uint8_t** ptr_io0_w,
                            uint8_t** ptr_io1_w,
+                           uint8_t** ptr_io2_w,
                            wuffs_base__slice_u8 data) {
   b->data = data;
   b->meta.wi = 0;
@@ -4543,24 +4659,12 @@
   b->meta.pos = 0;
   b->meta.closed = false;
 
-  o->private_impl.buf = b;
-  o->private_impl.mark = data.ptr;
-  o->private_impl.limit = data.ptr + data.len;
   *ptr_iop_w = data.ptr;
-  *ptr_io1_w = data.ptr + data.len;
+  *ptr_io0_w = data.ptr;
+  *ptr_io1_w = data.ptr;
+  *ptr_io2_w = data.ptr + data.len;
 
-  wuffs_base__empty_struct ret;
-  ret.private_impl = 0;
-  return ret;
-}
-
-static inline wuffs_base__empty_struct  //
-wuffs_base__io_writer__set_mark(wuffs_base__io_writer* o, uint8_t* mark) {
-  o->private_impl.mark = mark;
-
-  wuffs_base__empty_struct ret;
-  ret.private_impl = 0;
-  return ret;
+  return b;
 }
 
   // ---------------- I/O (Utility)
@@ -4618,8 +4722,10 @@
 };
 
 const char* wuffs_base__warning__end_of_data = "@base: end of data";
+const char* wuffs_base__warning__metadata_reported = "@base: metadata reported";
 const char* wuffs_base__suspension__short_read = "$base: short read";
 const char* wuffs_base__suspension__short_write = "$base: short write";
+const char* wuffs_base__error__bad_i_o_position = "#base: bad I/O position";
 const char* wuffs_base__error__bad_argument_length_too_short =
     "#base: bad argument (length too short)";
 const char* wuffs_base__error__bad_argument = "#base: bad argument";
@@ -4641,6 +4747,7 @@
 const char* wuffs_base__error__interleaved_coroutine_calls =
     "#base: interleaved coroutine calls";
 const char* wuffs_base__error__not_enough_data = "#base: not enough data";
+const char* wuffs_base__error__unsupported_option = "#base: unsupported option";
 const char* wuffs_base__error__too_much_data = "#base: too much data";
 
 // ---------------- Images
@@ -4658,6 +4765,61 @@
 }
 
 static uint64_t  //
+wuffs_base__pixel_swizzler__copy_3_1(wuffs_base__slice_u8 dst,
+                                     wuffs_base__slice_u8 dst_palette,
+                                     wuffs_base__slice_u8 src) {
+  if (dst_palette.len != 1024) {
+    return 0;
+  }
+  size_t dst_len3 = dst.len / 3;
+  size_t len = dst_len3 < src.len ? dst_len3 : src.len;
+  uint8_t* d = dst.ptr;
+  uint8_t* s = src.ptr;
+  size_t n = len;
+
+  // N is the loop unroll count.
+  const int N = 4;
+
+  // The comparison in the while condition is ">", not ">=", because with ">=",
+  // the last 4-byte store could write past the end of the dst slice.
+  //
+  // Each 4-byte store writes one too many bytes, but a subsequent store will
+  // overwrite that with the correct byte. There is always another store,
+  // whether a 4-byte store in this loop or a 1-byte store in the next loop.
+  while (n > N) {
+    wuffs_base__store_u32le(
+        d + (0 * 3),
+        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[0]) * 4)));
+    wuffs_base__store_u32le(
+        d + (1 * 3),
+        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[1]) * 4)));
+    wuffs_base__store_u32le(
+        d + (2 * 3),
+        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[2]) * 4)));
+    wuffs_base__store_u32le(
+        d + (3 * 3),
+        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[3]) * 4)));
+
+    s += 1 * N;
+    d += 3 * N;
+    n -= (size_t)(1 * N);
+  }
+
+  while (n >= 1) {
+    uint32_t color =
+        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[0]) * 4));
+    d[0] = (uint8_t)(color >> 0);
+    d[1] = (uint8_t)(color >> 8);
+    d[2] = (uint8_t)(color >> 16);
+
+    s += 1 * 1;
+    d += 3 * 1;
+    n -= (size_t)(1 * 1);
+  }
+
+  return len;
+}
+static uint64_t  //
 wuffs_base__pixel_swizzler__copy_4_1(wuffs_base__slice_u8 dst,
                                      wuffs_base__slice_u8 dst_palette,
                                      wuffs_base__slice_u8 src) {
@@ -4668,8 +4830,9 @@
   size_t len = dst_len4 < src.len ? dst_len4 : src.len;
   uint8_t* d = dst.ptr;
   uint8_t* s = src.ptr;
-
   size_t n = len;
+
+  // N is the loop unroll count.
   const int N = 4;
 
   while (n >= N) {
@@ -4727,14 +4890,14 @@
   return len4 * 4;
 }
 
-void  //
+wuffs_base__status  //
 wuffs_base__pixel_swizzler__prepare(wuffs_base__pixel_swizzler* p,
                                     wuffs_base__pixel_format dst_format,
                                     wuffs_base__slice_u8 dst_palette,
                                     wuffs_base__pixel_format src_format,
                                     wuffs_base__slice_u8 src_palette) {
   if (!p) {
-    return;
+    return wuffs_base__error__bad_receiver;
   }
 
   // TODO: support many more formats.
@@ -4754,6 +4917,13 @@
           }
           func = wuffs_base__pixel_swizzler__copy_1_1;
           break;
+        case WUFFS_BASE__PIXEL_FORMAT__BGR:
+          if (wuffs_base__slice_u8__copy_from_slice(dst_palette, src_palette) !=
+              1024) {
+            break;
+          }
+          func = wuffs_base__pixel_swizzler__copy_3_1;
+          break;
         case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL:
         case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL:
         case WUFFS_BASE__PIXEL_FORMAT__BGRA_BINARY:
@@ -4763,6 +4933,13 @@
           }
           func = wuffs_base__pixel_swizzler__copy_4_1;
           break;
+        case WUFFS_BASE__PIXEL_FORMAT__RGB:
+          if (wuffs_base__pixel_swizzler__swap_rgbx_bgrx(dst_palette,
+                                                         src_palette) != 1024) {
+            break;
+          }
+          func = wuffs_base__pixel_swizzler__copy_3_1;
+          break;
         case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL:
         case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL:
         case WUFFS_BASE__PIXEL_FORMAT__RGBA_BINARY:
@@ -4782,13 +4959,15 @@
   }
 
   p->private_impl.func = func;
+  return func ? NULL : wuffs_base__error__unsupported_option;
 }
 
 uint64_t  //
-wuffs_base__pixel_swizzler__swizzle_packed(wuffs_base__pixel_swizzler* p,
-                                           wuffs_base__slice_u8 dst,
-                                           wuffs_base__slice_u8 dst_palette,
-                                           wuffs_base__slice_u8 src) {
+wuffs_base__pixel_swizzler__swizzle_interleaved(
+    const wuffs_base__pixel_swizzler* p,
+    wuffs_base__slice_u8 dst,
+    wuffs_base__slice_u8 dst_palette,
+    wuffs_base__slice_u8 src) {
   if (p && p->private_impl.func) {
     return (*(p->private_impl.func))(dst, dst_palette, src);
   }
@@ -6091,20 +6270,20 @@
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__decode_blocks(wuffs_deflate__decoder* self,
-                                      wuffs_base__io_writer a_dst,
-                                      wuffs_base__io_reader a_src);
+                                      wuffs_base__io_buffer* a_dst,
+                                      wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__decode_uncompressed(wuffs_deflate__decoder* self,
-                                            wuffs_base__io_writer a_dst,
-                                            wuffs_base__io_reader a_src);
+                                            wuffs_base__io_buffer* a_dst,
+                                            wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__init_fixed_huffman(wuffs_deflate__decoder* self);
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__init_dynamic_huffman(wuffs_deflate__decoder* self,
-                                             wuffs_base__io_reader a_src);
+                                             wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__init_huff(wuffs_deflate__decoder* self,
@@ -6115,13 +6294,13 @@
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__decode_huffman_fast(wuffs_deflate__decoder* self,
-                                            wuffs_base__io_writer a_dst,
-                                            wuffs_base__io_reader a_src);
+                                            wuffs_base__io_buffer* a_dst,
+                                            wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__decode_huffman_slow(wuffs_deflate__decoder* self,
-                                            wuffs_base__io_writer a_dst,
-                                            wuffs_base__io_reader a_src);
+                                            wuffs_base__io_buffer* a_dst,
+                                            wuffs_base__io_buffer* a_src);
 
 // ---------------- Initializer Implementations
 
@@ -6196,8 +6375,8 @@
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_deflate__decoder__decode_io_writer(wuffs_deflate__decoder* self,
-                                         wuffs_base__io_writer a_dst,
-                                         wuffs_base__io_reader a_src,
+                                         wuffs_base__io_buffer* a_dst,
+                                         wuffs_base__io_buffer* a_src,
                                          wuffs_base__slice_u8 a_workbuf) {
   if (!self) {
     return wuffs_base__error__bad_receiver;
@@ -6207,6 +6386,10 @@
                ? 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__error__bad_argument;
+  }
   if ((self->private_impl.active_coroutine != 0) &&
       (self->private_impl.active_coroutine != 1)) {
     self->private_impl.magic = WUFFS_BASE__DISABLED;
@@ -6215,6 +6398,7 @@
   self->private_impl.active_coroutine = 0;
   wuffs_base__status status = NULL;
 
+  uint64_t v_mark = 0;
   wuffs_base__status v_status = NULL;
   wuffs_base__slice_u8 v_written = {0};
   uint64_t v_n_copied = 0;
@@ -6223,19 +6407,15 @@
   uint8_t* iop_a_dst = NULL;
   uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_dst.private_impl.buf) {
-    iop_a_dst =
-        a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->meta.wi;
-    if (!a_dst.private_impl.mark) {
-      a_dst.private_impl.mark = iop_a_dst;
-      a_dst.private_impl.limit =
-          a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->data.len;
+  uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_dst) {
+    io0_a_dst = a_dst->data.ptr;
+    io1_a_dst = io0_a_dst + a_dst->meta.wi;
+    iop_a_dst = io1_a_dst;
+    io2_a_dst = io0_a_dst + a_dst->data.len;
+    if (a_dst->meta.closed) {
+      io2_a_dst = iop_a_dst;
     }
-    if (a_dst.private_impl.buf->meta.closed) {
-      a_dst.private_impl.limit = iop_a_dst;
-    }
-    io0_a_dst = a_dst.private_impl.mark;
-    io1_a_dst = a_dst.private_impl.limit;
   }
 
   uint32_t coro_susp_point = self->private_impl.p_decode_io_writer[0];
@@ -6245,17 +6425,15 @@
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
     while (true) {
-      wuffs_base__io_writer__set_mark(&a_dst, iop_a_dst);
+      v_mark = ((uint64_t)(iop_a_dst - io0_a_dst));
       {
-        if (a_dst.private_impl.buf) {
-          a_dst.private_impl.buf->meta.wi =
-              ((size_t)(iop_a_dst - a_dst.private_impl.buf->data.ptr));
+        if (a_dst) {
+          a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
         }
         wuffs_base__status t_0 =
             wuffs_deflate__decoder__decode_blocks(self, a_dst, a_src);
-        if (a_dst.private_impl.buf) {
-          iop_a_dst = a_dst.private_impl.buf->data.ptr +
-                      a_dst.private_impl.buf->meta.wi;
+        if (a_dst) {
+          iop_a_dst = a_dst->data.ptr + a_dst->meta.wi;
         }
         v_status = t_0;
       }
@@ -6269,9 +6447,8 @@
         }
         goto ok;
       }
-      v_written = wuffs_base__make_slice_u8(
-          a_dst.private_impl.mark,
-          (size_t)(iop_a_dst - a_dst.private_impl.mark));
+      v_written = wuffs_base__io__since(
+          v_mark, ((uint64_t)(iop_a_dst - io0_a_dst)), io0_a_dst);
       if (((uint64_t)(v_written.len)) >= 32768) {
         v_written = wuffs_base__slice_u8__suffix(v_written, 32768);
         wuffs_base__slice_u8__copy_from_slice(
@@ -6313,14 +6490,15 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_io_writer[0] = coro_susp_point;
-  self->private_impl.active_coroutine = 1;
+  self->private_impl.p_decode_io_writer[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
+  self->private_impl.active_coroutine =
+      wuffs_base__status__is_suspension(status) ? 1 : 0;
 
   goto exit;
 exit:
-  if (a_dst.private_impl.buf) {
-    a_dst.private_impl.buf->meta.wi =
-        ((size_t)(iop_a_dst - a_dst.private_impl.buf->data.ptr));
+  if (a_dst) {
+    a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
   }
 
   if (wuffs_base__status__is_error(status)) {
@@ -6333,8 +6511,8 @@
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__decode_blocks(wuffs_deflate__decoder* self,
-                                      wuffs_base__io_writer a_dst,
-                                      wuffs_base__io_reader a_src) {
+                                      wuffs_base__io_buffer* a_dst,
+                                      wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint32_t v_final = 0;
@@ -6345,16 +6523,12 @@
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_blocks[0];
@@ -6369,7 +6543,7 @@
       while (self->private_impl.f_n_bits < 3) {
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -6384,16 +6558,14 @@
       self->private_impl.f_bits >>= 3;
       self->private_impl.f_n_bits -= 3;
       if (v_type == 0) {
-        if (a_src.private_impl.buf) {
-          a_src.private_impl.buf->meta.ri =
-              ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
         }
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
         status =
             wuffs_deflate__decoder__decode_uncompressed(self, a_dst, a_src);
-        if (a_src.private_impl.buf) {
-          iop_a_src = a_src.private_impl.buf->data.ptr +
-                      a_src.private_impl.buf->meta.ri;
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
         }
         if (status) {
           goto suspend;
@@ -6412,15 +6584,13 @@
           goto ok;
         }
       } else if (v_type == 2) {
-        if (a_src.private_impl.buf) {
-          a_src.private_impl.buf->meta.ri =
-              ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
         }
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
         status = wuffs_deflate__decoder__init_dynamic_huffman(self, a_src);
-        if (a_src.private_impl.buf) {
-          iop_a_src = a_src.private_impl.buf->data.ptr +
-                      a_src.private_impl.buf->meta.ri;
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
         }
         if (status) {
           goto suspend;
@@ -6431,15 +6601,13 @@
       }
       self->private_impl.f_end_of_block = false;
       while (true) {
-        if (a_src.private_impl.buf) {
-          a_src.private_impl.buf->meta.ri =
-              ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
         }
         v_status =
             wuffs_deflate__decoder__decode_huffman_fast(self, a_dst, a_src);
-        if (a_src.private_impl.buf) {
-          iop_a_src = a_src.private_impl.buf->data.ptr +
-                      a_src.private_impl.buf->meta.ri;
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
         }
         if (wuffs_base__status__is_error(v_status)) {
           status = v_status;
@@ -6448,16 +6616,14 @@
         if (self->private_impl.f_end_of_block) {
           goto label_0_continue;
         }
-        if (a_src.private_impl.buf) {
-          a_src.private_impl.buf->meta.ri =
-              ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
         }
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
         status =
             wuffs_deflate__decoder__decode_huffman_slow(self, a_dst, a_src);
-        if (a_src.private_impl.buf) {
-          iop_a_src = a_src.private_impl.buf->data.ptr +
-                      a_src.private_impl.buf->meta.ri;
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
         }
         if (status) {
           goto suspend;
@@ -6476,14 +6642,14 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_blocks[0] = coro_susp_point;
+  self->private_impl.p_decode_blocks[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
   self->private_data.s_decode_blocks[0].v_final = v_final;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -6493,8 +6659,8 @@
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__decode_uncompressed(wuffs_deflate__decoder* self,
-                                            wuffs_base__io_writer a_dst,
-                                            wuffs_base__io_reader a_src) {
+                                            wuffs_base__io_buffer* a_dst,
+                                            wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint32_t v_length = 0;
@@ -6503,33 +6669,25 @@
   uint8_t* iop_a_dst = NULL;
   uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_dst.private_impl.buf) {
-    iop_a_dst =
-        a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->meta.wi;
-    if (!a_dst.private_impl.mark) {
-      a_dst.private_impl.mark = iop_a_dst;
-      a_dst.private_impl.limit =
-          a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->data.len;
+  uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_dst) {
+    io0_a_dst = a_dst->data.ptr;
+    io1_a_dst = io0_a_dst + a_dst->meta.wi;
+    iop_a_dst = io1_a_dst;
+    io2_a_dst = io0_a_dst + a_dst->data.len;
+    if (a_dst->meta.closed) {
+      io2_a_dst = iop_a_dst;
     }
-    if (a_dst.private_impl.buf->meta.closed) {
-      a_dst.private_impl.limit = iop_a_dst;
-    }
-    io0_a_dst = a_dst.private_impl.mark;
-    io1_a_dst = a_dst.private_impl.limit;
   }
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_uncompressed[0];
@@ -6550,14 +6708,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
       uint32_t t_0;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 4)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) {
         t_0 = wuffs_base__load_u32le(iop_a_src);
         iop_a_src += 4;
       } else {
         self->private_data.s_decode_uncompressed[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -6584,13 +6742,13 @@
     v_length = ((v_length)&0xFFFF);
     while (true) {
       v_n_copied = wuffs_base__io_writer__copy_n_from_reader(
-          &iop_a_dst, io1_a_dst, v_length, &iop_a_src, io1_a_src);
+          &iop_a_dst, io2_a_dst, v_length, &iop_a_src, io2_a_src);
       if (v_length <= v_n_copied) {
         status = NULL;
         goto ok;
       }
       v_length -= v_n_copied;
-      if (((uint64_t)(io1_a_dst - iop_a_dst)) == 0) {
+      if (((uint64_t)(io2_a_dst - iop_a_dst)) == 0) {
         status = wuffs_base__suspension__short_write;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(3);
       } else {
@@ -6607,18 +6765,17 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_uncompressed[0] = coro_susp_point;
+  self->private_impl.p_decode_uncompressed[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
   self->private_data.s_decode_uncompressed[0].v_length = v_length;
 
   goto exit;
 exit:
-  if (a_dst.private_impl.buf) {
-    a_dst.private_impl.buf->meta.wi =
-        ((size_t)(iop_a_dst - a_dst.private_impl.buf->data.ptr));
+  if (a_dst) {
+    a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
   }
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -6666,7 +6823,7 @@
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__init_dynamic_huffman(wuffs_deflate__decoder* self,
-                                             wuffs_base__io_reader a_src) {
+                                             wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint32_t v_bits = 0;
@@ -6690,16 +6847,12 @@
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_init_dynamic_huffman[0];
@@ -6725,7 +6878,7 @@
     while (v_n_bits < 14) {
       {
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-        if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+        if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
           status = wuffs_base__suspension__short_read;
           goto suspend;
         }
@@ -6755,7 +6908,7 @@
       while (v_n_bits < 3) {
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -6794,7 +6947,7 @@
         }
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -6843,7 +6996,7 @@
       while (v_n_bits < v_n_extra_bits) {
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -6896,7 +7049,8 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_init_dynamic_huffman[0] = coro_susp_point;
+  self->private_impl.p_init_dynamic_huffman[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
   self->private_data.s_init_dynamic_huffman[0].v_bits = v_bits;
   self->private_data.s_init_dynamic_huffman[0].v_n_bits = v_n_bits;
   self->private_data.s_init_dynamic_huffman[0].v_n_lit = v_n_lit;
@@ -6911,9 +7065,8 @@
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -7178,8 +7331,8 @@
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__decode_huffman_fast(wuffs_deflate__decoder* self,
-                                            wuffs_base__io_writer a_dst,
-                                            wuffs_base__io_reader a_src) {
+                                            wuffs_base__io_buffer* a_dst,
+                                            wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint32_t v_bits = 0;
@@ -7199,33 +7352,25 @@
   uint8_t* iop_a_dst = NULL;
   uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_dst.private_impl.buf) {
-    iop_a_dst =
-        a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->meta.wi;
-    if (!a_dst.private_impl.mark) {
-      a_dst.private_impl.mark = iop_a_dst;
-      a_dst.private_impl.limit =
-          a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->data.len;
+  uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_dst) {
+    io0_a_dst = a_dst->data.ptr;
+    io1_a_dst = io0_a_dst + a_dst->meta.wi;
+    iop_a_dst = io1_a_dst;
+    io2_a_dst = io0_a_dst + a_dst->data.len;
+    if (a_dst->meta.closed) {
+      io2_a_dst = iop_a_dst;
     }
-    if (a_dst.private_impl.buf->meta.closed) {
-      a_dst.private_impl.limit = iop_a_dst;
-    }
-    io0_a_dst = a_dst.private_impl.mark;
-    io1_a_dst = a_dst.private_impl.limit;
   }
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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;
   }
 
   if ((self->private_impl.f_n_bits >= 8) ||
@@ -7238,8 +7383,8 @@
   v_lmask = ((((uint32_t)(1)) << self->private_impl.f_n_huffs_bits[0]) - 1);
   v_dmask = ((((uint32_t)(1)) << self->private_impl.f_n_huffs_bits[1]) - 1);
 label_0_continue:;
-  while ((((uint64_t)(io1_a_dst - iop_a_dst)) >= 258) &&
-         (((uint64_t)(io1_a_src - iop_a_src)) >= 12)) {
+  while ((((uint64_t)(io2_a_dst - iop_a_dst)) >= 258) &&
+         (((uint64_t)(io2_a_src - iop_a_src)) >= 12)) {
     if (v_n_bits < 15) {
       v_bits |= (((uint32_t)(wuffs_base__load_u8be(iop_a_src))) << v_n_bits);
       (iop_a_src += 1, wuffs_base__make_empty_struct());
@@ -7391,11 +7536,10 @@
     v_n_copied = 0;
     while (true) {
       if (((uint64_t)((v_dist_minus_1 + 1))) >
-          ((uint64_t)(iop_a_dst - a_dst.private_impl.mark))) {
+          ((uint64_t)(iop_a_dst - a_dst->data.ptr))) {
         v_hlen = 0;
-        v_hdist =
-            ((uint32_t)((((uint64_t)((v_dist_minus_1 + 1))) -
-                         ((uint64_t)(iop_a_dst - a_dst.private_impl.mark)))));
+        v_hdist = ((uint32_t)((((uint64_t)((v_dist_minus_1 + 1))) -
+                               ((uint64_t)(iop_a_dst - a_dst->data.ptr)))));
         if (v_length > v_hdist) {
           v_length -= v_hdist;
           v_hlen = v_hdist;
@@ -7410,7 +7554,7 @@
         v_hdist = (self->private_impl.f_history_index - v_hdist);
         while (true) {
           v_n_copied = wuffs_base__io_writer__copy_n_from_slice(
-              &iop_a_dst, io1_a_dst, v_hlen,
+              &iop_a_dst, io2_a_dst, v_hlen,
               wuffs_base__slice_u8__subslice_i(
                   wuffs_base__make_slice_u8(self->private_data.f_history,
                                             32768),
@@ -7420,7 +7564,7 @@
           }
           v_hlen -= v_n_copied;
           wuffs_base__io_writer__copy_n_from_slice(
-              &iop_a_dst, io1_a_dst, v_hlen,
+              &iop_a_dst, io2_a_dst, v_hlen,
               wuffs_base__make_slice_u8(self->private_data.f_history, 32768));
           goto label_1_break;
         }
@@ -7429,13 +7573,13 @@
           goto label_0_continue;
         }
         if (((uint64_t)((v_dist_minus_1 + 1))) >
-            ((uint64_t)(iop_a_dst - a_dst.private_impl.mark))) {
+            ((uint64_t)(iop_a_dst - a_dst->data.ptr))) {
           status = wuffs_deflate__error__internal_error_inconsistent_distance;
           goto exit;
         }
       }
       wuffs_base__io_writer__copy_n_from_history_fast(
-          &iop_a_dst, a_dst.private_impl.mark, io1_a_dst, v_length,
+          &iop_a_dst, a_dst->data.ptr, io2_a_dst, v_length,
           (v_dist_minus_1 + 1));
       goto label_2_break;
     }
@@ -7444,7 +7588,7 @@
 label_0_break:;
   while (v_n_bits >= 8) {
     v_n_bits -= 8;
-    if (iop_a_src > io0_a_src) {
+    if (iop_a_src > io1_a_src) {
       (iop_a_src--, wuffs_base__make_empty_struct());
     } else {
       status = wuffs_deflate__error__internal_error_inconsistent_i_o;
@@ -7460,13 +7604,11 @@
   }
   goto exit;
 exit:
-  if (a_dst.private_impl.buf) {
-    a_dst.private_impl.buf->meta.wi =
-        ((size_t)(iop_a_dst - a_dst.private_impl.buf->data.ptr));
+  if (a_dst) {
+    a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
   }
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -7476,8 +7618,8 @@
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__decode_huffman_slow(wuffs_deflate__decoder* self,
-                                            wuffs_base__io_writer a_dst,
-                                            wuffs_base__io_reader a_src) {
+                                            wuffs_base__io_buffer* a_dst,
+                                            wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint32_t v_bits = 0;
@@ -7503,33 +7645,25 @@
   uint8_t* iop_a_dst = NULL;
   uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_dst.private_impl.buf) {
-    iop_a_dst =
-        a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->meta.wi;
-    if (!a_dst.private_impl.mark) {
-      a_dst.private_impl.mark = iop_a_dst;
-      a_dst.private_impl.limit =
-          a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->data.len;
+  uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_dst) {
+    io0_a_dst = a_dst->data.ptr;
+    io1_a_dst = io0_a_dst + a_dst->meta.wi;
+    iop_a_dst = io1_a_dst;
+    io2_a_dst = io0_a_dst + a_dst->data.len;
+    if (a_dst->meta.closed) {
+      io2_a_dst = iop_a_dst;
     }
-    if (a_dst.private_impl.buf->meta.closed) {
-      a_dst.private_impl.limit = iop_a_dst;
-    }
-    io0_a_dst = a_dst.private_impl.mark;
-    io1_a_dst = a_dst.private_impl.limit;
   }
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_huffman_slow[0];
@@ -7573,7 +7707,7 @@
         }
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -7586,7 +7720,7 @@
     label_1_break:;
       if ((v_table_entry >> 31) != 0) {
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
-        if (iop_a_dst == io1_a_dst) {
+        if (iop_a_dst == io2_a_dst) {
           status = wuffs_base__suspension__short_write;
           goto suspend;
         }
@@ -7611,7 +7745,7 @@
           }
           {
             WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
-            if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+            if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
               status = wuffs_base__suspension__short_read;
               goto suspend;
             }
@@ -7624,7 +7758,7 @@
       label_2_break:;
         if ((v_table_entry >> 31) != 0) {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
-          if (iop_a_dst == io1_a_dst) {
+          if (iop_a_dst == io2_a_dst) {
             status = wuffs_base__suspension__short_write;
             goto suspend;
           }
@@ -7660,7 +7794,7 @@
         while (v_n_bits < v_table_entry_n_bits) {
           {
             WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
-            if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+            if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
               status = wuffs_base__suspension__short_read;
               goto suspend;
             }
@@ -7688,7 +7822,7 @@
         }
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -7714,7 +7848,7 @@
           }
           {
             WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
-            if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+            if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
               status = wuffs_base__suspension__short_read;
               goto suspend;
             }
@@ -7741,7 +7875,7 @@
         while (v_n_bits < v_table_entry_n_bits) {
           {
             WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
-            if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+            if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
               status = wuffs_base__suspension__short_read;
               goto suspend;
             }
@@ -7760,10 +7894,9 @@
       }
       while (true) {
         if (((uint64_t)((v_dist_minus_1 + 1))) >
-            ((uint64_t)(iop_a_dst - a_dst.private_impl.mark))) {
-          v_hdist =
-              ((uint32_t)((((uint64_t)((v_dist_minus_1 + 1))) -
-                           ((uint64_t)(iop_a_dst - a_dst.private_impl.mark)))));
+            ((uint64_t)(iop_a_dst - a_dst->data.ptr))) {
+          v_hdist = ((uint32_t)((((uint64_t)((v_dist_minus_1 + 1))) -
+                                 ((uint64_t)(iop_a_dst - a_dst->data.ptr)))));
           if (v_length > v_hdist) {
             v_length -= v_hdist;
             v_hlen = v_hdist;
@@ -7778,7 +7911,7 @@
           v_hdist = (self->private_impl.f_history_index - v_hdist);
           while (true) {
             v_n_copied = wuffs_base__io_writer__copy_n_from_slice(
-                &iop_a_dst, io1_a_dst, v_hlen,
+                &iop_a_dst, io2_a_dst, v_hlen,
                 wuffs_base__slice_u8__subslice_i(
                     wuffs_base__make_slice_u8(self->private_data.f_history,
                                               32768),
@@ -7801,7 +7934,7 @@
           if (v_hlen > 0) {
             while (true) {
               v_n_copied = wuffs_base__io_writer__copy_n_from_slice(
-                  &iop_a_dst, io1_a_dst, v_hlen,
+                  &iop_a_dst, io2_a_dst, v_hlen,
                   wuffs_base__slice_u8__subslice_i(
                       wuffs_base__make_slice_u8(self->private_data.f_history,
                                                 32768),
@@ -7822,7 +7955,7 @@
           }
         }
         v_n_copied = wuffs_base__io_writer__copy_n_from_history(
-            &iop_a_dst, a_dst.private_impl.mark, io1_a_dst, v_length,
+            &iop_a_dst, a_dst->data.ptr, io2_a_dst, v_length,
             (v_dist_minus_1 + 1));
         if (v_length <= v_n_copied) {
           v_length = 0;
@@ -7852,7 +7985,8 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_huffman_slow[0] = coro_susp_point;
+  self->private_impl.p_decode_huffman_slow[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
   self->private_data.s_decode_huffman_slow[0].v_bits = v_bits;
   self->private_data.s_decode_huffman_slow[0].v_n_bits = v_n_bits;
   self->private_data.s_decode_huffman_slow[0].v_table_entry = v_table_entry;
@@ -7869,13 +8003,11 @@
 
   goto exit;
 exit:
-  if (a_dst.private_impl.buf) {
-    a_dst.private_impl.buf->meta.wi =
-        ((size_t)(iop_a_dst - a_dst.private_impl.buf->data.ptr));
+  if (a_dst) {
+    a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
   }
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -7900,11 +8032,11 @@
 
 static wuffs_base__empty_struct  //
 wuffs_lzw__decoder__read_from(wuffs_lzw__decoder* self,
-                              wuffs_base__io_reader a_src);
+                              wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_lzw__decoder__write_to(wuffs_lzw__decoder* self,
-                             wuffs_base__io_writer a_dst);
+                             wuffs_base__io_buffer* a_dst);
 
 // ---------------- Initializer Implementations
 
@@ -7970,12 +8102,12 @@
   if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
     return wuffs_base__make_empty_struct();
   }
-  if (a_lw < 2 || a_lw > 8) {
+  if (a_lw > 8) {
     self->private_impl.magic = WUFFS_BASE__DISABLED;
     return wuffs_base__make_empty_struct();
   }
 
-  self->private_impl.f_set_literal_width_arg = a_lw;
+  self->private_impl.f_set_literal_width_arg = (a_lw + 1);
   return wuffs_base__make_empty_struct();
 }
 
@@ -7998,8 +8130,8 @@
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_lzw__decoder__decode_io_writer(wuffs_lzw__decoder* self,
-                                     wuffs_base__io_writer a_dst,
-                                     wuffs_base__io_reader a_src,
+                                     wuffs_base__io_buffer* a_dst,
+                                     wuffs_base__io_buffer* a_src,
                                      wuffs_base__slice_u8 a_workbuf) {
   if (!self) {
     return wuffs_base__error__bad_receiver;
@@ -8009,6 +8141,10 @@
                ? 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__error__bad_argument;
+  }
   if ((self->private_impl.active_coroutine != 0) &&
       (self->private_impl.active_coroutine != 1)) {
     self->private_impl.magic = WUFFS_BASE__DISABLED;
@@ -8026,9 +8162,9 @@
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
     self->private_impl.f_literal_width = 8;
-    if (self->private_impl.f_set_literal_width_arg >= 2) {
+    if (self->private_impl.f_set_literal_width_arg > 0) {
       self->private_impl.f_literal_width =
-          self->private_impl.f_set_literal_width_arg;
+          (self->private_impl.f_set_literal_width_arg - 1);
     }
     self->private_impl.f_clear_code =
         (((uint32_t)(1)) << self->private_impl.f_literal_width);
@@ -8081,8 +8217,10 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_io_writer[0] = coro_susp_point;
-  self->private_impl.active_coroutine = 1;
+  self->private_impl.p_decode_io_writer[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
+  self->private_impl.active_coroutine =
+      wuffs_base__status__is_suspension(status) ? 1 : 0;
 
   goto exit;
 exit:
@@ -8096,7 +8234,7 @@
 
 static wuffs_base__empty_struct  //
 wuffs_lzw__decoder__read_from(wuffs_lzw__decoder* self,
-                              wuffs_base__io_reader a_src) {
+                              wuffs_base__io_buffer* a_src) {
   uint32_t v_clear_code = 0;
   uint32_t v_end_code = 0;
   uint32_t v_save_code = 0;
@@ -8116,16 +8254,12 @@
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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;
   }
 
   v_clear_code = self->private_impl.f_clear_code;
@@ -8138,11 +8272,11 @@
   v_output_wi = self->private_impl.f_output_wi;
   while (true) {
     if (v_n_bits < v_width) {
-      if (((uint64_t)(io1_a_src - iop_a_src)) >= 4) {
+      if (((uint64_t)(io2_a_src - iop_a_src)) >= 4) {
         v_bits |= (wuffs_base__load_u32le(iop_a_src) << v_n_bits);
         (iop_a_src += ((31 - v_n_bits) >> 3), wuffs_base__make_empty_struct());
         v_n_bits |= 24;
-      } else if (((uint64_t)(io1_a_src - iop_a_src)) <= 0) {
+      } else if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) {
         self->private_impl.f_read_from_return_value = 2;
         goto label_0_break;
       } else {
@@ -8150,7 +8284,7 @@
         (iop_a_src += 1, wuffs_base__make_empty_struct());
         v_n_bits += 8;
         if (v_n_bits >= v_width) {
-        } else if (((uint64_t)(io1_a_src - iop_a_src)) <= 0) {
+        } else if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) {
           self->private_impl.f_read_from_return_value = 2;
           goto label_0_break;
         } else {
@@ -8265,7 +8399,7 @@
   if (self->private_impl.f_read_from_return_value != 2) {
     while (v_n_bits >= 8) {
       v_n_bits -= 8;
-      if (iop_a_src > io0_a_src) {
+      if (iop_a_src > io1_a_src) {
         (iop_a_src--, wuffs_base__make_empty_struct());
       } else {
         self->private_impl.f_read_from_return_value = 4;
@@ -8280,9 +8414,8 @@
   self->private_impl.f_bits = v_bits;
   self->private_impl.f_n_bits = v_n_bits;
   self->private_impl.f_output_wi = v_output_wi;
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return wuffs_base__make_empty_struct();
@@ -8292,7 +8425,7 @@
 
 static wuffs_base__status  //
 wuffs_lzw__decoder__write_to(wuffs_lzw__decoder* self,
-                             wuffs_base__io_writer a_dst) {
+                             wuffs_base__io_buffer* a_dst) {
   wuffs_base__status status = NULL;
 
   wuffs_base__slice_u8 v_s = {0};
@@ -8301,19 +8434,15 @@
   uint8_t* iop_a_dst = NULL;
   uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_dst.private_impl.buf) {
-    iop_a_dst =
-        a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->meta.wi;
-    if (!a_dst.private_impl.mark) {
-      a_dst.private_impl.mark = iop_a_dst;
-      a_dst.private_impl.limit =
-          a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->data.len;
+  uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_dst) {
+    io0_a_dst = a_dst->data.ptr;
+    io1_a_dst = io0_a_dst + a_dst->meta.wi;
+    iop_a_dst = io1_a_dst;
+    io2_a_dst = io0_a_dst + a_dst->data.len;
+    if (a_dst->meta.closed) {
+      io2_a_dst = iop_a_dst;
     }
-    if (a_dst.private_impl.buf->meta.closed) {
-      a_dst.private_impl.limit = iop_a_dst;
-    }
-    io0_a_dst = a_dst.private_impl.mark;
-    io1_a_dst = a_dst.private_impl.limit;
   }
 
   uint32_t coro_susp_point = self->private_impl.p_write_to[0];
@@ -8330,7 +8459,7 @@
       v_s = wuffs_base__slice_u8__subslice_ij(
           wuffs_base__make_slice_u8(self->private_data.f_output, 8199),
           self->private_impl.f_output_ri, self->private_impl.f_output_wi);
-      v_n = wuffs_base__io_writer__copy_from_slice(&iop_a_dst, io1_a_dst, v_s);
+      v_n = wuffs_base__io_writer__copy_from_slice(&iop_a_dst, io2_a_dst, v_s);
       if (v_n == ((uint64_t)(v_s.len))) {
         self->private_impl.f_output_ri = 0;
         self->private_impl.f_output_wi = 0;
@@ -8352,13 +8481,13 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_write_to[0] = coro_susp_point;
+  self->private_impl.p_write_to[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
 
   goto exit;
 exit:
-  if (a_dst.private_impl.buf) {
-    a_dst.private_impl.buf->meta.wi =
-        ((size_t)(iop_a_dst - a_dst.private_impl.buf->data.ptr));
+  if (a_dst) {
+    a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
   }
 
   return status;
@@ -8395,9 +8524,11 @@
 
 const char* wuffs_gif__error__bad_block = "#gif: bad block";
 const char* wuffs_gif__error__bad_extension_label = "#gif: bad extension label";
+const char* wuffs_gif__error__bad_frame_size = "#gif: bad frame size";
 const char* wuffs_gif__error__bad_graphic_control = "#gif: bad graphic control";
 const char* wuffs_gif__error__bad_header = "#gif: bad header";
 const char* wuffs_gif__error__bad_literal_width = "#gif: bad literal width";
+const char* wuffs_gif__error__bad_palette = "#gif: bad palette";
 const char* wuffs_gif__error__internal_error_inconsistent_ri_wi =
     "#gif: internal error: inconsistent ri/wi";
 
@@ -8427,58 +8558,70 @@
         78, 69, 84, 83, 67, 65, 80, 69, 50, 46, 48,
 };
 
+static const uint8_t            //
+    wuffs_gif__iccrgbg1012[11]  //
+    WUFFS_BASE__POTENTIALLY_UNUSED = {
+        73, 67, 67, 82, 71, 66, 71, 49, 48, 49, 50,
+};
+
+static const uint8_t           //
+    wuffs_gif__xmpdataxmp[11]  //
+    WUFFS_BASE__POTENTIALLY_UNUSED = {
+        88, 77, 80, 32, 68, 97, 116, 97, 88, 77, 80,
+};
+
 // ---------------- Private Initializer Prototypes
 
 // ---------------- Private Function Prototypes
 
 static wuffs_base__status  //
 wuffs_gif__decoder__skip_frame(wuffs_gif__decoder* self,
-                               wuffs_base__io_reader a_src);
+                               wuffs_base__io_buffer* a_src);
 
 static wuffs_base__empty_struct  //
 wuffs_gif__decoder__reset_gc(wuffs_gif__decoder* self);
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_up_to_id_part1(wuffs_gif__decoder* self,
-                                          wuffs_base__io_reader a_src);
+                                          wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_header(wuffs_gif__decoder* self,
-                                  wuffs_base__io_reader a_src);
+                                  wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_lsd(wuffs_gif__decoder* self,
-                               wuffs_base__io_reader a_src);
+                               wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_extension(wuffs_gif__decoder* self,
-                                     wuffs_base__io_reader a_src);
+                                     wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_gif__decoder__skip_blocks(wuffs_gif__decoder* self,
-                                wuffs_base__io_reader a_src);
+                                wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_ae(wuffs_gif__decoder* self,
-                              wuffs_base__io_reader a_src);
+                              wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_gc(wuffs_gif__decoder* self,
-                              wuffs_base__io_reader a_src);
+                              wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_id_part0(wuffs_gif__decoder* self,
-                                    wuffs_base__io_reader a_src);
+                                    wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_id_part1(wuffs_gif__decoder* self,
                                     wuffs_base__pixel_buffer* a_dst,
-                                    wuffs_base__io_reader a_src);
+                                    wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_id_part2(wuffs_gif__decoder* self,
                                     wuffs_base__pixel_buffer* a_dst,
-                                    wuffs_base__io_reader a_src,
+                                    wuffs_base__io_buffer* a_src,
                                     wuffs_base__slice_u8 a_workbuf);
 
 static wuffs_base__status  //
@@ -8548,12 +8691,47 @@
 
 // ---------------- Function Implementations
 
+// -------- func gif.decoder.set_quirk_enabled
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct  //
+wuffs_gif__decoder__set_quirk_enabled(wuffs_gif__decoder* self,
+                                      uint32_t a_quirk,
+                                      bool a_enabled) {
+  if (!self) {
+    return wuffs_base__make_empty_struct();
+  }
+  if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+    return wuffs_base__make_empty_struct();
+  }
+
+  if (self->private_impl.f_call_sequence == 0) {
+    if (a_quirk == 1041635328) {
+      self->private_impl.f_quirk_enabled_delay_num_decoded_frames = a_enabled;
+    } else if (a_quirk == 1041635329) {
+      self->private_impl
+          .f_quirk_enabled_first_frame_local_palette_means_black_background =
+          a_enabled;
+    } else if (a_quirk == 1041635330) {
+      self->private_impl.f_quirk_enabled_honor_background_color = a_enabled;
+    } else if (a_quirk == 1041635331) {
+      self->private_impl.f_quirk_enabled_ignore_too_much_pixel_data = a_enabled;
+    } else if (a_quirk == 1041635332) {
+      self->private_impl.f_quirk_enabled_image_bounds_are_strict = a_enabled;
+    } else if (a_quirk == 1041635333) {
+      self->private_impl.f_quirk_enabled_reject_empty_frame = a_enabled;
+    } else if (a_quirk == 1041635334) {
+      self->private_impl.f_quirk_enabled_reject_empty_palette = a_enabled;
+    }
+  }
+  return wuffs_base__make_empty_struct();
+}
+
 // -------- func gif.decoder.decode_image_config
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_gif__decoder__decode_image_config(wuffs_gif__decoder* self,
                                         wuffs_base__image_config* a_dst,
-                                        wuffs_base__io_reader a_src) {
+                                        wuffs_base__io_buffer* a_src) {
   if (!self) {
     return wuffs_base__error__bad_receiver;
   }
@@ -8562,6 +8740,10 @@
                ? 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__error__bad_argument;
+  }
   if ((self->private_impl.active_coroutine != 0) &&
       (self->private_impl.active_coroutine != 1)) {
     self->private_impl.magic = WUFFS_BASE__DISABLED;
@@ -8578,38 +8760,47 @@
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
-    if (self->private_impl.f_call_sequence >= 1) {
+    if (self->private_impl.f_call_sequence == 0) {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
+      status = wuffs_gif__decoder__decode_header(self, a_src);
+      if (status) {
+        goto suspend;
+      }
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
+      status = wuffs_gif__decoder__decode_lsd(self, a_src);
+      if (status) {
+        goto suspend;
+      }
+    } else if (self->private_impl.f_call_sequence != 2) {
       status = wuffs_base__error__bad_call_sequence;
       goto exit;
     }
-    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-    status = wuffs_gif__decoder__decode_header(self, a_src);
-    if (status) {
-      goto suspend;
-    }
-    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
-    status = wuffs_gif__decoder__decode_lsd(self, a_src);
-    if (status) {
-      goto suspend;
-    }
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
     status = wuffs_gif__decoder__decode_up_to_id_part1(self, a_src);
     if (status) {
       goto suspend;
     }
-    v_ffio =
-        (!self->private_impl.f_gc_has_transparent_index &&
-         (self->private_impl.f_frame_rect_x0 == 0) &&
-         (self->private_impl.f_frame_rect_y0 == 0) &&
-         (self->private_impl.f_frame_rect_x1 == self->private_impl.f_width) &&
-         (self->private_impl.f_frame_rect_y1 == self->private_impl.f_height));
+    v_ffio = !self->private_impl.f_gc_has_transparent_index;
+    if (!self->private_impl.f_quirk_enabled_honor_background_color) {
+      v_ffio =
+          (v_ffio && (self->private_impl.f_frame_rect_x0 == 0) &&
+           (self->private_impl.f_frame_rect_y0 == 0) &&
+           (self->private_impl.f_frame_rect_x1 == self->private_impl.f_width) &&
+           (self->private_impl.f_frame_rect_y1 == self->private_impl.f_height));
+    } else if (v_ffio) {
+      self->private_impl.f_black_color_u32_argb_premul = 4278190080;
+    }
+    if (self->private_impl.f_background_color_u32_argb_premul == 77) {
+      self->private_impl.f_background_color_u32_argb_premul =
+          self->private_impl.f_black_color_u32_argb_premul;
+    }
     if (a_dst != NULL) {
       wuffs_base__image_config__set(
           a_dst, 1191444488, 0, self->private_impl.f_width,
           self->private_impl.f_height,
           self->private_impl.f_frame_config_io_position, v_ffio);
     }
-    self->private_impl.f_call_sequence = 1;
+    self->private_impl.f_call_sequence = 3;
 
     goto ok;
   ok:
@@ -8619,8 +8810,10 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_image_config[0] = coro_susp_point;
-  self->private_impl.active_coroutine = 1;
+  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;
 
   goto exit;
 exit:
@@ -8630,6 +8823,172 @@
   return status;
 }
 
+// -------- func gif.decoder.set_report_metadata
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct  //
+wuffs_gif__decoder__set_report_metadata(wuffs_gif__decoder* self,
+                                        uint32_t a_fourcc,
+                                        bool a_report) {
+  if (!self) {
+    return wuffs_base__make_empty_struct();
+  }
+  if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+    return wuffs_base__make_empty_struct();
+  }
+
+  if (a_fourcc == 1229144912) {
+    self->private_impl.f_report_metadata_iccp = a_report;
+  } else if (a_fourcc == 1481461792) {
+    self->private_impl.f_report_metadata_xmp = a_report;
+  }
+  return wuffs_base__make_empty_struct();
+}
+
+// -------- func gif.decoder.ack_metadata_chunk
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
+wuffs_gif__decoder__ack_metadata_chunk(wuffs_gif__decoder* self,
+                                       wuffs_base__io_buffer* a_src) {
+  if (!self) {
+    return wuffs_base__error__bad_receiver;
+  }
+  if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+    return (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__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__error__interleaved_coroutine_calls;
+  }
+  self->private_impl.active_coroutine = 0;
+  wuffs_base__status status = NULL;
+
+  uint8_t* iop_a_src = NULL;
+  uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  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_ack_metadata_chunk[0];
+  if (coro_susp_point) {
+  }
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    if (self->private_impl.f_call_sequence != 1) {
+      status = wuffs_base__error__bad_call_sequence;
+      goto exit;
+    }
+    if ((a_src ? wuffs_base__u64__sat_add(
+                     a_src->meta.pos, ((uint64_t)(iop_a_src - a_src->data.ptr)))
+               : 0) != self->private_impl.f_metadata_io_position) {
+      status = wuffs_base__error__bad_i_o_position;
+      goto exit;
+    }
+    while (((uint64_t)(io2_a_src - iop_a_src)) <= 0) {
+      status = wuffs_base__suspension__short_read;
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1);
+    }
+    if (self->private_impl.f_metadata_fourcc_value == 1481461792) {
+      self->private_impl.f_metadata_chunk_length_value =
+          (((uint64_t)(wuffs_base__load_u8be(iop_a_src))) + 1);
+      if (self->private_impl.f_metadata_chunk_length_value > 1) {
+        self->private_impl.f_metadata_io_position = wuffs_base__u64__sat_add(
+            (a_src ? wuffs_base__u64__sat_add(
+                         a_src->meta.pos,
+                         ((uint64_t)(iop_a_src - a_src->data.ptr)))
+                   : 0),
+            self->private_impl.f_metadata_chunk_length_value);
+        status = wuffs_base__warning__metadata_reported;
+        goto ok;
+      }
+    } else {
+      self->private_impl.f_metadata_chunk_length_value =
+          ((uint64_t)(wuffs_base__load_u8be(iop_a_src)));
+      if (self->private_impl.f_metadata_chunk_length_value > 0) {
+        (iop_a_src += 1, wuffs_base__make_empty_struct());
+        self->private_impl.f_metadata_io_position = wuffs_base__u64__sat_add(
+            (a_src ? wuffs_base__u64__sat_add(
+                         a_src->meta.pos,
+                         ((uint64_t)(iop_a_src - a_src->data.ptr)))
+                   : 0),
+            self->private_impl.f_metadata_chunk_length_value);
+        status = wuffs_base__warning__metadata_reported;
+        goto ok;
+      }
+    }
+    (iop_a_src += 1, wuffs_base__make_empty_struct());
+    self->private_impl.f_call_sequence = 2;
+    self->private_impl.f_metadata_fourcc_value = 0;
+    self->private_impl.f_metadata_io_position = 0;
+    status = NULL;
+    goto ok;
+    goto ok;
+  ok:
+    self->private_impl.p_ack_metadata_chunk[0] = 0;
+    goto exit;
+  }
+
+  goto suspend;
+suspend:
+  self->private_impl.p_ack_metadata_chunk[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 gif.decoder.metadata_fourcc
+
+WUFFS_BASE__MAYBE_STATIC uint32_t  //
+wuffs_gif__decoder__metadata_fourcc(const wuffs_gif__decoder* self) {
+  if (!self) {
+    return 0;
+  }
+  if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&
+      (self->private_impl.magic != WUFFS_BASE__DISABLED)) {
+    return 0;
+  }
+
+  return self->private_impl.f_metadata_fourcc_value;
+}
+
+// -------- func gif.decoder.metadata_chunk_length
+
+WUFFS_BASE__MAYBE_STATIC uint64_t  //
+wuffs_gif__decoder__metadata_chunk_length(const wuffs_gif__decoder* self) {
+  if (!self) {
+    return 0;
+  }
+  if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&
+      (self->private_impl.magic != WUFFS_BASE__DISABLED)) {
+    return 0;
+  }
+
+  return self->private_impl.f_metadata_chunk_length_value;
+}
+
 // -------- func gif.decoder.num_animation_loops
 
 WUFFS_BASE__MAYBE_STATIC uint32_t  //
@@ -8732,6 +9091,7 @@
   if (self->private_impl.f_call_sequence == 0) {
     return wuffs_base__error__bad_call_sequence;
   }
+  self->private_impl.f_delayed_num_decoded_frames = false;
   self->private_impl.f_end_of_data = false;
   self->private_impl.f_restarted = true;
   self->private_impl.f_frame_config_io_position = a_io_position;
@@ -8746,7 +9106,7 @@
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_gif__decoder__decode_frame_config(wuffs_gif__decoder* self,
                                         wuffs_base__frame_config* a_dst,
-                                        wuffs_base__io_reader a_src) {
+                                        wuffs_base__io_buffer* a_src) {
   if (!self) {
     return wuffs_base__error__bad_receiver;
   }
@@ -8755,8 +9115,12 @@
                ? 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__error__bad_argument;
+  }
   if ((self->private_impl.active_coroutine != 0) &&
-      (self->private_impl.active_coroutine != 2)) {
+      (self->private_impl.active_coroutine != 3)) {
     self->private_impl.magic = WUFFS_BASE__DISABLED;
     return wuffs_base__error__interleaved_coroutine_calls;
   }
@@ -8764,32 +9128,67 @@
   wuffs_base__status status = NULL;
 
   uint8_t v_blend = 0;
+  uint32_t v_background_color = 0;
+  uint8_t v_flags = 0;
+
+  uint8_t* iop_a_src = NULL;
+  uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  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];
   if (coro_susp_point) {
+    v_blend = self->private_data.s_decode_frame_config[0].v_blend;
+    v_background_color =
+        self->private_data.s_decode_frame_config[0].v_background_color;
   }
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
+    self->private_impl.f_ignore_metadata = true;
     (memset(&self->private_impl.f_dirty_y, 0, sizeof(wuffs_base__range_ie_u32)),
      wuffs_base__make_empty_struct());
     if (!self->private_impl.f_end_of_data) {
       if (self->private_impl.f_call_sequence == 0) {
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+        }
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
         status = wuffs_gif__decoder__decode_image_config(self, NULL, a_src);
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
+        }
         if (status) {
           goto suspend;
         }
-      } else if (self->private_impl.f_call_sequence != 1) {
-        if (self->private_impl.f_call_sequence == 2) {
+      } else if (self->private_impl.f_call_sequence != 3) {
+        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(2);
           status = wuffs_gif__decoder__skip_frame(self, a_src);
+          if (a_src) {
+            iop_a_src = a_src->data.ptr + a_src->meta.ri;
+          }
           if (status) {
             goto suspend;
           }
         }
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+        }
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
         status = wuffs_gif__decoder__decode_up_to_id_part1(self, a_src);
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
+        }
         if (status) {
           goto suspend;
         }
@@ -8800,8 +9199,23 @@
       goto ok;
     }
     v_blend = 0;
+    v_background_color = self->private_impl.f_black_color_u32_argb_premul;
     if (!self->private_impl.f_gc_has_transparent_index) {
       v_blend = 2;
+      v_background_color =
+          self->private_impl.f_background_color_u32_argb_premul;
+      if (self->private_impl
+              .f_quirk_enabled_first_frame_local_palette_means_black_background &&
+          (self->private_impl.f_num_decoded_frame_configs_value == 0)) {
+        while (((uint64_t)(io2_a_src - iop_a_src)) <= 0) {
+          status = wuffs_base__suspension__short_read;
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(4);
+        }
+        v_flags = wuffs_base__load_u8be(iop_a_src);
+        if ((v_flags & 128) != 0) {
+          v_background_color = self->private_impl.f_black_color_u32_argb_premul;
+        }
+      }
     }
     if (a_dst != NULL) {
       wuffs_base__frame_config__update(
@@ -8818,11 +9232,11 @@
           ((wuffs_base__flicks)(self->private_impl.f_gc_duration)),
           self->private_impl.f_num_decoded_frame_configs_value,
           self->private_impl.f_frame_config_io_position, v_blend,
-          self->private_impl.f_gc_disposal);
+          self->private_impl.f_gc_disposal, v_background_color);
     }
     wuffs_base__u64__sat_add_indirect(
         &self->private_impl.f_num_decoded_frame_configs_value, 1);
-    self->private_impl.f_call_sequence = 2;
+    self->private_impl.f_call_sequence = 4;
 
     goto ok;
   ok:
@@ -8832,11 +9246,20 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_frame_config[0] = coro_susp_point;
-  self->private_impl.active_coroutine = 2;
+  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) ? 3 : 0;
+  self->private_data.s_decode_frame_config[0].v_blend = v_blend;
+  self->private_data.s_decode_frame_config[0].v_background_color =
+      v_background_color;
 
   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;
   }
@@ -8847,24 +9270,21 @@
 
 static wuffs_base__status  //
 wuffs_gif__decoder__skip_frame(wuffs_gif__decoder* self,
-                               wuffs_base__io_reader a_src) {
+                               wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint8_t v_flags = 0;
+  uint8_t v_lw = 0;
 
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_skip_frame[0];
@@ -8875,7 +9295,7 @@
 
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -8887,36 +9307,45 @@
           (((uint32_t)(3)) << (1 + (v_flags & 7)));
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
       if (self->private_data.s_skip_frame[0].scratch >
-          ((uint64_t)(io1_a_src - iop_a_src))) {
+          ((uint64_t)(io2_a_src - iop_a_src))) {
         self->private_data.s_skip_frame[0].scratch -=
-            ((uint64_t)(io1_a_src - iop_a_src));
-        iop_a_src = io1_a_src;
+            ((uint64_t)(io2_a_src - iop_a_src));
+        iop_a_src = io2_a_src;
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
       iop_a_src += self->private_data.s_skip_frame[0].scratch;
     }
-    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
-    if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
-      status = wuffs_base__suspension__short_read;
-      goto suspend;
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+        status = wuffs_base__suspension__short_read;
+        goto suspend;
+      }
+      uint8_t t_1 = *iop_a_src++;
+      v_lw = t_1;
     }
-    iop_a_src++;
-    if (a_src.private_impl.buf) {
-      a_src.private_impl.buf->meta.ri =
-          ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+    if (v_lw > 8) {
+      status = wuffs_gif__error__bad_literal_width;
+      goto exit;
+    }
+    if (a_src) {
+      a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
     }
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
     status = wuffs_gif__decoder__skip_blocks(self, a_src);
-    if (a_src.private_impl.buf) {
-      iop_a_src =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
+    if (a_src) {
+      iop_a_src = a_src->data.ptr + a_src->meta.ri;
     }
     if (status) {
       goto suspend;
     }
-    wuffs_base__u64__sat_add_indirect(
-        &self->private_impl.f_num_decoded_frames_value, 1);
+    if (self->private_impl.f_quirk_enabled_delay_num_decoded_frames) {
+      self->private_impl.f_delayed_num_decoded_frames = true;
+    } else {
+      wuffs_base__u64__sat_add_indirect(
+          &self->private_impl.f_num_decoded_frames_value, 1);
+    }
     wuffs_gif__decoder__reset_gc(self);
 
     goto ok;
@@ -8927,13 +9356,13 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_skip_frame[0] = coro_susp_point;
+  self->private_impl.p_skip_frame[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -8944,7 +9373,7 @@
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_gif__decoder__decode_frame(wuffs_gif__decoder* self,
                                  wuffs_base__pixel_buffer* a_dst,
-                                 wuffs_base__io_reader a_src,
+                                 wuffs_base__io_buffer* a_src,
                                  wuffs_base__slice_u8 a_workbuf,
                                  wuffs_base__decode_frame_options* a_opts) {
   if (!self) {
@@ -8955,12 +9384,12 @@
                ? wuffs_base__error__disabled_by_previous_error
                : wuffs_base__error__initialize_not_called;
   }
-  if (!a_dst) {
+  if (!a_dst || !a_src) {
     self->private_impl.magic = WUFFS_BASE__DISABLED;
     return wuffs_base__error__bad_argument;
   }
   if ((self->private_impl.active_coroutine != 0) &&
-      (self->private_impl.active_coroutine != 3)) {
+      (self->private_impl.active_coroutine != 4)) {
     self->private_impl.magic = WUFFS_BASE__DISABLED;
     return wuffs_base__error__interleaved_coroutine_calls;
   }
@@ -8973,13 +9402,22 @@
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
-    if (self->private_impl.f_call_sequence != 2) {
+    self->private_impl.f_ignore_metadata = true;
+    if (self->private_impl.f_call_sequence != 4) {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
       status = wuffs_gif__decoder__decode_frame_config(self, NULL, a_src);
       if (status) {
         goto suspend;
       }
     }
+    if (self->private_impl.f_quirk_enabled_reject_empty_frame &&
+        ((self->private_impl.f_frame_rect_x0 ==
+          self->private_impl.f_frame_rect_x1) ||
+         (self->private_impl.f_frame_rect_y0 ==
+          self->private_impl.f_frame_rect_y1))) {
+      status = wuffs_gif__error__bad_frame_size;
+      goto exit;
+    }
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
     status = wuffs_gif__decoder__decode_id_part1(self, a_dst, a_src);
     if (status) {
@@ -9002,8 +9440,10 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_frame[0] = coro_susp_point;
-  self->private_impl.active_coroutine = 3;
+  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) ? 4 : 0;
 
   goto exit;
 exit:
@@ -9017,8 +9457,7 @@
 
 static wuffs_base__empty_struct  //
 wuffs_gif__decoder__reset_gc(wuffs_gif__decoder* self) {
-  self->private_impl.f_call_sequence = 3;
-  self->private_impl.f_seen_graphic_control = false;
+  self->private_impl.f_call_sequence = 5;
   self->private_impl.f_gc_has_transparent_index = false;
   self->private_impl.f_gc_transparent_index = 0;
   self->private_impl.f_gc_disposal = 0;
@@ -9030,7 +9469,7 @@
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_up_to_id_part1(wuffs_gif__decoder* self,
-                                          wuffs_base__io_reader a_src) {
+                                          wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint8_t v_block_type = 0;
@@ -9038,16 +9477,12 @@
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_up_to_id_part1[0];
@@ -9057,19 +9492,18 @@
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
     if (!self->private_impl.f_restarted) {
-      self->private_impl.f_frame_config_io_position =
-          (a_src.private_impl.buf
-               ? wuffs_base__u64__sat_add(
-                     a_src.private_impl.buf->meta.pos,
-                     ((uint64_t)(iop_a_src - a_src.private_impl.buf->data.ptr)))
-               : 0);
+      if (self->private_impl.f_call_sequence != 2) {
+        self->private_impl.f_frame_config_io_position =
+            (a_src ? wuffs_base__u64__sat_add(
+                         a_src->meta.pos,
+                         ((uint64_t)(iop_a_src - a_src->data.ptr)))
+                   : 0);
+      }
     } else if (self->private_impl.f_frame_config_io_position !=
-               (a_src.private_impl.buf
-                    ? wuffs_base__u64__sat_add(
-                          a_src.private_impl.buf->meta.pos,
-                          ((uint64_t)(iop_a_src -
-                                      a_src.private_impl.buf->data.ptr)))
-                    : 0)) {
+               (a_src ? wuffs_base__u64__sat_add(
+                            a_src->meta.pos,
+                            ((uint64_t)(iop_a_src - a_src->data.ptr)))
+                      : 0)) {
       status = wuffs_base__error__bad_restart;
       goto exit;
     } else {
@@ -9078,7 +9512,7 @@
     while (true) {
       {
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-        if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+        if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
           status = wuffs_base__suspension__short_read;
           goto suspend;
         }
@@ -9086,35 +9520,41 @@
         v_block_type = t_0;
       }
       if (v_block_type == 33) {
-        if (a_src.private_impl.buf) {
-          a_src.private_impl.buf->meta.ri =
-              ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
         }
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
         status = wuffs_gif__decoder__decode_extension(self, a_src);
-        if (a_src.private_impl.buf) {
-          iop_a_src = a_src.private_impl.buf->data.ptr +
-                      a_src.private_impl.buf->meta.ri;
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
         }
         if (status) {
           goto suspend;
         }
       } else if (v_block_type == 44) {
-        if (a_src.private_impl.buf) {
-          a_src.private_impl.buf->meta.ri =
-              ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+        if (self->private_impl.f_delayed_num_decoded_frames) {
+          self->private_impl.f_delayed_num_decoded_frames = false;
+          wuffs_base__u64__sat_add_indirect(
+              &self->private_impl.f_num_decoded_frames_value, 1);
+        }
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
         }
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
         status = wuffs_gif__decoder__decode_id_part0(self, a_src);
-        if (a_src.private_impl.buf) {
-          iop_a_src = a_src.private_impl.buf->data.ptr +
-                      a_src.private_impl.buf->meta.ri;
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
         }
         if (status) {
           goto suspend;
         }
         goto label_0_break;
       } else if (v_block_type == 59) {
+        if (self->private_impl.f_delayed_num_decoded_frames) {
+          self->private_impl.f_delayed_num_decoded_frames = false;
+          wuffs_base__u64__sat_add_indirect(
+              &self->private_impl.f_num_decoded_frames_value, 1);
+        }
         self->private_impl.f_end_of_data = true;
         goto label_0_break;
       } else {
@@ -9132,13 +9572,13 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_up_to_id_part1[0] = coro_susp_point;
+  self->private_impl.p_decode_up_to_id_part1[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -9148,7 +9588,7 @@
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_header(wuffs_gif__decoder* self,
-                                  wuffs_base__io_reader a_src) {
+                                  wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint8_t v_c[6] = {0};
@@ -9157,16 +9597,12 @@
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_header[0];
@@ -9180,7 +9616,7 @@
     while (v_i < 6) {
       {
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-        if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+        if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
           status = wuffs_base__suspension__short_read;
           goto suspend;
         }
@@ -9203,15 +9639,15 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_header[0] = coro_susp_point;
+  self->private_impl.p_decode_header[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
   memcpy(self->private_data.s_decode_header[0].v_c, v_c, sizeof(v_c));
   self->private_data.s_decode_header[0].v_i = v_i;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -9221,32 +9657,32 @@
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_lsd(wuffs_gif__decoder* self,
-                               wuffs_base__io_reader a_src) {
+                               wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint8_t v_flags = 0;
+  uint8_t v_background_color_index = 0;
   uint32_t v_num_palette_entries = 0;
   uint32_t v_i = 0;
+  uint32_t v_j = 0;
   uint32_t v_argb = 0;
 
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_lsd[0];
   if (coro_susp_point) {
     v_flags = self->private_data.s_decode_lsd[0].v_flags;
+    v_background_color_index =
+        self->private_data.s_decode_lsd[0].v_background_color_index;
     v_num_palette_entries =
         self->private_data.s_decode_lsd[0].v_num_palette_entries;
     v_i = self->private_data.s_decode_lsd[0].v_i;
@@ -9257,14 +9693,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
       uint32_t t_0;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
         t_0 = ((uint32_t)(wuffs_base__load_u16le(iop_a_src)));
         iop_a_src += 2;
       } else {
         self->private_data.s_decode_lsd[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -9286,14 +9722,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
       uint32_t t_1;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
         t_1 = ((uint32_t)(wuffs_base__load_u16le(iop_a_src)));
         iop_a_src += 2;
       } else {
         self->private_data.s_decode_lsd[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -9314,56 +9750,61 @@
     }
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
       uint8_t t_2 = *iop_a_src++;
       v_flags = t_2;
     }
-    self->private_data.s_decode_lsd[0].scratch = 2;
-    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
-    if (self->private_data.s_decode_lsd[0].scratch >
-        ((uint64_t)(io1_a_src - iop_a_src))) {
-      self->private_data.s_decode_lsd[0].scratch -=
-          ((uint64_t)(io1_a_src - iop_a_src));
-      iop_a_src = io1_a_src;
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+        status = wuffs_base__suspension__short_read;
+        goto suspend;
+      }
+      uint8_t t_3 = *iop_a_src++;
+      v_background_color_index = t_3;
+    }
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
+    if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
       status = wuffs_base__suspension__short_read;
       goto suspend;
     }
-    iop_a_src += self->private_data.s_decode_lsd[0].scratch;
-    if ((v_flags & 128) != 0) {
+    iop_a_src++;
+    v_i = 0;
+    self->private_impl.f_has_global_palette = ((v_flags & 128) != 0);
+    if (self->private_impl.f_has_global_palette) {
       v_num_palette_entries = (((uint32_t)(1)) << (1 + (v_flags & 7)));
-      v_i = 0;
       while (v_i < v_num_palette_entries) {
         {
-          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
-          uint32_t t_3;
-          if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 3)) {
-            t_3 = ((uint32_t)(wuffs_base__load_u24be(iop_a_src)));
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
+          uint32_t t_4;
+          if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 3)) {
+            t_4 = ((uint32_t)(wuffs_base__load_u24be(iop_a_src)));
             iop_a_src += 3;
           } else {
             self->private_data.s_decode_lsd[0].scratch = 0;
-            WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
+            WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9);
             while (true) {
-              if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+              if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
                 status = wuffs_base__suspension__short_read;
                 goto suspend;
               }
               uint64_t* scratch = &self->private_data.s_decode_lsd[0].scratch;
-              uint32_t num_bits_3 = ((uint32_t)(*scratch & 0xFF));
+              uint32_t num_bits_4 = ((uint32_t)(*scratch & 0xFF));
               *scratch >>= 8;
               *scratch <<= 8;
-              *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_3);
-              if (num_bits_3 == 16) {
-                t_3 = ((uint32_t)(*scratch >> 40));
+              *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_4);
+              if (num_bits_4 == 16) {
+                t_4 = ((uint32_t)(*scratch >> 40));
                 break;
               }
-              num_bits_3 += 8;
-              *scratch |= ((uint64_t)(num_bits_3));
+              num_bits_4 += 8;
+              *scratch |= ((uint64_t)(num_bits_4));
             }
           }
-          v_argb = t_3;
+          v_argb = t_4;
         }
         v_argb |= 4278190080;
         self->private_data.f_palettes[0][((4 * v_i) + 0)] =
@@ -9376,14 +9817,31 @@
             ((uint8_t)(((v_argb >> 24) & 255)));
         v_i += 1;
       }
-      while (v_i < 256) {
-        self->private_data.f_palettes[0][((4 * v_i) + 0)] = 0;
-        self->private_data.f_palettes[0][((4 * v_i) + 1)] = 0;
-        self->private_data.f_palettes[0][((4 * v_i) + 2)] = 0;
-        self->private_data.f_palettes[0][((4 * v_i) + 3)] = 255;
-        v_i += 1;
+      if (self->private_impl.f_quirk_enabled_honor_background_color) {
+        if ((v_background_color_index != 0) &&
+            (((uint32_t)(v_background_color_index)) < v_num_palette_entries)) {
+          v_j = (4 * ((uint32_t)(v_background_color_index)));
+          self->private_impl.f_background_color_u32_argb_premul =
+              ((((uint32_t)(self->private_data.f_palettes[0][(v_j + 0)]))
+                << 0) |
+               (((uint32_t)(self->private_data.f_palettes[0][(v_j + 1)]))
+                << 8) |
+               (((uint32_t)(self->private_data.f_palettes[0][(v_j + 2)]))
+                << 16) |
+               (((uint32_t)(self->private_data.f_palettes[0][(v_j + 3)]))
+                << 24));
+        } else {
+          self->private_impl.f_background_color_u32_argb_premul = 77;
+        }
       }
     }
+    while (v_i < 256) {
+      self->private_data.f_palettes[0][((4 * v_i) + 0)] = 0;
+      self->private_data.f_palettes[0][((4 * v_i) + 1)] = 0;
+      self->private_data.f_palettes[0][((4 * v_i) + 2)] = 0;
+      self->private_data.f_palettes[0][((4 * v_i) + 3)] = 255;
+      v_i += 1;
+    }
 
     goto ok;
   ok:
@@ -9393,17 +9851,19 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_lsd[0] = coro_susp_point;
+  self->private_impl.p_decode_lsd[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
   self->private_data.s_decode_lsd[0].v_flags = v_flags;
+  self->private_data.s_decode_lsd[0].v_background_color_index =
+      v_background_color_index;
   self->private_data.s_decode_lsd[0].v_num_palette_entries =
       v_num_palette_entries;
   self->private_data.s_decode_lsd[0].v_i = v_i;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -9413,7 +9873,7 @@
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_extension(wuffs_gif__decoder* self,
-                                     wuffs_base__io_reader a_src) {
+                                     wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint8_t v_label = 0;
@@ -9421,16 +9881,12 @@
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_extension[0];
@@ -9441,7 +9897,7 @@
 
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -9449,15 +9905,13 @@
       v_label = t_0;
     }
     if (v_label == 249) {
-      if (a_src.private_impl.buf) {
-        a_src.private_impl.buf->meta.ri =
-            ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+      if (a_src) {
+        a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
       }
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
       status = wuffs_gif__decoder__decode_gc(self, a_src);
-      if (a_src.private_impl.buf) {
-        iop_a_src =
-            a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
+      if (a_src) {
+        iop_a_src = a_src->data.ptr + a_src->meta.ri;
       }
       if (status) {
         goto suspend;
@@ -9465,15 +9919,13 @@
       status = NULL;
       goto ok;
     } else if (v_label == 255) {
-      if (a_src.private_impl.buf) {
-        a_src.private_impl.buf->meta.ri =
-            ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+      if (a_src) {
+        a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
       }
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
       status = wuffs_gif__decoder__decode_ae(self, a_src);
-      if (a_src.private_impl.buf) {
-        iop_a_src =
-            a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
+      if (a_src) {
+        iop_a_src = a_src->data.ptr + a_src->meta.ri;
       }
       if (status) {
         goto suspend;
@@ -9481,15 +9933,13 @@
       status = NULL;
       goto ok;
     }
-    if (a_src.private_impl.buf) {
-      a_src.private_impl.buf->meta.ri =
-          ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+    if (a_src) {
+      a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
     }
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
     status = wuffs_gif__decoder__skip_blocks(self, a_src);
-    if (a_src.private_impl.buf) {
-      iop_a_src =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
+    if (a_src) {
+      iop_a_src = a_src->data.ptr + a_src->meta.ri;
     }
     if (status) {
       goto suspend;
@@ -9503,13 +9953,13 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_extension[0] = coro_susp_point;
+  self->private_impl.p_decode_extension[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -9519,7 +9969,7 @@
 
 static wuffs_base__status  //
 wuffs_gif__decoder__skip_blocks(wuffs_gif__decoder* self,
-                                wuffs_base__io_reader a_src) {
+                                wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint8_t v_block_size = 0;
@@ -9527,16 +9977,12 @@
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_skip_blocks[0];
@@ -9548,7 +9994,7 @@
     while (true) {
       {
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-        if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+        if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
           status = wuffs_base__suspension__short_read;
           goto suspend;
         }
@@ -9562,10 +10008,10 @@
       self->private_data.s_skip_blocks[0].scratch = ((uint32_t)(v_block_size));
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
       if (self->private_data.s_skip_blocks[0].scratch >
-          ((uint64_t)(io1_a_src - iop_a_src))) {
+          ((uint64_t)(io2_a_src - iop_a_src))) {
         self->private_data.s_skip_blocks[0].scratch -=
-            ((uint64_t)(io1_a_src - iop_a_src));
-        iop_a_src = io1_a_src;
+            ((uint64_t)(io2_a_src - iop_a_src));
+        iop_a_src = io2_a_src;
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -9580,13 +10026,13 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_skip_blocks[0] = coro_susp_point;
+  self->private_impl.p_skip_blocks[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -9596,34 +10042,34 @@
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_ae(wuffs_gif__decoder* self,
-                              wuffs_base__io_reader a_src) {
+                              wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint8_t v_c = 0;
   uint8_t v_block_size = 0;
-  bool v_not_animexts = false;
-  bool v_not_netscape = false;
+  bool v_is_animexts = false;
+  bool v_is_netscape = false;
+  bool v_is_iccp = false;
+  bool v_is_xmp = false;
 
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_ae[0];
   if (coro_susp_point) {
     v_block_size = self->private_data.s_decode_ae[0].v_block_size;
-    v_not_animexts = self->private_data.s_decode_ae[0].v_not_animexts;
-    v_not_netscape = self->private_data.s_decode_ae[0].v_not_netscape;
+    v_is_animexts = self->private_data.s_decode_ae[0].v_is_animexts;
+    v_is_netscape = self->private_data.s_decode_ae[0].v_is_netscape;
+    v_is_iccp = self->private_data.s_decode_ae[0].v_is_iccp;
+    v_is_xmp = self->private_data.s_decode_ae[0].v_is_xmp;
   }
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
@@ -9631,7 +10077,7 @@
     while (true) {
       {
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-        if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+        if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
           status = wuffs_base__suspension__short_read;
           goto suspend;
         }
@@ -9646,33 +10092,38 @@
         self->private_data.s_decode_ae[0].scratch = ((uint32_t)(v_block_size));
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
         if (self->private_data.s_decode_ae[0].scratch >
-            ((uint64_t)(io1_a_src - iop_a_src))) {
+            ((uint64_t)(io2_a_src - iop_a_src))) {
           self->private_data.s_decode_ae[0].scratch -=
-              ((uint64_t)(io1_a_src - iop_a_src));
-          iop_a_src = io1_a_src;
+              ((uint64_t)(io2_a_src - iop_a_src));
+          iop_a_src = io2_a_src;
           status = wuffs_base__suspension__short_read;
           goto suspend;
         }
         iop_a_src += self->private_data.s_decode_ae[0].scratch;
         goto label_0_break;
       }
-      v_not_animexts = false;
-      v_not_netscape = false;
+      v_is_animexts = true;
+      v_is_netscape = true;
+      v_is_iccp = true;
+      v_is_xmp = true;
       v_block_size = 0;
       while (v_block_size < 11) {
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
           uint8_t t_1 = *iop_a_src++;
           v_c = t_1;
         }
-        v_not_animexts =
-            (v_not_animexts || (v_c != wuffs_gif__animexts1dot0[v_block_size]));
-        v_not_netscape =
-            (v_not_netscape || (v_c != wuffs_gif__netscape2dot0[v_block_size]));
+        v_is_animexts =
+            (v_is_animexts && (v_c == wuffs_gif__animexts1dot0[v_block_size]));
+        v_is_netscape =
+            (v_is_netscape && (v_c == wuffs_gif__netscape2dot0[v_block_size]));
+        v_is_iccp =
+            (v_is_iccp && (v_c == wuffs_gif__iccrgbg1012[v_block_size]));
+        v_is_xmp = (v_is_xmp && (v_c == wuffs_gif__xmpdataxmp[v_block_size]));
 #if defined(__GNUC__)
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wconversion"
@@ -9682,101 +10133,135 @@
 #pragma GCC diagnostic pop
 #endif
       }
-      if (v_not_animexts && v_not_netscape) {
-        goto label_0_break;
-      }
-      {
-        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
-        if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
-          status = wuffs_base__suspension__short_read;
-          goto suspend;
-        }
-        uint8_t t_2 = *iop_a_src++;
-        v_block_size = t_2;
-      }
-      if (v_block_size != 3) {
-        self->private_data.s_decode_ae[0].scratch = ((uint32_t)(v_block_size));
-        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
-        if (self->private_data.s_decode_ae[0].scratch >
-            ((uint64_t)(io1_a_src - iop_a_src))) {
-          self->private_data.s_decode_ae[0].scratch -=
-              ((uint64_t)(io1_a_src - iop_a_src));
-          iop_a_src = io1_a_src;
-          status = wuffs_base__suspension__short_read;
-          goto suspend;
-        }
-        iop_a_src += self->private_data.s_decode_ae[0].scratch;
-        goto label_0_break;
-      }
-      {
-        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
-        if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
-          status = wuffs_base__suspension__short_read;
-          goto suspend;
-        }
-        uint8_t t_3 = *iop_a_src++;
-        v_c = t_3;
-      }
-      if (v_c != 1) {
-        self->private_data.s_decode_ae[0].scratch = 2;
-        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
-        if (self->private_data.s_decode_ae[0].scratch >
-            ((uint64_t)(io1_a_src - iop_a_src))) {
-          self->private_data.s_decode_ae[0].scratch -=
-              ((uint64_t)(io1_a_src - iop_a_src));
-          iop_a_src = io1_a_src;
-          status = wuffs_base__suspension__short_read;
-          goto suspend;
-        }
-        iop_a_src += self->private_data.s_decode_ae[0].scratch;
-        goto label_0_break;
-      }
-      {
-        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
-        uint32_t t_4;
-        if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
-          t_4 = ((uint32_t)(wuffs_base__load_u16le(iop_a_src)));
-          iop_a_src += 2;
-        } else {
-          self->private_data.s_decode_ae[0].scratch = 0;
-          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9);
-          while (true) {
-            if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
-              status = wuffs_base__suspension__short_read;
-              goto suspend;
-            }
-            uint64_t* scratch = &self->private_data.s_decode_ae[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 = ((uint32_t)(*scratch));
-              break;
-            }
-            num_bits_4 += 8;
-            *scratch |= ((uint64_t)(num_bits_4)) << 56;
+      if (v_is_animexts || v_is_netscape) {
+        {
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+            status = wuffs_base__suspension__short_read;
+            goto suspend;
           }
+          uint8_t t_2 = *iop_a_src++;
+          v_block_size = t_2;
         }
-        self->private_impl.f_num_loops = t_4;
-      }
-      self->private_impl.f_seen_num_loops = true;
-      if ((0 < self->private_impl.f_num_loops) &&
-          (self->private_impl.f_num_loops <= 65535)) {
-        self->private_impl.f_num_loops += 1;
+        if (v_block_size != 3) {
+          self->private_data.s_decode_ae[0].scratch =
+              ((uint32_t)(v_block_size));
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
+          if (self->private_data.s_decode_ae[0].scratch >
+              ((uint64_t)(io2_a_src - iop_a_src))) {
+            self->private_data.s_decode_ae[0].scratch -=
+                ((uint64_t)(io2_a_src - iop_a_src));
+            iop_a_src = io2_a_src;
+            status = wuffs_base__suspension__short_read;
+            goto suspend;
+          }
+          iop_a_src += self->private_data.s_decode_ae[0].scratch;
+          goto label_0_break;
+        }
+        {
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+            status = wuffs_base__suspension__short_read;
+            goto suspend;
+          }
+          uint8_t t_3 = *iop_a_src++;
+          v_c = t_3;
+        }
+        if (v_c != 1) {
+          self->private_data.s_decode_ae[0].scratch = 2;
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
+          if (self->private_data.s_decode_ae[0].scratch >
+              ((uint64_t)(io2_a_src - iop_a_src))) {
+            self->private_data.s_decode_ae[0].scratch -=
+                ((uint64_t)(io2_a_src - iop_a_src));
+            iop_a_src = io2_a_src;
+            status = wuffs_base__suspension__short_read;
+            goto suspend;
+          }
+          iop_a_src += self->private_data.s_decode_ae[0].scratch;
+          goto label_0_break;
+        }
+        {
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
+          uint32_t t_4;
+          if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
+            t_4 = ((uint32_t)(wuffs_base__load_u16le(iop_a_src)));
+            iop_a_src += 2;
+          } else {
+            self->private_data.s_decode_ae[0].scratch = 0;
+            WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9);
+            while (true) {
+              if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+                status = wuffs_base__suspension__short_read;
+                goto suspend;
+              }
+              uint64_t* scratch = &self->private_data.s_decode_ae[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 = ((uint32_t)(*scratch));
+                break;
+              }
+              num_bits_4 += 8;
+              *scratch |= ((uint64_t)(num_bits_4)) << 56;
+            }
+          }
+          self->private_impl.f_num_loops = t_4;
+        }
+        self->private_impl.f_seen_num_loops = true;
+        if ((0 < self->private_impl.f_num_loops) &&
+            (self->private_impl.f_num_loops <= 65535)) {
+          self->private_impl.f_num_loops += 1;
+        }
+      } else if (self->private_impl.f_ignore_metadata) {
+      } else if (v_is_iccp && self->private_impl.f_report_metadata_iccp) {
+        while (((uint64_t)(io2_a_src - iop_a_src)) <= 0) {
+          status = wuffs_base__suspension__short_read;
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(10);
+        }
+        self->private_impl.f_metadata_chunk_length_value =
+            ((uint64_t)(wuffs_base__load_u8be(iop_a_src)));
+        (iop_a_src += 1, wuffs_base__make_empty_struct());
+        self->private_impl.f_metadata_fourcc_value = 1229144912;
+        self->private_impl.f_metadata_io_position = wuffs_base__u64__sat_add(
+            (a_src ? wuffs_base__u64__sat_add(
+                         a_src->meta.pos,
+                         ((uint64_t)(iop_a_src - a_src->data.ptr)))
+                   : 0),
+            self->private_impl.f_metadata_chunk_length_value);
+        self->private_impl.f_call_sequence = 1;
+        status = wuffs_base__warning__metadata_reported;
+        goto ok;
+      } else if (v_is_xmp && self->private_impl.f_report_metadata_xmp) {
+        while (((uint64_t)(io2_a_src - iop_a_src)) <= 0) {
+          status = wuffs_base__suspension__short_read;
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(11);
+        }
+        self->private_impl.f_metadata_chunk_length_value =
+            (((uint64_t)(wuffs_base__load_u8be(iop_a_src))) + 1);
+        self->private_impl.f_metadata_fourcc_value = 1481461792;
+        self->private_impl.f_metadata_io_position = wuffs_base__u64__sat_add(
+            (a_src ? wuffs_base__u64__sat_add(
+                         a_src->meta.pos,
+                         ((uint64_t)(iop_a_src - a_src->data.ptr)))
+                   : 0),
+            self->private_impl.f_metadata_chunk_length_value);
+        self->private_impl.f_call_sequence = 1;
+        status = wuffs_base__warning__metadata_reported;
+        goto ok;
       }
       goto label_0_break;
     }
   label_0_break:;
-    if (a_src.private_impl.buf) {
-      a_src.private_impl.buf->meta.ri =
-          ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+    if (a_src) {
+      a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
     }
-    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(10);
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(12);
     status = wuffs_gif__decoder__skip_blocks(self, a_src);
-    if (a_src.private_impl.buf) {
-      iop_a_src =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
+    if (a_src) {
+      iop_a_src = a_src->data.ptr + a_src->meta.ri;
     }
     if (status) {
       goto suspend;
@@ -9790,16 +10275,18 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_ae[0] = coro_susp_point;
+  self->private_impl.p_decode_ae[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
   self->private_data.s_decode_ae[0].v_block_size = v_block_size;
-  self->private_data.s_decode_ae[0].v_not_animexts = v_not_animexts;
-  self->private_data.s_decode_ae[0].v_not_netscape = v_not_netscape;
+  self->private_data.s_decode_ae[0].v_is_animexts = v_is_animexts;
+  self->private_data.s_decode_ae[0].v_is_netscape = v_is_netscape;
+  self->private_data.s_decode_ae[0].v_is_iccp = v_is_iccp;
+  self->private_data.s_decode_ae[0].v_is_xmp = v_is_xmp;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -9809,7 +10296,7 @@
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_gc(wuffs_gif__decoder* self,
-                              wuffs_base__io_reader a_src) {
+                              wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint8_t v_c = 0;
@@ -9819,16 +10306,12 @@
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_gc[0];
@@ -9837,13 +10320,9 @@
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
-    if (self->private_impl.f_seen_graphic_control) {
-      status = wuffs_gif__error__bad_graphic_control;
-      goto exit;
-    }
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -9856,7 +10335,7 @@
     }
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -9875,14 +10354,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
       uint16_t t_2;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
         t_2 = wuffs_base__load_u16le(iop_a_src);
         iop_a_src += 2;
       } else {
         self->private_data.s_decode_gc[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -9905,7 +10384,7 @@
         (((uint64_t)(v_gc_duration_centiseconds)) * 7056000);
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -9914,7 +10393,7 @@
     }
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -9925,7 +10404,6 @@
       status = wuffs_gif__error__bad_graphic_control;
       goto exit;
     }
-    self->private_impl.f_seen_graphic_control = true;
 
     goto ok;
   ok:
@@ -9935,13 +10413,13 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_gc[0] = coro_susp_point;
+  self->private_impl.p_decode_gc[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -9951,22 +10429,18 @@
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_id_part0(wuffs_gif__decoder* self,
-                                    wuffs_base__io_reader a_src) {
+                                    wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_id_part0[0];
@@ -9978,14 +10452,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
       uint32_t t_0;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
         t_0 = ((uint32_t)(wuffs_base__load_u16le(iop_a_src)));
         iop_a_src += 2;
       } else {
         self->private_data.s_decode_id_part0[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -10007,14 +10481,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
       uint32_t t_1;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
         t_1 = ((uint32_t)(wuffs_base__load_u16le(iop_a_src)));
         iop_a_src += 2;
       } else {
         self->private_data.s_decode_id_part0[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -10036,14 +10510,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
       uint32_t t_2;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
         t_2 = ((uint32_t)(wuffs_base__load_u16le(iop_a_src)));
         iop_a_src += 2;
       } else {
         self->private_data.s_decode_id_part0[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -10066,14 +10540,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
       uint32_t t_3;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
         t_3 = ((uint32_t)(wuffs_base__load_u16le(iop_a_src)));
         iop_a_src += 2;
       } else {
         self->private_data.s_decode_id_part0[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -10095,7 +10569,8 @@
     self->private_impl.f_frame_rect_y1 += self->private_impl.f_frame_rect_y0;
     self->private_impl.f_dst_x = self->private_impl.f_frame_rect_x0;
     self->private_impl.f_dst_y = self->private_impl.f_frame_rect_y0;
-    if (self->private_impl.f_call_sequence == 0) {
+    if ((self->private_impl.f_call_sequence == 0) &&
+        !self->private_impl.f_quirk_enabled_image_bounds_are_strict) {
       self->private_impl.f_width = wuffs_base__u32__max(
           self->private_impl.f_width, self->private_impl.f_frame_rect_x1);
       self->private_impl.f_height = wuffs_base__u32__max(
@@ -10110,13 +10585,13 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_id_part0[0] = coro_susp_point;
+  self->private_impl.p_decode_id_part0[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -10127,33 +10602,32 @@
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_id_part1(wuffs_gif__decoder* self,
                                     wuffs_base__pixel_buffer* a_dst,
-                                    wuffs_base__io_reader a_src) {
+                                    wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint8_t v_flags = 0;
+  uint8_t v_which_palette = 0;
   uint32_t v_num_palette_entries = 0;
   uint32_t v_i = 0;
   uint32_t v_argb = 0;
   wuffs_base__slice_u8 v_dst_palette = {0};
+  wuffs_base__status v_status = NULL;
   uint8_t v_lw = 0;
 
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_id_part1[0];
   if (coro_susp_point) {
+    v_which_palette = self->private_data.s_decode_id_part1[0].v_which_palette;
     v_num_palette_entries =
         self->private_data.s_decode_id_part1[0].v_num_palette_entries;
     v_i = self->private_data.s_decode_id_part1[0].v_i;
@@ -10163,7 +10637,7 @@
 
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -10175,7 +10649,7 @@
     } else {
       self->private_impl.f_interlace = 0;
     }
-    self->private_impl.f_which_palette = 1;
+    v_which_palette = 1;
     if ((v_flags & 128) != 0) {
       v_num_palette_entries = (((uint32_t)(1)) << (1 + (v_flags & 7)));
       v_i = 0;
@@ -10183,14 +10657,14 @@
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
           uint32_t t_1;
-          if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 3)) {
+          if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 3)) {
             t_1 = ((uint32_t)(wuffs_base__load_u24be(iop_a_src)));
             iop_a_src += 3;
           } else {
             self->private_data.s_decode_id_part1[0].scratch = 0;
             WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
             while (true) {
-              if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+              if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
                 status = wuffs_base__suspension__short_read;
                 goto suspend;
               }
@@ -10228,12 +10702,16 @@
         self->private_data.f_palettes[1][((4 * v_i) + 3)] = 255;
         v_i += 1;
       }
+    } else if (self->private_impl.f_quirk_enabled_reject_empty_palette &&
+               !self->private_impl.f_has_global_palette) {
+      status = wuffs_gif__error__bad_palette;
+      goto exit;
     } else if (self->private_impl.f_gc_has_transparent_index) {
       wuffs_base__slice_u8__copy_from_slice(
           wuffs_base__make_slice_u8(self->private_data.f_palettes[1], 1024),
           wuffs_base__make_slice_u8(self->private_data.f_palettes[0], 1024));
     } else {
-      self->private_impl.f_which_palette = 0;
+      v_which_palette = 0;
     }
     if (self->private_impl.f_gc_has_transparent_index) {
       self->private_data.f_palettes[1][(
@@ -10254,13 +10732,22 @@
       v_dst_palette =
           wuffs_base__make_slice_u8(self->private_data.f_dst_palette, 1024);
     }
-    wuffs_base__pixel_swizzler__prepare(
+    v_status = wuffs_base__pixel_swizzler__prepare(
         &self->private_impl.f_swizzler,
         wuffs_base__pixel_buffer__pixel_format(a_dst), v_dst_palette,
         1191444488,
         wuffs_base__make_slice_u8(
-            self->private_data.f_palettes[self->private_impl.f_which_palette],
-            1024));
+            self->private_data.f_palettes[v_which_palette], 1024));
+    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__error__cannot_return_a_suspension;
+        goto exit;
+      }
+      goto ok;
+    }
     if (self->private_impl.f_previous_lzw_decode_ended_abruptly) {
       wuffs_base__ignore_status(wuffs_lzw__decoder__initialize(
           &self->private_data.f_lzw, sizeof(wuffs_lzw__decoder), WUFFS_VERSION,
@@ -10268,14 +10755,14 @@
     }
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
       uint8_t t_2 = *iop_a_src++;
       v_lw = t_2;
     }
-    if ((v_lw < 2) || (8 < v_lw)) {
+    if (v_lw > 8) {
       status = wuffs_gif__error__bad_literal_width;
       goto exit;
     }
@@ -10291,16 +10778,17 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_id_part1[0] = coro_susp_point;
+  self->private_impl.p_decode_id_part1[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
+  self->private_data.s_decode_id_part1[0].v_which_palette = v_which_palette;
   self->private_data.s_decode_id_part1[0].v_num_palette_entries =
       v_num_palette_entries;
   self->private_data.s_decode_id_part1[0].v_i = v_i;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -10311,7 +10799,7 @@
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_id_part2(wuffs_gif__decoder* self,
                                     wuffs_base__pixel_buffer* a_dst,
-                                    wuffs_base__io_reader a_src,
+                                    wuffs_base__io_buffer* a_src,
                                     wuffs_base__slice_u8 a_workbuf) {
   wuffs_base__status status = NULL;
 
@@ -10319,11 +10807,13 @@
   bool v_need_block_size = false;
   uint64_t v_n_compressed = 0;
   wuffs_base__slice_u8 v_compressed = {0};
-  wuffs_base__io_reader v_r = wuffs_base__null_io_reader();
-  wuffs_base__io_buffer u_r WUFFS_BASE__POTENTIALLY_UNUSED =
-      wuffs_base__null_io_buffer();
+  wuffs_base__io_buffer u_r = wuffs_base__null_io_buffer();
+  wuffs_base__io_buffer* v_r = &u_r;
   uint8_t* iop_v_r WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  uint8_t* io0_v_r WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_v_r WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  uint8_t* io2_v_r WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  uint64_t v_mark = 0;
   wuffs_base__status v_lzw_status = NULL;
   wuffs_base__status v_copy_status = NULL;
   wuffs_base__slice_u8 v_uncompressed = {0};
@@ -10331,16 +10821,12 @@
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_id_part2[0];
@@ -10360,7 +10846,7 @@
         v_need_block_size = false;
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -10371,7 +10857,7 @@
       if (v_block_size == 0) {
         goto label_0_break;
       }
-      while (((uint64_t)(io1_a_src - iop_a_src)) == 0) {
+      while (((uint64_t)(io2_a_src - iop_a_src)) == 0) {
         status = wuffs_base__suspension__short_read;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(2);
       }
@@ -10382,12 +10868,12 @@
       }
       while (self->private_impl.f_compressed_wi <= 3841) {
         v_n_compressed = wuffs_base__u64__min(
-            v_block_size, ((uint64_t)(io1_a_src - iop_a_src)));
+            v_block_size, ((uint64_t)(io2_a_src - iop_a_src)));
         if (v_n_compressed <= 0) {
           goto label_1_break;
         }
         v_compressed =
-            wuffs_base__io_reader__take(&iop_a_src, io1_a_src, v_n_compressed);
+            wuffs_base__io_reader__take(&iop_a_src, io2_a_src, v_n_compressed);
         wuffs_base__slice_u8__copy_from_slice(
             wuffs_base__slice_u8__subslice_i(
                 wuffs_base__make_slice_u8(self->private_data.f_compressed,
@@ -10400,7 +10886,7 @@
         if (v_block_size > 0) {
           goto label_1_break;
         }
-        if (((uint64_t)(io1_a_src - iop_a_src)) <= 0) {
+        if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) {
           v_need_block_size = true;
           goto label_1_break;
         }
@@ -10421,16 +10907,19 @@
           goto exit;
         }
         {
-          wuffs_base__io_reader o_0_v_r = v_r;
+          wuffs_base__io_buffer* o_0_v_r = v_r;
           uint8_t* o_0_iop_v_r = iop_v_r;
+          uint8_t* o_0_io0_v_r = io0_v_r;
           uint8_t* o_0_io1_v_r = io1_v_r;
-          wuffs_base__io_reader__set(
-              &v_r, &u_r, &iop_v_r, &io1_v_r,
+          uint8_t* o_0_io2_v_r = io2_v_r;
+          v_r = wuffs_base__io_reader__set(
+              &u_r, &iop_v_r, &io0_v_r, &io1_v_r, &io2_v_r,
               wuffs_base__slice_u8__subslice_ij(
                   wuffs_base__make_slice_u8(self->private_data.f_compressed,
                                             4096),
                   self->private_impl.f_compressed_ri,
                   self->private_impl.f_compressed_wi));
+          v_mark = ((uint64_t)(iop_v_r - io0_v_r));
           {
             u_r.meta.ri = ((size_t)(iop_v_r - u_r.data.ptr));
             wuffs_base__status t_1 = wuffs_lzw__decoder__decode_io_writer(
@@ -10442,10 +10931,13 @@
           }
           wuffs_base__u64__sat_add_indirect(
               &self->private_impl.f_compressed_ri,
-              ((uint64_t)(iop_v_r - v_r.private_impl.mark)));
+              wuffs_base__io__count_since(v_mark,
+                                          ((uint64_t)(iop_v_r - io0_v_r))));
           v_r = o_0_v_r;
           iop_v_r = o_0_iop_v_r;
+          io0_v_r = o_0_io0_v_r;
           io1_v_r = o_0_io1_v_r;
+          io2_v_r = o_0_io2_v_r;
         }
         v_uncompressed = wuffs_lzw__decoder__flush(&self->private_data.f_lzw);
         if (((uint64_t)(v_uncompressed.len)) > 0) {
@@ -10463,23 +10955,21 @@
                 ((uint32_t)(v_block_size));
             WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
             if (self->private_data.s_decode_id_part2[0].scratch >
-                ((uint64_t)(io1_a_src - iop_a_src))) {
+                ((uint64_t)(io2_a_src - iop_a_src))) {
               self->private_data.s_decode_id_part2[0].scratch -=
-                  ((uint64_t)(io1_a_src - iop_a_src));
-              iop_a_src = io1_a_src;
+                  ((uint64_t)(io2_a_src - iop_a_src));
+              iop_a_src = io2_a_src;
               status = wuffs_base__suspension__short_read;
               goto suspend;
             }
             iop_a_src += self->private_data.s_decode_id_part2[0].scratch;
-            if (a_src.private_impl.buf) {
-              a_src.private_impl.buf->meta.ri =
-                  ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+            if (a_src) {
+              a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
             }
             WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
             status = wuffs_gif__decoder__skip_blocks(self, a_src);
-            if (a_src.private_impl.buf) {
-              iop_a_src = a_src.private_impl.buf->data.ptr +
-                          a_src.private_impl.buf->meta.ri;
+            if (a_src) {
+              iop_a_src = a_src->data.ptr + a_src->meta.ri;
             }
             if (status) {
               goto suspend;
@@ -10521,16 +11011,16 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_id_part2[0] = coro_susp_point;
+  self->private_impl.p_decode_id_part2[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
   self->private_data.s_decode_id_part2[0].v_block_size = v_block_size;
   self->private_data.s_decode_id_part2[0].v_need_block_size = v_need_block_size;
   self->private_data.s_decode_id_part2[0].v_lzw_status = v_lzw_status;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -10547,23 +11037,29 @@
   uint64_t v_n = 0;
   uint64_t v_src_ri = 0;
   uint32_t v_bytes_per_pixel = 0;
-  uint32_t v_pixfmt = 0;
+  uint32_t v_pixfmt_channels = 0;
   wuffs_base__table_u8 v_tab = {0};
   uint64_t v_i = 0;
   uint64_t v_j = 0;
 
-  v_bytes_per_pixel = 1;
-  v_pixfmt = wuffs_base__pixel_buffer__pixel_format(a_pb);
-  if ((v_pixfmt == 1157662856) || (v_pixfmt == 1426098312) ||
-      (v_pixfmt == 1174440072) || (v_pixfmt == 1442875528) ||
-      (v_pixfmt == 1191217288) || (v_pixfmt == 1459652744)) {
+  v_pixfmt_channels = (wuffs_base__pixel_buffer__pixel_format(a_pb) & 65535);
+  if (v_pixfmt_channels == 34952) {
     v_bytes_per_pixel = 4;
+  } else if (v_pixfmt_channels == 2184) {
+    v_bytes_per_pixel = 3;
+  } else if (v_pixfmt_channels == 8) {
+    v_bytes_per_pixel = 1;
+  } else {
+    return wuffs_base__error__unsupported_option;
   }
   v_tab = wuffs_base__pixel_buffer__plane(a_pb, 0);
 label_0_continue:;
   while (v_src_ri < ((uint64_t)(a_src.len))) {
     v_src = wuffs_base__slice_u8__subslice_i(a_src, v_src_ri);
     if (self->private_impl.f_dst_y >= self->private_impl.f_frame_rect_y1) {
+      if (self->private_impl.f_quirk_enabled_ignore_too_much_pixel_data) {
+        return NULL;
+      }
       return wuffs_base__error__too_much_data;
     }
     v_dst = wuffs_base__table_u8__row(v_tab, self->private_impl.f_dst_y);
@@ -10577,7 +11073,7 @@
       } else {
         v_dst = wuffs_base__slice_u8__subslice_i(v_dst, v_i);
       }
-      v_n = wuffs_base__pixel_swizzler__swizzle_packed(
+      v_n = wuffs_base__pixel_swizzler__swizzle_interleaved(
           &self->private_impl.f_swizzler, v_dst,
           wuffs_base__make_slice_u8(self->private_data.f_dst_palette, 1024),
           v_src);
@@ -10778,8 +11274,8 @@
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_gzip__decoder__decode_io_writer(wuffs_gzip__decoder* self,
-                                      wuffs_base__io_writer a_dst,
-                                      wuffs_base__io_reader a_src,
+                                      wuffs_base__io_buffer* a_dst,
+                                      wuffs_base__io_buffer* a_src,
                                       wuffs_base__slice_u8 a_workbuf) {
   if (!self) {
     return wuffs_base__error__bad_receiver;
@@ -10789,6 +11285,10 @@
                ? 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__error__bad_argument;
+  }
   if ((self->private_impl.active_coroutine != 0) &&
       (self->private_impl.active_coroutine != 1)) {
     self->private_impl.magic = WUFFS_BASE__DISABLED;
@@ -10800,6 +11300,7 @@
   uint8_t v_c = 0;
   uint8_t v_flags = 0;
   uint16_t v_xlen = 0;
+  uint64_t v_mark = 0;
   uint32_t v_checksum_got = 0;
   uint32_t v_decoded_length_got = 0;
   wuffs_base__status v_status = NULL;
@@ -10809,33 +11310,25 @@
   uint8_t* iop_a_dst = NULL;
   uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_dst.private_impl.buf) {
-    iop_a_dst =
-        a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->meta.wi;
-    if (!a_dst.private_impl.mark) {
-      a_dst.private_impl.mark = iop_a_dst;
-      a_dst.private_impl.limit =
-          a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->data.len;
+  uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_dst) {
+    io0_a_dst = a_dst->data.ptr;
+    io1_a_dst = io0_a_dst + a_dst->meta.wi;
+    iop_a_dst = io1_a_dst;
+    io2_a_dst = io0_a_dst + a_dst->data.len;
+    if (a_dst->meta.closed) {
+      io2_a_dst = iop_a_dst;
     }
-    if (a_dst.private_impl.buf->meta.closed) {
-      a_dst.private_impl.limit = iop_a_dst;
-    }
-    io0_a_dst = a_dst.private_impl.mark;
-    io1_a_dst = a_dst.private_impl.limit;
   }
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_io_writer[0];
@@ -10851,7 +11344,7 @@
 
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -10864,7 +11357,7 @@
     }
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -10877,7 +11370,7 @@
     }
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -10890,7 +11383,7 @@
     }
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -10900,10 +11393,10 @@
     self->private_data.s_decode_io_writer[0].scratch = 6;
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
     if (self->private_data.s_decode_io_writer[0].scratch >
-        ((uint64_t)(io1_a_src - iop_a_src))) {
+        ((uint64_t)(io2_a_src - iop_a_src))) {
       self->private_data.s_decode_io_writer[0].scratch -=
-          ((uint64_t)(io1_a_src - iop_a_src));
-      iop_a_src = io1_a_src;
+          ((uint64_t)(io2_a_src - iop_a_src));
+      iop_a_src = io2_a_src;
       status = wuffs_base__suspension__short_read;
       goto suspend;
     }
@@ -10912,14 +11405,14 @@
       {
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
         uint16_t t_4;
-        if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
+        if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
           t_4 = wuffs_base__load_u16le(iop_a_src);
           iop_a_src += 2;
         } else {
           self->private_data.s_decode_io_writer[0].scratch = 0;
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
           while (true) {
-            if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+            if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
               status = wuffs_base__suspension__short_read;
               goto suspend;
             }
@@ -10942,10 +11435,10 @@
       self->private_data.s_decode_io_writer[0].scratch = ((uint32_t)(v_xlen));
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
       if (self->private_data.s_decode_io_writer[0].scratch >
-          ((uint64_t)(io1_a_src - iop_a_src))) {
+          ((uint64_t)(io2_a_src - iop_a_src))) {
         self->private_data.s_decode_io_writer[0].scratch -=
-            ((uint64_t)(io1_a_src - iop_a_src));
-        iop_a_src = io1_a_src;
+            ((uint64_t)(io2_a_src - iop_a_src));
+        iop_a_src = io2_a_src;
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -10955,7 +11448,7 @@
       while (true) {
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9);
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -10972,7 +11465,7 @@
       while (true) {
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(10);
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -10989,10 +11482,10 @@
       self->private_data.s_decode_io_writer[0].scratch = 2;
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(11);
       if (self->private_data.s_decode_io_writer[0].scratch >
-          ((uint64_t)(io1_a_src - iop_a_src))) {
+          ((uint64_t)(io2_a_src - iop_a_src))) {
         self->private_data.s_decode_io_writer[0].scratch -=
-            ((uint64_t)(io1_a_src - iop_a_src));
-        iop_a_src = io1_a_src;
+            ((uint64_t)(io2_a_src - iop_a_src));
+        iop_a_src = io2_a_src;
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -11003,36 +11496,33 @@
       goto exit;
     }
     while (true) {
-      wuffs_base__io_writer__set_mark(&a_dst, iop_a_dst);
+      v_mark = ((uint64_t)(iop_a_dst - io0_a_dst));
       {
-        if (a_dst.private_impl.buf) {
-          a_dst.private_impl.buf->meta.wi =
-              ((size_t)(iop_a_dst - a_dst.private_impl.buf->data.ptr));
+        if (a_dst) {
+          a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
         }
-        if (a_src.private_impl.buf) {
-          a_src.private_impl.buf->meta.ri =
-              ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
         }
         wuffs_base__status t_7 = wuffs_deflate__decoder__decode_io_writer(
             &self->private_data.f_flate, a_dst, a_src, a_workbuf);
-        if (a_dst.private_impl.buf) {
-          iop_a_dst = a_dst.private_impl.buf->data.ptr +
-                      a_dst.private_impl.buf->meta.wi;
+        if (a_dst) {
+          iop_a_dst = a_dst->data.ptr + a_dst->meta.wi;
         }
-        if (a_src.private_impl.buf) {
-          iop_a_src = a_src.private_impl.buf->data.ptr +
-                      a_src.private_impl.buf->meta.ri;
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
         }
         v_status = t_7;
       }
       if (!self->private_impl.f_ignore_checksum) {
         v_checksum_got = wuffs_crc32__ieee_hasher__update(
             &self->private_data.f_checksum,
-            wuffs_base__make_slice_u8(
-                a_dst.private_impl.mark,
-                (size_t)(iop_a_dst - a_dst.private_impl.mark)));
-        v_decoded_length_got += ((uint32_t)(
-            (((uint64_t)(iop_a_dst - a_dst.private_impl.mark)) & 4294967295)));
+            wuffs_base__io__since(v_mark, ((uint64_t)(iop_a_dst - io0_a_dst)),
+                                  io0_a_dst));
+        v_decoded_length_got +=
+            ((uint32_t)((wuffs_base__io__count_since(
+                             v_mark, ((uint64_t)(iop_a_dst - io0_a_dst))) &
+                         4294967295)));
       }
       if (wuffs_base__status__is_ok(v_status)) {
         goto label_2_break;
@@ -11044,14 +11534,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(13);
       uint32_t t_8;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 4)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) {
         t_8 = wuffs_base__load_u32le(iop_a_src);
         iop_a_src += 4;
       } else {
         self->private_data.s_decode_io_writer[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(14);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -11073,14 +11563,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(15);
       uint32_t t_9;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 4)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) {
         t_9 = wuffs_base__load_u32le(iop_a_src);
         iop_a_src += 4;
       } else {
         self->private_data.s_decode_io_writer[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(16);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -11114,8 +11604,10 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_io_writer[0] = coro_susp_point;
-  self->private_impl.active_coroutine = 1;
+  self->private_impl.p_decode_io_writer[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_io_writer[0].v_flags = v_flags;
   self->private_data.s_decode_io_writer[0].v_checksum_got = v_checksum_got;
   self->private_data.s_decode_io_writer[0].v_decoded_length_got =
@@ -11124,13 +11616,11 @@
 
   goto exit;
 exit:
-  if (a_dst.private_impl.buf) {
-    a_dst.private_impl.buf->meta.wi =
-        ((size_t)(iop_a_dst - a_dst.private_impl.buf->data.ptr));
+  if (a_dst) {
+    a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
   }
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   if (wuffs_base__status__is_error(status)) {
@@ -11265,8 +11755,8 @@
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_zlib__decoder__decode_io_writer(wuffs_zlib__decoder* self,
-                                      wuffs_base__io_writer a_dst,
-                                      wuffs_base__io_reader a_src,
+                                      wuffs_base__io_buffer* a_dst,
+                                      wuffs_base__io_buffer* a_src,
                                       wuffs_base__slice_u8 a_workbuf) {
   if (!self) {
     return wuffs_base__error__bad_receiver;
@@ -11276,6 +11766,10 @@
                ? 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__error__bad_argument;
+  }
   if ((self->private_impl.active_coroutine != 0) &&
       (self->private_impl.active_coroutine != 1)) {
     self->private_impl.magic = WUFFS_BASE__DISABLED;
@@ -11288,37 +11782,30 @@
   uint32_t v_checksum_got = 0;
   wuffs_base__status v_status = NULL;
   uint32_t v_checksum_want = 0;
+  uint64_t v_mark = 0;
 
   uint8_t* iop_a_dst = NULL;
   uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_dst.private_impl.buf) {
-    iop_a_dst =
-        a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->meta.wi;
-    if (!a_dst.private_impl.mark) {
-      a_dst.private_impl.mark = iop_a_dst;
-      a_dst.private_impl.limit =
-          a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->data.len;
+  uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_dst) {
+    io0_a_dst = a_dst->data.ptr;
+    io1_a_dst = io0_a_dst + a_dst->meta.wi;
+    iop_a_dst = io1_a_dst;
+    io2_a_dst = io0_a_dst + a_dst->data.len;
+    if (a_dst->meta.closed) {
+      io2_a_dst = iop_a_dst;
     }
-    if (a_dst.private_impl.buf->meta.closed) {
-      a_dst.private_impl.limit = iop_a_dst;
-    }
-    io0_a_dst = a_dst.private_impl.mark;
-    io1_a_dst = a_dst.private_impl.limit;
   }
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_io_writer[0];
@@ -11331,14 +11818,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
       uint16_t t_0;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
         t_0 = wuffs_base__load_u16be(iop_a_src);
         iop_a_src += 2;
       } else {
         self->private_data.s_decode_io_writer[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -11374,34 +11861,29 @@
       goto exit;
     }
     while (true) {
-      wuffs_base__io_writer__set_mark(&a_dst, iop_a_dst);
+      v_mark = ((uint64_t)(iop_a_dst - io0_a_dst));
       {
-        if (a_dst.private_impl.buf) {
-          a_dst.private_impl.buf->meta.wi =
-              ((size_t)(iop_a_dst - a_dst.private_impl.buf->data.ptr));
+        if (a_dst) {
+          a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
         }
-        if (a_src.private_impl.buf) {
-          a_src.private_impl.buf->meta.ri =
-              ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
         }
         wuffs_base__status t_1 = wuffs_deflate__decoder__decode_io_writer(
             &self->private_data.f_flate, a_dst, a_src, a_workbuf);
-        if (a_dst.private_impl.buf) {
-          iop_a_dst = a_dst.private_impl.buf->data.ptr +
-                      a_dst.private_impl.buf->meta.wi;
+        if (a_dst) {
+          iop_a_dst = a_dst->data.ptr + a_dst->meta.wi;
         }
-        if (a_src.private_impl.buf) {
-          iop_a_src = a_src.private_impl.buf->data.ptr +
-                      a_src.private_impl.buf->meta.ri;
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
         }
         v_status = t_1;
       }
       if (!self->private_impl.f_ignore_checksum) {
         v_checksum_got = wuffs_adler32__hasher__update(
             &self->private_data.f_checksum,
-            wuffs_base__make_slice_u8(
-                a_dst.private_impl.mark,
-                (size_t)(iop_a_dst - a_dst.private_impl.mark)));
+            wuffs_base__io__since(v_mark, ((uint64_t)(iop_a_dst - io0_a_dst)),
+                                  io0_a_dst));
       }
       if (wuffs_base__status__is_ok(v_status)) {
         goto label_0_break;
@@ -11413,14 +11895,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
       uint32_t t_2;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 4)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) {
         t_2 = wuffs_base__load_u32be(iop_a_src);
         iop_a_src += 4;
       } else {
         self->private_data.s_decode_io_writer[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -11453,19 +11935,19 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_io_writer[0] = coro_susp_point;
-  self->private_impl.active_coroutine = 1;
+  self->private_impl.p_decode_io_writer[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_io_writer[0].v_checksum_got = v_checksum_got;
 
   goto exit;
 exit:
-  if (a_dst.private_impl.buf) {
-    a_dst.private_impl.buf->meta.wi =
-        ((size_t)(iop_a_dst - a_dst.private_impl.buf->data.ptr));
+  if (a_dst) {
+    a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
   }
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   if (wuffs_base__status__is_error(status)) {
diff --git a/release/c/wuffs-v0.2.c b/release/c/wuffs-v0.2.c
index 8ea6abd..2a0215b 100644
--- a/release/c/wuffs-v0.2.c
+++ b/release/c/wuffs-v0.2.c
@@ -60,15 +60,15 @@
 // each major.minor branch, the commit count should increase monotonically.
 //
 // WUFFS_VERSION was overridden by "wuffs gen -version" based on revision
-// 6b9f658c593bb57851e3fac7df2e5f3949a4fac8 committed on 2019-04-07.
+// 3037f1e389b54f9721161cc92c9b00372254cafc committed on 2019-07-07.
 #define WUFFS_VERSION ((uint64_t)0x0000000000020000)
 #define WUFFS_VERSION_MAJOR ((uint64_t)0x00000000)
 #define WUFFS_VERSION_MINOR ((uint64_t)0x0002)
 #define WUFFS_VERSION_PATCH ((uint64_t)0x0000)
-#define WUFFS_VERSION_PRE_RELEASE_LABEL "alpha.36"
-#define WUFFS_VERSION_BUILD_METADATA_COMMIT_COUNT 1675
-#define WUFFS_VERSION_BUILD_METADATA_COMMIT_DATE 20190407
-#define WUFFS_VERSION_STRING "0.2.0-alpha.36+1675.20190407"
+#define WUFFS_VERSION_PRE_RELEASE_LABEL "alpha.44"
+#define WUFFS_VERSION_BUILD_METADATA_COMMIT_COUNT 1776
+#define WUFFS_VERSION_BUILD_METADATA_COMMIT_DATE 20190707
+#define WUFFS_VERSION_STRING "0.2.0-alpha.44+1776.20190707"
 
 // Define WUFFS_CONFIG__STATIC_FUNCTIONS to make all of Wuffs' functions have
 // static storage. The motivation is discussed in the "ALLOW STATIC
@@ -192,8 +192,10 @@
 typedef const char* wuffs_base__status;
 
 extern const char* wuffs_base__warning__end_of_data;
+extern const char* wuffs_base__warning__metadata_reported;
 extern const char* wuffs_base__suspension__short_read;
 extern const char* wuffs_base__suspension__short_write;
+extern const char* wuffs_base__error__bad_i_o_position;
 extern const char* wuffs_base__error__bad_argument_length_too_short;
 extern const char* wuffs_base__error__bad_argument;
 extern const char* wuffs_base__error__bad_call_sequence;
@@ -208,6 +210,7 @@
 extern const char* wuffs_base__error__initialize_not_called;
 extern const char* wuffs_base__error__interleaved_coroutine_calls;
 extern const char* wuffs_base__error__not_enough_data;
+extern const char* wuffs_base__error__unsupported_option;
 extern const char* wuffs_base__error__too_much_data;
 
 static inline bool  //
@@ -237,6 +240,16 @@
 
 // --------
 
+// FourCC constants.
+
+// International Color Consortium Profile.
+#define WUFFS_BASE__FOURCC__ICCP 0x49434350
+
+// Extensible Metadata Platform.
+#define WUFFS_BASE__FOURCC__XMP 0x584D5020
+
+// --------
+
 // Flicks are a unit of time. One flick (frame-tick) is 1 / 705_600_000 of a
 // second. See https://github.com/OculusVR/Flicks
 typedef int64_t wuffs_base__flicks;
@@ -510,14 +523,14 @@
   uint32_t max_incl;
 
 #ifdef __cplusplus
-  inline bool is_empty();
-  inline bool equals(wuffs_base__range_ii_u32__struct s);
+  inline bool is_empty() const;
+  inline bool equals(wuffs_base__range_ii_u32__struct s) const;
   inline wuffs_base__range_ii_u32__struct intersect(
-      wuffs_base__range_ii_u32__struct s);
+      wuffs_base__range_ii_u32__struct s) const;
   inline wuffs_base__range_ii_u32__struct unite(
-      wuffs_base__range_ii_u32__struct s);
-  inline bool contains(uint32_t x);
-  inline bool contains_range(wuffs_base__range_ii_u32__struct s);
+      wuffs_base__range_ii_u32__struct s) const;
+  inline bool contains(uint32_t x) const;
+  inline bool contains_range(wuffs_base__range_ii_u32__struct s) const;
 #endif  // __cplusplus
 
 } wuffs_base__range_ii_u32;
@@ -531,12 +544,12 @@
 }
 
 static inline bool  //
-wuffs_base__range_ii_u32__is_empty(wuffs_base__range_ii_u32* r) {
+wuffs_base__range_ii_u32__is_empty(const wuffs_base__range_ii_u32* r) {
   return r->min_incl > r->max_incl;
 }
 
 static inline bool  //
-wuffs_base__range_ii_u32__equals(wuffs_base__range_ii_u32* r,
+wuffs_base__range_ii_u32__equals(const wuffs_base__range_ii_u32* r,
                                  wuffs_base__range_ii_u32 s) {
   return (r->min_incl == s.min_incl && r->max_incl == s.max_incl) ||
          (wuffs_base__range_ii_u32__is_empty(r) &&
@@ -544,7 +557,7 @@
 }
 
 static inline wuffs_base__range_ii_u32  //
-wuffs_base__range_ii_u32__intersect(wuffs_base__range_ii_u32* r,
+wuffs_base__range_ii_u32__intersect(const wuffs_base__range_ii_u32* r,
                                     wuffs_base__range_ii_u32 s) {
   wuffs_base__range_ii_u32 t;
   t.min_incl = wuffs_base__u32__max(r->min_incl, s.min_incl);
@@ -553,7 +566,7 @@
 }
 
 static inline wuffs_base__range_ii_u32  //
-wuffs_base__range_ii_u32__unite(wuffs_base__range_ii_u32* r,
+wuffs_base__range_ii_u32__unite(const wuffs_base__range_ii_u32* r,
                                 wuffs_base__range_ii_u32 s) {
   if (wuffs_base__range_ii_u32__is_empty(r)) {
     return s;
@@ -568,12 +581,13 @@
 }
 
 static inline bool  //
-wuffs_base__range_ii_u32__contains(wuffs_base__range_ii_u32* r, uint32_t x) {
+wuffs_base__range_ii_u32__contains(const wuffs_base__range_ii_u32* r,
+                                   uint32_t x) {
   return (r->min_incl <= x) && (x <= r->max_incl);
 }
 
 static inline bool  //
-wuffs_base__range_ii_u32__contains_range(wuffs_base__range_ii_u32* r,
+wuffs_base__range_ii_u32__contains_range(const wuffs_base__range_ii_u32* r,
                                          wuffs_base__range_ii_u32 s) {
   return wuffs_base__range_ii_u32__equals(
       &s, wuffs_base__range_ii_u32__intersect(r, s));
@@ -582,32 +596,32 @@
 #ifdef __cplusplus
 
 inline bool  //
-wuffs_base__range_ii_u32::is_empty() {
+wuffs_base__range_ii_u32::is_empty() const {
   return wuffs_base__range_ii_u32__is_empty(this);
 }
 
 inline bool  //
-wuffs_base__range_ii_u32::equals(wuffs_base__range_ii_u32 s) {
+wuffs_base__range_ii_u32::equals(wuffs_base__range_ii_u32 s) const {
   return wuffs_base__range_ii_u32__equals(this, s);
 }
 
 inline wuffs_base__range_ii_u32  //
-wuffs_base__range_ii_u32::intersect(wuffs_base__range_ii_u32 s) {
+wuffs_base__range_ii_u32::intersect(wuffs_base__range_ii_u32 s) const {
   return wuffs_base__range_ii_u32__intersect(this, s);
 }
 
 inline wuffs_base__range_ii_u32  //
-wuffs_base__range_ii_u32::unite(wuffs_base__range_ii_u32 s) {
+wuffs_base__range_ii_u32::unite(wuffs_base__range_ii_u32 s) const {
   return wuffs_base__range_ii_u32__unite(this, s);
 }
 
 inline bool  //
-wuffs_base__range_ii_u32::contains(uint32_t x) {
+wuffs_base__range_ii_u32::contains(uint32_t x) const {
   return wuffs_base__range_ii_u32__contains(this, x);
 }
 
 inline bool  //
-wuffs_base__range_ii_u32::contains_range(wuffs_base__range_ii_u32 s) {
+wuffs_base__range_ii_u32::contains_range(wuffs_base__range_ii_u32 s) const {
   return wuffs_base__range_ii_u32__contains_range(this, s);
 }
 
@@ -620,15 +634,15 @@
   uint32_t max_excl;
 
 #ifdef __cplusplus
-  inline bool is_empty();
-  inline bool equals(wuffs_base__range_ie_u32__struct s);
+  inline bool is_empty() const;
+  inline bool equals(wuffs_base__range_ie_u32__struct s) const;
   inline wuffs_base__range_ie_u32__struct intersect(
-      wuffs_base__range_ie_u32__struct s);
+      wuffs_base__range_ie_u32__struct s) const;
   inline wuffs_base__range_ie_u32__struct unite(
-      wuffs_base__range_ie_u32__struct s);
-  inline bool contains(uint32_t x);
-  inline bool contains_range(wuffs_base__range_ie_u32__struct s);
-  inline uint32_t length();
+      wuffs_base__range_ie_u32__struct s) const;
+  inline bool contains(uint32_t x) const;
+  inline bool contains_range(wuffs_base__range_ie_u32__struct s) const;
+  inline uint32_t length() const;
 #endif  // __cplusplus
 
 } wuffs_base__range_ie_u32;
@@ -642,12 +656,12 @@
 }
 
 static inline bool  //
-wuffs_base__range_ie_u32__is_empty(wuffs_base__range_ie_u32* r) {
+wuffs_base__range_ie_u32__is_empty(const wuffs_base__range_ie_u32* r) {
   return r->min_incl >= r->max_excl;
 }
 
 static inline bool  //
-wuffs_base__range_ie_u32__equals(wuffs_base__range_ie_u32* r,
+wuffs_base__range_ie_u32__equals(const wuffs_base__range_ie_u32* r,
                                  wuffs_base__range_ie_u32 s) {
   return (r->min_incl == s.min_incl && r->max_excl == s.max_excl) ||
          (wuffs_base__range_ie_u32__is_empty(r) &&
@@ -655,7 +669,7 @@
 }
 
 static inline wuffs_base__range_ie_u32  //
-wuffs_base__range_ie_u32__intersect(wuffs_base__range_ie_u32* r,
+wuffs_base__range_ie_u32__intersect(const wuffs_base__range_ie_u32* r,
                                     wuffs_base__range_ie_u32 s) {
   wuffs_base__range_ie_u32 t;
   t.min_incl = wuffs_base__u32__max(r->min_incl, s.min_incl);
@@ -664,7 +678,7 @@
 }
 
 static inline wuffs_base__range_ie_u32  //
-wuffs_base__range_ie_u32__unite(wuffs_base__range_ie_u32* r,
+wuffs_base__range_ie_u32__unite(const wuffs_base__range_ie_u32* r,
                                 wuffs_base__range_ie_u32 s) {
   if (wuffs_base__range_ie_u32__is_empty(r)) {
     return s;
@@ -679,56 +693,57 @@
 }
 
 static inline bool  //
-wuffs_base__range_ie_u32__contains(wuffs_base__range_ie_u32* r, uint32_t x) {
+wuffs_base__range_ie_u32__contains(const wuffs_base__range_ie_u32* r,
+                                   uint32_t x) {
   return (r->min_incl <= x) && (x < r->max_excl);
 }
 
 static inline bool  //
-wuffs_base__range_ie_u32__contains_range(wuffs_base__range_ie_u32* r,
+wuffs_base__range_ie_u32__contains_range(const wuffs_base__range_ie_u32* r,
                                          wuffs_base__range_ie_u32 s) {
   return wuffs_base__range_ie_u32__equals(
       &s, wuffs_base__range_ie_u32__intersect(r, s));
 }
 
 static inline uint32_t  //
-wuffs_base__range_ie_u32__length(wuffs_base__range_ie_u32* r) {
+wuffs_base__range_ie_u32__length(const wuffs_base__range_ie_u32* r) {
   return wuffs_base__u32__sat_sub(r->max_excl, r->min_incl);
 }
 
 #ifdef __cplusplus
 
 inline bool  //
-wuffs_base__range_ie_u32::is_empty() {
+wuffs_base__range_ie_u32::is_empty() const {
   return wuffs_base__range_ie_u32__is_empty(this);
 }
 
 inline bool  //
-wuffs_base__range_ie_u32::equals(wuffs_base__range_ie_u32 s) {
+wuffs_base__range_ie_u32::equals(wuffs_base__range_ie_u32 s) const {
   return wuffs_base__range_ie_u32__equals(this, s);
 }
 
 inline wuffs_base__range_ie_u32  //
-wuffs_base__range_ie_u32::intersect(wuffs_base__range_ie_u32 s) {
+wuffs_base__range_ie_u32::intersect(wuffs_base__range_ie_u32 s) const {
   return wuffs_base__range_ie_u32__intersect(this, s);
 }
 
 inline wuffs_base__range_ie_u32  //
-wuffs_base__range_ie_u32::unite(wuffs_base__range_ie_u32 s) {
+wuffs_base__range_ie_u32::unite(wuffs_base__range_ie_u32 s) const {
   return wuffs_base__range_ie_u32__unite(this, s);
 }
 
 inline bool  //
-wuffs_base__range_ie_u32::contains(uint32_t x) {
+wuffs_base__range_ie_u32::contains(uint32_t x) const {
   return wuffs_base__range_ie_u32__contains(this, x);
 }
 
 inline bool  //
-wuffs_base__range_ie_u32::contains_range(wuffs_base__range_ie_u32 s) {
+wuffs_base__range_ie_u32::contains_range(wuffs_base__range_ie_u32 s) const {
   return wuffs_base__range_ie_u32__contains_range(this, s);
 }
 
 inline uint32_t  //
-wuffs_base__range_ie_u32::length() {
+wuffs_base__range_ie_u32::length() const {
   return wuffs_base__range_ie_u32__length(this);
 }
 
@@ -741,14 +756,14 @@
   uint64_t max_incl;
 
 #ifdef __cplusplus
-  inline bool is_empty();
-  inline bool equals(wuffs_base__range_ii_u64__struct s);
+  inline bool is_empty() const;
+  inline bool equals(wuffs_base__range_ii_u64__struct s) const;
   inline wuffs_base__range_ii_u64__struct intersect(
-      wuffs_base__range_ii_u64__struct s);
+      wuffs_base__range_ii_u64__struct s) const;
   inline wuffs_base__range_ii_u64__struct unite(
-      wuffs_base__range_ii_u64__struct s);
-  inline bool contains(uint64_t x);
-  inline bool contains_range(wuffs_base__range_ii_u64__struct s);
+      wuffs_base__range_ii_u64__struct s) const;
+  inline bool contains(uint64_t x) const;
+  inline bool contains_range(wuffs_base__range_ii_u64__struct s) const;
 #endif  // __cplusplus
 
 } wuffs_base__range_ii_u64;
@@ -762,12 +777,12 @@
 }
 
 static inline bool  //
-wuffs_base__range_ii_u64__is_empty(wuffs_base__range_ii_u64* r) {
+wuffs_base__range_ii_u64__is_empty(const wuffs_base__range_ii_u64* r) {
   return r->min_incl > r->max_incl;
 }
 
 static inline bool  //
-wuffs_base__range_ii_u64__equals(wuffs_base__range_ii_u64* r,
+wuffs_base__range_ii_u64__equals(const wuffs_base__range_ii_u64* r,
                                  wuffs_base__range_ii_u64 s) {
   return (r->min_incl == s.min_incl && r->max_incl == s.max_incl) ||
          (wuffs_base__range_ii_u64__is_empty(r) &&
@@ -775,7 +790,7 @@
 }
 
 static inline wuffs_base__range_ii_u64  //
-wuffs_base__range_ii_u64__intersect(wuffs_base__range_ii_u64* r,
+wuffs_base__range_ii_u64__intersect(const wuffs_base__range_ii_u64* r,
                                     wuffs_base__range_ii_u64 s) {
   wuffs_base__range_ii_u64 t;
   t.min_incl = wuffs_base__u64__max(r->min_incl, s.min_incl);
@@ -784,7 +799,7 @@
 }
 
 static inline wuffs_base__range_ii_u64  //
-wuffs_base__range_ii_u64__unite(wuffs_base__range_ii_u64* r,
+wuffs_base__range_ii_u64__unite(const wuffs_base__range_ii_u64* r,
                                 wuffs_base__range_ii_u64 s) {
   if (wuffs_base__range_ii_u64__is_empty(r)) {
     return s;
@@ -799,12 +814,13 @@
 }
 
 static inline bool  //
-wuffs_base__range_ii_u64__contains(wuffs_base__range_ii_u64* r, uint64_t x) {
+wuffs_base__range_ii_u64__contains(const wuffs_base__range_ii_u64* r,
+                                   uint64_t x) {
   return (r->min_incl <= x) && (x <= r->max_incl);
 }
 
 static inline bool  //
-wuffs_base__range_ii_u64__contains_range(wuffs_base__range_ii_u64* r,
+wuffs_base__range_ii_u64__contains_range(const wuffs_base__range_ii_u64* r,
                                          wuffs_base__range_ii_u64 s) {
   return wuffs_base__range_ii_u64__equals(
       &s, wuffs_base__range_ii_u64__intersect(r, s));
@@ -813,32 +829,32 @@
 #ifdef __cplusplus
 
 inline bool  //
-wuffs_base__range_ii_u64::is_empty() {
+wuffs_base__range_ii_u64::is_empty() const {
   return wuffs_base__range_ii_u64__is_empty(this);
 }
 
 inline bool  //
-wuffs_base__range_ii_u64::equals(wuffs_base__range_ii_u64 s) {
+wuffs_base__range_ii_u64::equals(wuffs_base__range_ii_u64 s) const {
   return wuffs_base__range_ii_u64__equals(this, s);
 }
 
 inline wuffs_base__range_ii_u64  //
-wuffs_base__range_ii_u64::intersect(wuffs_base__range_ii_u64 s) {
+wuffs_base__range_ii_u64::intersect(wuffs_base__range_ii_u64 s) const {
   return wuffs_base__range_ii_u64__intersect(this, s);
 }
 
 inline wuffs_base__range_ii_u64  //
-wuffs_base__range_ii_u64::unite(wuffs_base__range_ii_u64 s) {
+wuffs_base__range_ii_u64::unite(wuffs_base__range_ii_u64 s) const {
   return wuffs_base__range_ii_u64__unite(this, s);
 }
 
 inline bool  //
-wuffs_base__range_ii_u64::contains(uint64_t x) {
+wuffs_base__range_ii_u64::contains(uint64_t x) const {
   return wuffs_base__range_ii_u64__contains(this, x);
 }
 
 inline bool  //
-wuffs_base__range_ii_u64::contains_range(wuffs_base__range_ii_u64 s) {
+wuffs_base__range_ii_u64::contains_range(wuffs_base__range_ii_u64 s) const {
   return wuffs_base__range_ii_u64__contains_range(this, s);
 }
 
@@ -851,15 +867,15 @@
   uint64_t max_excl;
 
 #ifdef __cplusplus
-  inline bool is_empty();
-  inline bool equals(wuffs_base__range_ie_u64__struct s);
+  inline bool is_empty() const;
+  inline bool equals(wuffs_base__range_ie_u64__struct s) const;
   inline wuffs_base__range_ie_u64__struct intersect(
-      wuffs_base__range_ie_u64__struct s);
+      wuffs_base__range_ie_u64__struct s) const;
   inline wuffs_base__range_ie_u64__struct unite(
-      wuffs_base__range_ie_u64__struct s);
-  inline bool contains(uint64_t x);
-  inline bool contains_range(wuffs_base__range_ie_u64__struct s);
-  inline uint64_t length();
+      wuffs_base__range_ie_u64__struct s) const;
+  inline bool contains(uint64_t x) const;
+  inline bool contains_range(wuffs_base__range_ie_u64__struct s) const;
+  inline uint64_t length() const;
 #endif  // __cplusplus
 
 } wuffs_base__range_ie_u64;
@@ -873,12 +889,12 @@
 }
 
 static inline bool  //
-wuffs_base__range_ie_u64__is_empty(wuffs_base__range_ie_u64* r) {
+wuffs_base__range_ie_u64__is_empty(const wuffs_base__range_ie_u64* r) {
   return r->min_incl >= r->max_excl;
 }
 
 static inline bool  //
-wuffs_base__range_ie_u64__equals(wuffs_base__range_ie_u64* r,
+wuffs_base__range_ie_u64__equals(const wuffs_base__range_ie_u64* r,
                                  wuffs_base__range_ie_u64 s) {
   return (r->min_incl == s.min_incl && r->max_excl == s.max_excl) ||
          (wuffs_base__range_ie_u64__is_empty(r) &&
@@ -886,7 +902,7 @@
 }
 
 static inline wuffs_base__range_ie_u64  //
-wuffs_base__range_ie_u64__intersect(wuffs_base__range_ie_u64* r,
+wuffs_base__range_ie_u64__intersect(const wuffs_base__range_ie_u64* r,
                                     wuffs_base__range_ie_u64 s) {
   wuffs_base__range_ie_u64 t;
   t.min_incl = wuffs_base__u64__max(r->min_incl, s.min_incl);
@@ -895,7 +911,7 @@
 }
 
 static inline wuffs_base__range_ie_u64  //
-wuffs_base__range_ie_u64__unite(wuffs_base__range_ie_u64* r,
+wuffs_base__range_ie_u64__unite(const wuffs_base__range_ie_u64* r,
                                 wuffs_base__range_ie_u64 s) {
   if (wuffs_base__range_ie_u64__is_empty(r)) {
     return s;
@@ -910,56 +926,57 @@
 }
 
 static inline bool  //
-wuffs_base__range_ie_u64__contains(wuffs_base__range_ie_u64* r, uint64_t x) {
+wuffs_base__range_ie_u64__contains(const wuffs_base__range_ie_u64* r,
+                                   uint64_t x) {
   return (r->min_incl <= x) && (x < r->max_excl);
 }
 
 static inline bool  //
-wuffs_base__range_ie_u64__contains_range(wuffs_base__range_ie_u64* r,
+wuffs_base__range_ie_u64__contains_range(const wuffs_base__range_ie_u64* r,
                                          wuffs_base__range_ie_u64 s) {
   return wuffs_base__range_ie_u64__equals(
       &s, wuffs_base__range_ie_u64__intersect(r, s));
 }
 
 static inline uint64_t  //
-wuffs_base__range_ie_u64__length(wuffs_base__range_ie_u64* r) {
+wuffs_base__range_ie_u64__length(const wuffs_base__range_ie_u64* r) {
   return wuffs_base__u64__sat_sub(r->max_excl, r->min_incl);
 }
 
 #ifdef __cplusplus
 
 inline bool  //
-wuffs_base__range_ie_u64::is_empty() {
+wuffs_base__range_ie_u64::is_empty() const {
   return wuffs_base__range_ie_u64__is_empty(this);
 }
 
 inline bool  //
-wuffs_base__range_ie_u64::equals(wuffs_base__range_ie_u64 s) {
+wuffs_base__range_ie_u64::equals(wuffs_base__range_ie_u64 s) const {
   return wuffs_base__range_ie_u64__equals(this, s);
 }
 
 inline wuffs_base__range_ie_u64  //
-wuffs_base__range_ie_u64::intersect(wuffs_base__range_ie_u64 s) {
+wuffs_base__range_ie_u64::intersect(wuffs_base__range_ie_u64 s) const {
   return wuffs_base__range_ie_u64__intersect(this, s);
 }
 
 inline wuffs_base__range_ie_u64  //
-wuffs_base__range_ie_u64::unite(wuffs_base__range_ie_u64 s) {
+wuffs_base__range_ie_u64::unite(wuffs_base__range_ie_u64 s) const {
   return wuffs_base__range_ie_u64__unite(this, s);
 }
 
 inline bool  //
-wuffs_base__range_ie_u64::contains(uint64_t x) {
+wuffs_base__range_ie_u64::contains(uint64_t x) const {
   return wuffs_base__range_ie_u64__contains(this, x);
 }
 
 inline bool  //
-wuffs_base__range_ie_u64::contains_range(wuffs_base__range_ie_u64 s) {
+wuffs_base__range_ie_u64::contains_range(wuffs_base__range_ie_u64 s) const {
   return wuffs_base__range_ie_u64__contains_range(this, s);
 }
 
 inline uint64_t  //
-wuffs_base__range_ie_u64::length() {
+wuffs_base__range_ie_u64::length() const {
   return wuffs_base__range_ie_u64__length(this);
 }
 
@@ -983,14 +1000,14 @@
   uint32_t max_incl_y;
 
 #ifdef __cplusplus
-  inline bool is_empty();
-  inline bool equals(wuffs_base__rect_ii_u32__struct s);
+  inline bool is_empty() const;
+  inline bool equals(wuffs_base__rect_ii_u32__struct s) const;
   inline wuffs_base__rect_ii_u32__struct intersect(
-      wuffs_base__rect_ii_u32__struct s);
+      wuffs_base__rect_ii_u32__struct s) const;
   inline wuffs_base__rect_ii_u32__struct unite(
-      wuffs_base__rect_ii_u32__struct s);
-  inline bool contains(uint32_t x, uint32_t y);
-  inline bool contains_rect(wuffs_base__rect_ii_u32__struct s);
+      wuffs_base__rect_ii_u32__struct s) const;
+  inline bool contains(uint32_t x, uint32_t y) const;
+  inline bool contains_rect(wuffs_base__rect_ii_u32__struct s) const;
 #endif  // __cplusplus
 
 } wuffs_base__rect_ii_u32;
@@ -1009,12 +1026,12 @@
 }
 
 static inline bool  //
-wuffs_base__rect_ii_u32__is_empty(wuffs_base__rect_ii_u32* r) {
+wuffs_base__rect_ii_u32__is_empty(const wuffs_base__rect_ii_u32* r) {
   return (r->min_incl_x > r->max_incl_x) || (r->min_incl_y > r->max_incl_y);
 }
 
 static inline bool  //
-wuffs_base__rect_ii_u32__equals(wuffs_base__rect_ii_u32* r,
+wuffs_base__rect_ii_u32__equals(const wuffs_base__rect_ii_u32* r,
                                 wuffs_base__rect_ii_u32 s) {
   return (r->min_incl_x == s.min_incl_x && r->min_incl_y == s.min_incl_y &&
           r->max_incl_x == s.max_incl_x && r->max_incl_y == s.max_incl_y) ||
@@ -1023,7 +1040,7 @@
 }
 
 static inline wuffs_base__rect_ii_u32  //
-wuffs_base__rect_ii_u32__intersect(wuffs_base__rect_ii_u32* r,
+wuffs_base__rect_ii_u32__intersect(const wuffs_base__rect_ii_u32* r,
                                    wuffs_base__rect_ii_u32 s) {
   wuffs_base__rect_ii_u32 t;
   t.min_incl_x = wuffs_base__u32__max(r->min_incl_x, s.min_incl_x);
@@ -1034,7 +1051,7 @@
 }
 
 static inline wuffs_base__rect_ii_u32  //
-wuffs_base__rect_ii_u32__unite(wuffs_base__rect_ii_u32* r,
+wuffs_base__rect_ii_u32__unite(const wuffs_base__rect_ii_u32* r,
                                wuffs_base__rect_ii_u32 s) {
   if (wuffs_base__rect_ii_u32__is_empty(r)) {
     return s;
@@ -1051,7 +1068,7 @@
 }
 
 static inline bool  //
-wuffs_base__rect_ii_u32__contains(wuffs_base__rect_ii_u32* r,
+wuffs_base__rect_ii_u32__contains(const wuffs_base__rect_ii_u32* r,
                                   uint32_t x,
                                   uint32_t y) {
   return (r->min_incl_x <= x) && (x <= r->max_incl_x) && (r->min_incl_y <= y) &&
@@ -1059,7 +1076,7 @@
 }
 
 static inline bool  //
-wuffs_base__rect_ii_u32__contains_rect(wuffs_base__rect_ii_u32* r,
+wuffs_base__rect_ii_u32__contains_rect(const wuffs_base__rect_ii_u32* r,
                                        wuffs_base__rect_ii_u32 s) {
   return wuffs_base__rect_ii_u32__equals(
       &s, wuffs_base__rect_ii_u32__intersect(r, s));
@@ -1068,32 +1085,32 @@
 #ifdef __cplusplus
 
 inline bool  //
-wuffs_base__rect_ii_u32::is_empty() {
+wuffs_base__rect_ii_u32::is_empty() const {
   return wuffs_base__rect_ii_u32__is_empty(this);
 }
 
 inline bool  //
-wuffs_base__rect_ii_u32::equals(wuffs_base__rect_ii_u32 s) {
+wuffs_base__rect_ii_u32::equals(wuffs_base__rect_ii_u32 s) const {
   return wuffs_base__rect_ii_u32__equals(this, s);
 }
 
 inline wuffs_base__rect_ii_u32  //
-wuffs_base__rect_ii_u32::intersect(wuffs_base__rect_ii_u32 s) {
+wuffs_base__rect_ii_u32::intersect(wuffs_base__rect_ii_u32 s) const {
   return wuffs_base__rect_ii_u32__intersect(this, s);
 }
 
 inline wuffs_base__rect_ii_u32  //
-wuffs_base__rect_ii_u32::unite(wuffs_base__rect_ii_u32 s) {
+wuffs_base__rect_ii_u32::unite(wuffs_base__rect_ii_u32 s) const {
   return wuffs_base__rect_ii_u32__unite(this, s);
 }
 
 inline bool  //
-wuffs_base__rect_ii_u32::contains(uint32_t x, uint32_t y) {
+wuffs_base__rect_ii_u32::contains(uint32_t x, uint32_t y) const {
   return wuffs_base__rect_ii_u32__contains(this, x, y);
 }
 
 inline bool  //
-wuffs_base__rect_ii_u32::contains_rect(wuffs_base__rect_ii_u32 s) {
+wuffs_base__rect_ii_u32::contains_rect(wuffs_base__rect_ii_u32 s) const {
   return wuffs_base__rect_ii_u32__contains_rect(this, s);
 }
 
@@ -1118,16 +1135,16 @@
   uint32_t max_excl_y;
 
 #ifdef __cplusplus
-  inline bool is_empty();
-  inline bool equals(wuffs_base__rect_ie_u32__struct s);
+  inline bool is_empty() const;
+  inline bool equals(wuffs_base__rect_ie_u32__struct s) const;
   inline wuffs_base__rect_ie_u32__struct intersect(
-      wuffs_base__rect_ie_u32__struct s);
+      wuffs_base__rect_ie_u32__struct s) const;
   inline wuffs_base__rect_ie_u32__struct unite(
-      wuffs_base__rect_ie_u32__struct s);
-  inline bool contains(uint32_t x, uint32_t y);
-  inline bool contains_rect(wuffs_base__rect_ie_u32__struct s);
-  inline uint32_t width();
-  inline uint32_t height();
+      wuffs_base__rect_ie_u32__struct s) const;
+  inline bool contains(uint32_t x, uint32_t y) const;
+  inline bool contains_rect(wuffs_base__rect_ie_u32__struct s) const;
+  inline uint32_t width() const;
+  inline uint32_t height() const;
 #endif  // __cplusplus
 
 } wuffs_base__rect_ie_u32;
@@ -1146,12 +1163,12 @@
 }
 
 static inline bool  //
-wuffs_base__rect_ie_u32__is_empty(wuffs_base__rect_ie_u32* r) {
+wuffs_base__rect_ie_u32__is_empty(const wuffs_base__rect_ie_u32* r) {
   return (r->min_incl_x >= r->max_excl_x) || (r->min_incl_y >= r->max_excl_y);
 }
 
 static inline bool  //
-wuffs_base__rect_ie_u32__equals(wuffs_base__rect_ie_u32* r,
+wuffs_base__rect_ie_u32__equals(const wuffs_base__rect_ie_u32* r,
                                 wuffs_base__rect_ie_u32 s) {
   return (r->min_incl_x == s.min_incl_x && r->min_incl_y == s.min_incl_y &&
           r->max_excl_x == s.max_excl_x && r->max_excl_y == s.max_excl_y) ||
@@ -1160,7 +1177,7 @@
 }
 
 static inline wuffs_base__rect_ie_u32  //
-wuffs_base__rect_ie_u32__intersect(wuffs_base__rect_ie_u32* r,
+wuffs_base__rect_ie_u32__intersect(const wuffs_base__rect_ie_u32* r,
                                    wuffs_base__rect_ie_u32 s) {
   wuffs_base__rect_ie_u32 t;
   t.min_incl_x = wuffs_base__u32__max(r->min_incl_x, s.min_incl_x);
@@ -1171,7 +1188,7 @@
 }
 
 static inline wuffs_base__rect_ie_u32  //
-wuffs_base__rect_ie_u32__unite(wuffs_base__rect_ie_u32* r,
+wuffs_base__rect_ie_u32__unite(const wuffs_base__rect_ie_u32* r,
                                wuffs_base__rect_ie_u32 s) {
   if (wuffs_base__rect_ie_u32__is_empty(r)) {
     return s;
@@ -1188,7 +1205,7 @@
 }
 
 static inline bool  //
-wuffs_base__rect_ie_u32__contains(wuffs_base__rect_ie_u32* r,
+wuffs_base__rect_ie_u32__contains(const wuffs_base__rect_ie_u32* r,
                                   uint32_t x,
                                   uint32_t y) {
   return (r->min_incl_x <= x) && (x < r->max_excl_x) && (r->min_incl_y <= y) &&
@@ -1196,95 +1213,69 @@
 }
 
 static inline bool  //
-wuffs_base__rect_ie_u32__contains_rect(wuffs_base__rect_ie_u32* r,
+wuffs_base__rect_ie_u32__contains_rect(const wuffs_base__rect_ie_u32* r,
                                        wuffs_base__rect_ie_u32 s) {
   return wuffs_base__rect_ie_u32__equals(
       &s, wuffs_base__rect_ie_u32__intersect(r, s));
 }
 
 static inline uint32_t  //
-wuffs_base__rect_ie_u32__width(wuffs_base__rect_ie_u32* r) {
+wuffs_base__rect_ie_u32__width(const wuffs_base__rect_ie_u32* r) {
   return wuffs_base__u32__sat_sub(r->max_excl_x, r->min_incl_x);
 }
 
 static inline uint32_t  //
-wuffs_base__rect_ie_u32__height(wuffs_base__rect_ie_u32* r) {
+wuffs_base__rect_ie_u32__height(const wuffs_base__rect_ie_u32* r) {
   return wuffs_base__u32__sat_sub(r->max_excl_y, r->min_incl_y);
 }
 
 #ifdef __cplusplus
 
 inline bool  //
-wuffs_base__rect_ie_u32::is_empty() {
+wuffs_base__rect_ie_u32::is_empty() const {
   return wuffs_base__rect_ie_u32__is_empty(this);
 }
 
 inline bool  //
-wuffs_base__rect_ie_u32::equals(wuffs_base__rect_ie_u32 s) {
+wuffs_base__rect_ie_u32::equals(wuffs_base__rect_ie_u32 s) const {
   return wuffs_base__rect_ie_u32__equals(this, s);
 }
 
 inline wuffs_base__rect_ie_u32  //
-wuffs_base__rect_ie_u32::intersect(wuffs_base__rect_ie_u32 s) {
+wuffs_base__rect_ie_u32::intersect(wuffs_base__rect_ie_u32 s) const {
   return wuffs_base__rect_ie_u32__intersect(this, s);
 }
 
 inline wuffs_base__rect_ie_u32  //
-wuffs_base__rect_ie_u32::unite(wuffs_base__rect_ie_u32 s) {
+wuffs_base__rect_ie_u32::unite(wuffs_base__rect_ie_u32 s) const {
   return wuffs_base__rect_ie_u32__unite(this, s);
 }
 
 inline bool  //
-wuffs_base__rect_ie_u32::contains(uint32_t x, uint32_t y) {
+wuffs_base__rect_ie_u32::contains(uint32_t x, uint32_t y) const {
   return wuffs_base__rect_ie_u32__contains(this, x, y);
 }
 
 inline bool  //
-wuffs_base__rect_ie_u32::contains_rect(wuffs_base__rect_ie_u32 s) {
+wuffs_base__rect_ie_u32::contains_rect(wuffs_base__rect_ie_u32 s) const {
   return wuffs_base__rect_ie_u32__contains_rect(this, s);
 }
 
 inline uint32_t  //
-wuffs_base__rect_ie_u32::width() {
+wuffs_base__rect_ie_u32::width() const {
   return wuffs_base__rect_ie_u32__width(this);
 }
 
 inline uint32_t  //
-wuffs_base__rect_ie_u32::height() {
+wuffs_base__rect_ie_u32::height() const {
   return wuffs_base__rect_ie_u32__height(this);
 }
 
 #endif  // __cplusplus
 
 // ---------------- I/O
-
-struct wuffs_base__io_buffer__struct;
-
-typedef struct {
-  // Do not access the private_impl's fields directly. There is no API/ABI
-  // compatibility or safety guarantee if you do so.
-  struct {
-    struct wuffs_base__io_buffer__struct* buf;
-    // The bounds values are typically NULL, when created by the Wuffs public
-    // API. NULL means that the callee substitutes the implicit bounds derived
-    // from buf.
-    uint8_t* mark;
-    uint8_t* limit;
-  } private_impl;
-} wuffs_base__io_reader;
-
-typedef struct {
-  // Do not access the private_impl's fields directly. There is no API/ABI
-  // compatibility or safety guarantee if you do so.
-  struct {
-    struct wuffs_base__io_buffer__struct* buf;
-    // The bounds values are typically NULL, when created by the Wuffs public
-    // API. NULL means that the callee substitutes the implicit bounds derived
-    // from buf.
-    uint8_t* mark;
-    uint8_t* limit;
-  } private_impl;
-} wuffs_base__io_writer;
+//
+// See (/doc/note/io-input-output.md).
 
 // wuffs_base__io_buffer_meta is the metadata for a wuffs_base__io_buffer's
 // data.
@@ -1305,10 +1296,12 @@
 
 #ifdef __cplusplus
   inline void compact();
-  inline wuffs_base__io_reader reader();
-  inline wuffs_base__io_writer writer();
-  inline uint64_t reader_io_position();
-  inline uint64_t writer_io_position();
+  inline wuffs_base__io_buffer__struct* reader();  // Deprecated.
+  inline wuffs_base__io_buffer__struct* writer();  // Deprecated.
+  inline uint64_t reader_available() const;
+  inline uint64_t reader_io_position() const;
+  inline uint64_t writer_available() const;
+  inline uint64_t writer_io_position() const;
 #endif  // __cplusplus
 
 } wuffs_base__io_buffer;
@@ -1357,24 +1350,6 @@
   return ret;
 }
 
-static inline wuffs_base__io_reader  //
-wuffs_base__null_io_reader() {
-  wuffs_base__io_reader ret;
-  ret.private_impl.buf = NULL;
-  ret.private_impl.mark = NULL;
-  ret.private_impl.limit = NULL;
-  return ret;
-}
-
-static inline wuffs_base__io_writer  //
-wuffs_base__null_io_writer() {
-  wuffs_base__io_writer ret;
-  ret.private_impl.buf = NULL;
-  ret.private_impl.mark = NULL;
-  ret.private_impl.limit = NULL;
-  return ret;
-}
-
 // wuffs_base__io_buffer__compact moves any written but unread bytes to the
 // start of the buffer.
 static inline void  //
@@ -1391,31 +1366,23 @@
   buf->meta.ri = 0;
 }
 
-static inline wuffs_base__io_reader  //
-wuffs_base__io_buffer__reader(wuffs_base__io_buffer* buf) {
-  wuffs_base__io_reader ret;
-  ret.private_impl.buf = buf;
-  ret.private_impl.mark = NULL;
-  ret.private_impl.limit = NULL;
-  return ret;
-}
-
-static inline wuffs_base__io_writer  //
-wuffs_base__io_buffer__writer(wuffs_base__io_buffer* buf) {
-  wuffs_base__io_writer ret;
-  ret.private_impl.buf = buf;
-  ret.private_impl.mark = NULL;
-  ret.private_impl.limit = NULL;
-  return ret;
+static inline uint64_t  //
+wuffs_base__io_buffer__reader_available(const wuffs_base__io_buffer* buf) {
+  return buf ? buf->meta.wi - buf->meta.ri : 0;
 }
 
 static inline uint64_t  //
-wuffs_base__io_buffer__reader_io_position(wuffs_base__io_buffer* buf) {
+wuffs_base__io_buffer__reader_io_position(const wuffs_base__io_buffer* buf) {
   return buf ? wuffs_base__u64__sat_add(buf->meta.pos, buf->meta.ri) : 0;
 }
 
 static inline uint64_t  //
-wuffs_base__io_buffer__writer_io_position(wuffs_base__io_buffer* buf) {
+wuffs_base__io_buffer__writer_available(const wuffs_base__io_buffer* buf) {
+  return buf ? buf->data.len - buf->meta.wi : 0;
+}
+
+static inline uint64_t  //
+wuffs_base__io_buffer__writer_io_position(const wuffs_base__io_buffer* buf) {
   return buf ? wuffs_base__u64__sat_add(buf->meta.pos, buf->meta.wi) : 0;
 }
 
@@ -1426,23 +1393,33 @@
   wuffs_base__io_buffer__compact(this);
 }
 
-inline wuffs_base__io_reader  //
+inline wuffs_base__io_buffer*  //
 wuffs_base__io_buffer__struct::reader() {
-  return wuffs_base__io_buffer__reader(this);
+  return this;
 }
 
-inline wuffs_base__io_writer  //
+inline wuffs_base__io_buffer*  //
 wuffs_base__io_buffer__struct::writer() {
-  return wuffs_base__io_buffer__writer(this);
+  return this;
 }
 
 inline uint64_t  //
-wuffs_base__io_buffer__struct::reader_io_position() {
+wuffs_base__io_buffer__struct::reader_available() const {
+  return wuffs_base__io_buffer__reader_available(this);
+}
+
+inline uint64_t  //
+wuffs_base__io_buffer__struct::reader_io_position() const {
   return wuffs_base__io_buffer__reader_io_position(this);
 }
 
 inline uint64_t  //
-wuffs_base__io_buffer__struct::writer_io_position() {
+wuffs_base__io_buffer__struct::writer_available() const {
+  return wuffs_base__io_buffer__writer_available(this);
+}
+
+inline uint64_t  //
+wuffs_base__io_buffer__struct::writer_io_position() const {
   return wuffs_base__io_buffer__writer_io_position(this);
 }
 
@@ -1526,9 +1503,9 @@
 //  - bit        20 indicates big-endian/MSB-first (as opposed to little/LSB).
 //  - bit        19 indicates floating point (as opposed to integer).
 //  - bit        18 indicates palette-indexed. The number-of-planes (the next
-//                  field) will be zero, as the format is considered packed,
+//                  field) will be 0, as the format is considered interleaved,
 //                  but the 8-bit N-BGRA color data is stored in plane 3.
-//  - bits 17 .. 16 are the number of planes, minus 1. Zero means packed.
+//  - bits 17 .. 16 are the number of planes, minus 1. Zero means interleaved.
 //  - bits 15 .. 12 encodes the number of bits (depth) in the 3rd channel.
 //  - bits 11 ..  8 encodes the number of bits (depth) in the 2nd channel.
 //  - bits  7 ..  4 encodes the number of bits (depth) in the 1st channel.
@@ -1545,14 +1522,14 @@
 // channels: cyan, magenta, yellow, black).
 //
 // For direct formats with N > 1 channels, those channels can be laid out in
-// either 1 (packed) or N (planar) planes. For example, RGBA data is usually
-// packed, but YCbCr data is usually planar, due to chroma subsampling (for
-// details, see the wuffs_base__pixel_subsampling type).
+// either 1 (interleaved) or N (planar) planes. For example, RGBA data is
+// usually interleaved, but YCbCr data is usually planar, due to chroma
+// subsampling (for details, see the wuffs_base__pixel_subsampling type).
 //
 // For indexed formats, the palette (always 256 × 4 bytes) holds 8 bits per
 // channel non-alpha-premultiplied BGRA color data. There is only 1 plane (for
-// the index), as the format is considered packed. Plane 0 holds the per-pixel
-// indices. Plane 3 is re-purposed to hold the per-index colors.
+// the index), as the format is considered interleaved. Plane 0 holds the
+// per-pixel indices. Plane 3 is re-purposed to hold the per-index colors.
 //
 // The color field is encoded in 3 bits:
 //  - 0 means                   A (Alpha).
@@ -1566,8 +1543,8 @@
 //
 // In Wuffs, channels are given in memory order (also known as byte order),
 // regardless of endianness, since the C type for the pixel data is an array of
-// bytes, not an array of uint32_t. For example, packed BGRA with 8 bits per
-// channel means that the bytes in memory are always Blue, Green, Red then
+// bytes, not an array of uint32_t. For example, interleaved BGRA with 8 bits
+// per channel means that the bytes in memory are always Blue, Green, Red then
 // Alpha. On big-endian systems, that is the uint32_t 0xBBGGRRAA. On
 // little-endian, 0xAARRGGBB.
 //
@@ -1600,15 +1577,15 @@
 //
 // For example, wuffs_base__pixel_format 0x5510BBBB is a natural format for
 // decoding a PNG image - network byte order (also known as big-endian),
-// packed, non-premultiplied alpha - that happens to be 16-bit-depth truecolor
-// with alpha (RGBA). In memory order:
+// interleaved, non-premultiplied alpha - that happens to be 16-bit-depth
+// truecolor with alpha (RGBA). In memory order:
 //
 //  ptr+0  ptr+1  ptr+2  ptr+3  ptr+4  ptr+5  ptr+6  ptr+7
 //  Rhi    Rlo    Ghi    Glo    Bhi    Blo    Ahi    Alo
 //
 // For example, the value wuffs_base__pixel_format 0x40000565 means BGR with no
-// alpha or padding, 5/6/5 bits for blue/green/red, packed 2 bytes per pixel,
-// laid out LSB-first in memory order:
+// alpha or padding, 5/6/5 bits for blue/green/red, interleaved 2 bytes per
+// pixel, laid out LSB-first in memory order:
 //
 //  ptr+0...........  ptr+1...........
 //  MSB          LSB  MSB          LSB
@@ -1693,8 +1670,8 @@
   return f != 0;
 }
 
-// wuffs_base__pixel_format__bits_per_pixel returns, for packed pixel formats,
-// the number of bits per pixel. It returns 0 for planar pixel formats.
+// wuffs_base__pixel_format__bits_per_pixel returns the number of bits per
+// pixel for interleaved pixel formats, and returns 0 for planar pixel formats.
 static inline uint32_t  //
 wuffs_base__pixel_format__bits_per_pixel(wuffs_base__pixel_format f) {
   if (((f >> 16) & 0x03) != 0) {
@@ -1712,7 +1689,7 @@
 }
 
 static inline bool  //
-wuffs_base__pixel_format__is_packed(wuffs_base__pixel_format f) {
+wuffs_base__pixel_format__is_interleaved(wuffs_base__pixel_format f) {
   return ((f >> 16) & 0x03) == 0;
 }
 
@@ -1738,7 +1715,7 @@
 // plane p. For a depth of 8 bits (1 byte), the p'th plane's sample starts at
 // (planes[p].ptr + (j * planes[p].stride) + i).
 //
-// For packed pixel formats, the mapping is trivial: i = x and j = y. For
+// For interleaved pixel formats, the mapping is trivial: i = x and j = y. For
 // planar pixel formats, the mapping can differ due to chroma subsampling. For
 // example, consider a three plane YCbCr pixel format with 4:2:2 subsampling.
 // For the luma (Y) channel, there is one sample for every pixel, but for the
@@ -1829,13 +1806,13 @@
                   uint32_t width,
                   uint32_t height);
   inline void invalidate();
-  inline bool is_valid();
-  inline wuffs_base__pixel_format pixel_format();
-  inline wuffs_base__pixel_subsampling pixel_subsampling();
-  inline wuffs_base__rect_ie_u32 bounds();
-  inline uint32_t width();
-  inline uint32_t height();
-  inline uint64_t pixbuf_len();
+  inline bool is_valid() const;
+  inline wuffs_base__pixel_format pixel_format() const;
+  inline wuffs_base__pixel_subsampling pixel_subsampling() const;
+  inline wuffs_base__rect_ie_u32 bounds() const;
+  inline uint32_t width() const;
+  inline uint32_t height() const;
+  inline uint64_t pixbuf_len() const;
 #endif  // __cplusplus
 
 } wuffs_base__pixel_config;
@@ -1889,22 +1866,22 @@
 }
 
 static inline bool  //
-wuffs_base__pixel_config__is_valid(wuffs_base__pixel_config* c) {
+wuffs_base__pixel_config__is_valid(const wuffs_base__pixel_config* c) {
   return c && c->private_impl.pixfmt;
 }
 
 static inline wuffs_base__pixel_format  //
-wuffs_base__pixel_config__pixel_format(wuffs_base__pixel_config* c) {
+wuffs_base__pixel_config__pixel_format(const wuffs_base__pixel_config* c) {
   return c ? c->private_impl.pixfmt : 0;
 }
 
 static inline wuffs_base__pixel_subsampling  //
-wuffs_base__pixel_config__pixel_subsampling(wuffs_base__pixel_config* c) {
+wuffs_base__pixel_config__pixel_subsampling(const wuffs_base__pixel_config* c) {
   return c ? c->private_impl.pixsub : 0;
 }
 
 static inline wuffs_base__rect_ie_u32  //
-wuffs_base__pixel_config__bounds(wuffs_base__pixel_config* c) {
+wuffs_base__pixel_config__bounds(const wuffs_base__pixel_config* c) {
   if (c) {
     wuffs_base__rect_ie_u32 ret;
     ret.min_incl_x = 0;
@@ -1923,20 +1900,20 @@
 }
 
 static inline uint32_t  //
-wuffs_base__pixel_config__width(wuffs_base__pixel_config* c) {
+wuffs_base__pixel_config__width(const wuffs_base__pixel_config* c) {
   return c ? c->private_impl.width : 0;
 }
 
 static inline uint32_t  //
-wuffs_base__pixel_config__height(wuffs_base__pixel_config* c) {
+wuffs_base__pixel_config__height(const wuffs_base__pixel_config* c) {
   return c ? c->private_impl.height : 0;
 }
 
-// TODO: this is the right API for planar (not packed) pixbufs? Should it allow
-// decoding into a color model different from the format's intrinsic one? For
-// example, decoding a JPEG image straight to RGBA instead of to YCbCr?
+// TODO: this is the right API for planar (not interleaved) pixbufs? Should it
+// allow decoding into a color model different from the format's intrinsic one?
+// For example, decoding a JPEG image straight to RGBA instead of to YCbCr?
 static inline uint64_t  //
-wuffs_base__pixel_config__pixbuf_len(wuffs_base__pixel_config* c) {
+wuffs_base__pixel_config__pixbuf_len(const wuffs_base__pixel_config* c) {
   if (!c) {
     return 0;
   }
@@ -1985,37 +1962,37 @@
 }
 
 inline bool  //
-wuffs_base__pixel_config::is_valid() {
+wuffs_base__pixel_config::is_valid() const {
   return wuffs_base__pixel_config__is_valid(this);
 }
 
 inline wuffs_base__pixel_format  //
-wuffs_base__pixel_config::pixel_format() {
+wuffs_base__pixel_config::pixel_format() const {
   return wuffs_base__pixel_config__pixel_format(this);
 }
 
 inline wuffs_base__pixel_subsampling  //
-wuffs_base__pixel_config::pixel_subsampling() {
+wuffs_base__pixel_config::pixel_subsampling() const {
   return wuffs_base__pixel_config__pixel_subsampling(this);
 }
 
 inline wuffs_base__rect_ie_u32  //
-wuffs_base__pixel_config::bounds() {
+wuffs_base__pixel_config::bounds() const {
   return wuffs_base__pixel_config__bounds(this);
 }
 
 inline uint32_t  //
-wuffs_base__pixel_config::width() {
+wuffs_base__pixel_config::width() const {
   return wuffs_base__pixel_config__width(this);
 }
 
 inline uint32_t  //
-wuffs_base__pixel_config::height() {
+wuffs_base__pixel_config::height() const {
   return wuffs_base__pixel_config__height(this);
 }
 
 inline uint64_t  //
-wuffs_base__pixel_config::pixbuf_len() {
+wuffs_base__pixel_config::pixbuf_len() const {
   return wuffs_base__pixel_config__pixbuf_len(this);
 }
 
@@ -2041,9 +2018,9 @@
                   uint64_t first_frame_io_position,
                   bool first_frame_is_opaque);
   inline void invalidate();
-  inline bool is_valid();
-  inline uint64_t first_frame_io_position();
-  inline bool first_frame_is_opaque();
+  inline bool is_valid() const;
+  inline uint64_t first_frame_io_position() const;
+  inline bool first_frame_is_opaque() const;
 #endif  // __cplusplus
 
 } wuffs_base__image_config;
@@ -2100,17 +2077,19 @@
 }
 
 static inline bool  //
-wuffs_base__image_config__is_valid(wuffs_base__image_config* c) {
+wuffs_base__image_config__is_valid(const wuffs_base__image_config* c) {
   return c && wuffs_base__pixel_config__is_valid(&(c->pixcfg));
 }
 
 static inline uint64_t  //
-wuffs_base__image_config__first_frame_io_position(wuffs_base__image_config* c) {
+wuffs_base__image_config__first_frame_io_position(
+    const wuffs_base__image_config* c) {
   return c ? c->private_impl.first_frame_io_position : 0;
 }
 
 static inline bool  //
-wuffs_base__image_config__first_frame_is_opaque(wuffs_base__image_config* c) {
+wuffs_base__image_config__first_frame_is_opaque(
+    const wuffs_base__image_config* c) {
   return c ? c->private_impl.first_frame_is_opaque : false;
 }
 
@@ -2133,17 +2112,17 @@
 }
 
 inline bool  //
-wuffs_base__image_config::is_valid() {
+wuffs_base__image_config::is_valid() const {
   return wuffs_base__image_config__is_valid(this);
 }
 
 inline uint64_t  //
-wuffs_base__image_config::first_frame_io_position() {
+wuffs_base__image_config::first_frame_io_position() const {
   return wuffs_base__image_config__first_frame_io_position(this);
 }
 
 inline bool  //
-wuffs_base__image_config::first_frame_is_opaque() {
+wuffs_base__image_config::first_frame_is_opaque() const {
   return wuffs_base__image_config__first_frame_is_opaque(this);
 }
 
@@ -2199,6 +2178,7 @@
     uint64_t io_position;
     wuffs_base__animation_blend blend;
     wuffs_base__animation_disposal disposal;
+    wuffs_base__color_u32_argb_premul background_color;
   } private_impl;
 
 #ifdef __cplusplus
@@ -2207,15 +2187,17 @@
                      uint64_t index,
                      uint64_t io_position,
                      wuffs_base__animation_blend blend,
-                     wuffs_base__animation_disposal disposal);
-  inline wuffs_base__rect_ie_u32 bounds();
-  inline uint32_t width();
-  inline uint32_t height();
-  inline wuffs_base__flicks duration();
-  inline uint64_t index();
-  inline uint64_t io_position();
-  inline wuffs_base__animation_blend blend();
-  inline wuffs_base__animation_disposal disposal();
+                     wuffs_base__animation_disposal disposal,
+                     wuffs_base__color_u32_argb_premul background_color);
+  inline wuffs_base__rect_ie_u32 bounds() const;
+  inline uint32_t width() const;
+  inline uint32_t height() const;
+  inline wuffs_base__flicks duration() const;
+  inline uint64_t index() const;
+  inline uint64_t io_position() const;
+  inline wuffs_base__animation_blend blend() const;
+  inline wuffs_base__animation_disposal disposal() const;
+  inline wuffs_base__color_u32_argb_premul background_color() const;
 #endif  // __cplusplus
 
 } wuffs_base__frame_config;
@@ -2233,13 +2215,15 @@
 }
 
 static inline void  //
-wuffs_base__frame_config__update(wuffs_base__frame_config* c,
-                                 wuffs_base__rect_ie_u32 bounds,
-                                 wuffs_base__flicks duration,
-                                 uint64_t index,
-                                 uint64_t io_position,
-                                 wuffs_base__animation_blend blend,
-                                 wuffs_base__animation_disposal disposal) {
+wuffs_base__frame_config__update(
+    wuffs_base__frame_config* c,
+    wuffs_base__rect_ie_u32 bounds,
+    wuffs_base__flicks duration,
+    uint64_t index,
+    uint64_t io_position,
+    wuffs_base__animation_blend blend,
+    wuffs_base__animation_disposal disposal,
+    wuffs_base__color_u32_argb_premul background_color) {
   if (!c) {
     return;
   }
@@ -2250,10 +2234,11 @@
   c->private_impl.io_position = io_position;
   c->private_impl.blend = blend;
   c->private_impl.disposal = disposal;
+  c->private_impl.background_color = background_color;
 }
 
 static inline wuffs_base__rect_ie_u32  //
-wuffs_base__frame_config__bounds(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__bounds(const wuffs_base__frame_config* c) {
   if (c) {
     return c->private_impl.bounds;
   }
@@ -2267,103 +2252,115 @@
 }
 
 static inline uint32_t  //
-wuffs_base__frame_config__width(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__width(const wuffs_base__frame_config* c) {
   return c ? wuffs_base__rect_ie_u32__width(&c->private_impl.bounds) : 0;
 }
 
 static inline uint32_t  //
-wuffs_base__frame_config__height(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__height(const wuffs_base__frame_config* c) {
   return c ? wuffs_base__rect_ie_u32__height(&c->private_impl.bounds) : 0;
 }
 
 // wuffs_base__frame_config__duration returns the amount of time to display
 // this frame. Zero means to display forever - a still (non-animated) image.
 static inline wuffs_base__flicks  //
-wuffs_base__frame_config__duration(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__duration(const wuffs_base__frame_config* c) {
   return c ? c->private_impl.duration : 0;
 }
 
 // wuffs_base__frame_config__index returns the index of this frame. The first
 // frame in an image has index 0, the second frame has index 1, and so on.
 static inline uint64_t  //
-wuffs_base__frame_config__index(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__index(const wuffs_base__frame_config* c) {
   return c ? c->private_impl.index : 0;
 }
 
 // wuffs_base__frame_config__io_position returns the I/O stream position before
 // the frame config.
 static inline uint64_t  //
-wuffs_base__frame_config__io_position(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__io_position(const wuffs_base__frame_config* c) {
   return c ? c->private_impl.io_position : 0;
 }
 
 // wuffs_base__frame_config__blend returns, for an animated image, how to blend
 // the transparent pixels of this frame with the existing canvas.
 static inline wuffs_base__animation_blend  //
-wuffs_base__frame_config__blend(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__blend(const wuffs_base__frame_config* c) {
   return c ? c->private_impl.blend : 0;
 }
 
 // wuffs_base__frame_config__disposal returns, for an animated image, how to
 // dispose of this frame after displaying it.
 static inline wuffs_base__animation_disposal  //
-wuffs_base__frame_config__disposal(wuffs_base__frame_config* c) {
+wuffs_base__frame_config__disposal(const wuffs_base__frame_config* c) {
   return c ? c->private_impl.disposal : 0;
 }
 
+static inline wuffs_base__color_u32_argb_premul  //
+wuffs_base__frame_config__background_color(const wuffs_base__frame_config* c) {
+  return c ? c->private_impl.background_color : 0;
+}
+
 #ifdef __cplusplus
 
 inline void  //
-wuffs_base__frame_config::update(wuffs_base__rect_ie_u32 bounds,
-                                 wuffs_base__flicks duration,
-                                 uint64_t index,
-                                 uint64_t io_position,
-                                 wuffs_base__animation_blend blend,
-                                 wuffs_base__animation_disposal disposal) {
+wuffs_base__frame_config::update(
+    wuffs_base__rect_ie_u32 bounds,
+    wuffs_base__flicks duration,
+    uint64_t index,
+    uint64_t io_position,
+    wuffs_base__animation_blend blend,
+    wuffs_base__animation_disposal disposal,
+    wuffs_base__color_u32_argb_premul background_color) {
   wuffs_base__frame_config__update(this, bounds, duration, index, io_position,
-                                   blend, disposal);
+                                   blend, disposal, background_color);
 }
 
 inline wuffs_base__rect_ie_u32  //
-wuffs_base__frame_config::bounds() {
+wuffs_base__frame_config::bounds() const {
   return wuffs_base__frame_config__bounds(this);
 }
 
 inline uint32_t  //
-wuffs_base__frame_config::width() {
+wuffs_base__frame_config::width() const {
   return wuffs_base__frame_config__width(this);
 }
 
 inline uint32_t  //
-wuffs_base__frame_config::height() {
+wuffs_base__frame_config::height() const {
   return wuffs_base__frame_config__height(this);
 }
 
 inline wuffs_base__flicks  //
-wuffs_base__frame_config::duration() {
+wuffs_base__frame_config::duration() const {
   return wuffs_base__frame_config__duration(this);
 }
 
 inline uint64_t  //
-wuffs_base__frame_config::index() {
+wuffs_base__frame_config::index() const {
   return wuffs_base__frame_config__index(this);
 }
 
 inline uint64_t  //
-wuffs_base__frame_config::io_position() {
+wuffs_base__frame_config::io_position() const {
   return wuffs_base__frame_config__io_position(this);
 }
 
 inline wuffs_base__animation_blend  //
-wuffs_base__frame_config::blend() {
+wuffs_base__frame_config::blend() const {
   return wuffs_base__frame_config__blend(this);
 }
 
 inline wuffs_base__animation_disposal  //
-wuffs_base__frame_config::disposal() {
+wuffs_base__frame_config::disposal() const {
   return wuffs_base__frame_config__disposal(this);
 }
 
+inline wuffs_base__color_u32_argb_premul  //
+wuffs_base__frame_config::background_color() const {
+  return wuffs_base__frame_config__background_color(this);
+}
+
 #endif  // __cplusplus
 
 // --------
@@ -2381,8 +2378,10 @@
 #ifdef __cplusplus
   inline wuffs_base__status set_from_slice(wuffs_base__pixel_config* pixcfg,
                                            wuffs_base__slice_u8 pixbuf_memory);
+  inline wuffs_base__status set_from_table(wuffs_base__pixel_config* pixcfg,
+                                           wuffs_base__table_u8 pixbuf_memory);
   inline wuffs_base__slice_u8 palette();
-  inline wuffs_base__pixel_format pixel_format();
+  inline wuffs_base__pixel_format pixel_format() const;
   inline wuffs_base__table_u8 plane(uint32_t p);
 #endif  // __cplusplus
 
@@ -2412,12 +2411,13 @@
   }
   if (wuffs_base__pixel_format__is_planar(pixcfg->private_impl.pixfmt)) {
     // TODO: support planar pixel formats, concious of pixel subsampling.
-    return wuffs_base__error__bad_argument;
+    return wuffs_base__error__unsupported_option;
   }
   uint32_t bits_per_pixel =
       wuffs_base__pixel_format__bits_per_pixel(pixcfg->private_impl.pixfmt);
   if ((bits_per_pixel == 0) || ((bits_per_pixel % 8) != 0)) {
-    return wuffs_base__error__bad_argument;
+    // TODO: support fraction-of-byte pixels, e.g. 1 bit per pixel?
+    return wuffs_base__error__unsupported_option;
   }
   uint64_t bytes_per_pixel = bits_per_pixel / 8;
 
@@ -2463,6 +2463,38 @@
   return NULL;
 }
 
+static inline wuffs_base__status  //
+wuffs_base__pixel_buffer__set_from_table(wuffs_base__pixel_buffer* b,
+                                         wuffs_base__pixel_config* pixcfg,
+                                         wuffs_base__table_u8 pixbuf_memory) {
+  if (!b) {
+    return wuffs_base__error__bad_receiver;
+  }
+  memset(b, 0, sizeof(*b));
+  if (!pixcfg ||
+      wuffs_base__pixel_format__is_planar(pixcfg->private_impl.pixfmt)) {
+    return wuffs_base__error__bad_argument;
+  }
+  uint32_t bits_per_pixel =
+      wuffs_base__pixel_format__bits_per_pixel(pixcfg->private_impl.pixfmt);
+  if ((bits_per_pixel == 0) || ((bits_per_pixel % 8) != 0)) {
+    // TODO: support fraction-of-byte pixels, e.g. 1 bit per pixel?
+    return wuffs_base__error__unsupported_option;
+  }
+  uint64_t bytes_per_pixel = bits_per_pixel / 8;
+
+  uint64_t width_in_bytes =
+      ((uint64_t)pixcfg->private_impl.width) * bytes_per_pixel;
+  if ((width_in_bytes > pixbuf_memory.width) ||
+      (pixcfg->private_impl.height > pixbuf_memory.height)) {
+    return wuffs_base__error__bad_argument;
+  }
+
+  b->pixcfg = *pixcfg;
+  b->private_impl.planes[0] = pixbuf_memory;
+  return NULL;
+}
+
 // wuffs_base__pixel_buffer__palette returns the palette color data. If
 // non-empty, it will have length 1024.
 static inline wuffs_base__slice_u8  //
@@ -2479,7 +2511,7 @@
 }
 
 static inline wuffs_base__pixel_format  //
-wuffs_base__pixel_buffer__pixel_format(wuffs_base__pixel_buffer* b) {
+wuffs_base__pixel_buffer__pixel_format(const wuffs_base__pixel_buffer* b) {
   if (b) {
     return b->pixcfg.private_impl.pixfmt;
   }
@@ -2508,13 +2540,19 @@
   return wuffs_base__pixel_buffer__set_from_slice(this, pixcfg, pixbuf_memory);
 }
 
+inline wuffs_base__status  //
+wuffs_base__pixel_buffer::set_from_table(wuffs_base__pixel_config* pixcfg,
+                                         wuffs_base__table_u8 pixbuf_memory) {
+  return wuffs_base__pixel_buffer__set_from_table(this, pixcfg, pixbuf_memory);
+}
+
 inline wuffs_base__slice_u8  //
 wuffs_base__pixel_buffer::palette() {
   return wuffs_base__pixel_buffer__palette(this);
 }
 
 inline wuffs_base__pixel_format  //
-wuffs_base__pixel_buffer::pixel_format() {
+wuffs_base__pixel_buffer::pixel_format() const {
   return wuffs_base__pixel_buffer__pixel_format(this);
 }
 
@@ -2556,20 +2594,18 @@
   } private_impl;
 
 #ifdef __cplusplus
-  inline void prepare(wuffs_base__pixel_format dst_format,
-                      wuffs_base__slice_u8 dst_palette,
-                      wuffs_base__pixel_format src_format,
-                      wuffs_base__slice_u8 src_palette);
-  inline uint64_t swizzle_packed(wuffs_base__slice_u8 dst,
-                                 wuffs_base__slice_u8 dst_palette,
-                                 wuffs_base__slice_u8 src);
+  inline wuffs_base__status prepare(wuffs_base__pixel_format dst_format,
+                                    wuffs_base__slice_u8 dst_palette,
+                                    wuffs_base__pixel_format src_format,
+                                    wuffs_base__slice_u8 src_palette);
+  inline uint64_t swizzle_interleaved(wuffs_base__slice_u8 dst,
+                                      wuffs_base__slice_u8 dst_palette,
+                                      wuffs_base__slice_u8 src) const;
 #endif  // __cplusplus
 
 } wuffs_base__pixel_swizzler;
 
-// TODO: should prepare (both the C and C++ methods) return a status?
-
-void  //
+wuffs_base__status  //
 wuffs_base__pixel_swizzler__prepare(wuffs_base__pixel_swizzler* p,
                                     wuffs_base__pixel_format dst_format,
                                     wuffs_base__slice_u8 dst_palette,
@@ -2577,28 +2613,30 @@
                                     wuffs_base__slice_u8 src_palette);
 
 uint64_t  //
-wuffs_base__pixel_swizzler__swizzle_packed(wuffs_base__pixel_swizzler* p,
-                                           wuffs_base__slice_u8 dst,
-                                           wuffs_base__slice_u8 dst_palette,
-                                           wuffs_base__slice_u8 src);
+wuffs_base__pixel_swizzler__swizzle_interleaved(
+    const wuffs_base__pixel_swizzler* p,
+    wuffs_base__slice_u8 dst,
+    wuffs_base__slice_u8 dst_palette,
+    wuffs_base__slice_u8 src);
 
 #ifdef __cplusplus
 
-inline void  //
+inline wuffs_base__status  //
 wuffs_base__pixel_swizzler::prepare(wuffs_base__pixel_format dst_format,
                                     wuffs_base__slice_u8 dst_palette,
                                     wuffs_base__pixel_format src_format,
                                     wuffs_base__slice_u8 src_palette) {
-  wuffs_base__pixel_swizzler__prepare(this, dst_format, dst_palette, src_format,
-                                      src_palette);
+  return wuffs_base__pixel_swizzler__prepare(this, dst_format, dst_palette,
+                                             src_format, src_palette);
 }
 
 uint64_t  //
-wuffs_base__pixel_swizzler::swizzle_packed(wuffs_base__slice_u8 dst,
-                                           wuffs_base__slice_u8 dst_palette,
-                                           wuffs_base__slice_u8 src) {
-  return wuffs_base__pixel_swizzler__swizzle_packed(this, dst, dst_palette,
-                                                    src);
+wuffs_base__pixel_swizzler::swizzle_interleaved(
+    wuffs_base__slice_u8 dst,
+    wuffs_base__slice_u8 dst_palette,
+    wuffs_base__slice_u8 src) const {
+  return wuffs_base__pixel_swizzler__swizzle_interleaved(this, dst, dst_palette,
+                                                         src);
 }
 
 #endif  // __cplusplus
@@ -2901,8 +2939,8 @@
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_deflate__decoder__decode_io_writer(wuffs_deflate__decoder* self,
-                                         wuffs_base__io_writer a_dst,
-                                         wuffs_base__io_reader a_src,
+                                         wuffs_base__io_buffer* a_dst,
+                                         wuffs_base__io_buffer* a_src,
                                          wuffs_base__slice_u8 a_workbuf);
 
 // ---------------- Struct Definitions
@@ -3020,8 +3058,8 @@
   }
 
   inline wuffs_base__status  //
-  decode_io_writer(wuffs_base__io_writer a_dst,
-                   wuffs_base__io_reader a_src,
+  decode_io_writer(wuffs_base__io_buffer* a_dst,
+                   wuffs_base__io_buffer* a_src,
                    wuffs_base__slice_u8 a_workbuf) {
     return wuffs_deflate__decoder__decode_io_writer(this, a_dst, a_src,
                                                     a_workbuf);
@@ -3092,8 +3130,8 @@
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_lzw__decoder__decode_io_writer(wuffs_lzw__decoder* self,
-                                     wuffs_base__io_writer a_dst,
-                                     wuffs_base__io_reader a_src,
+                                     wuffs_base__io_buffer* a_dst,
+                                     wuffs_base__io_buffer* a_src,
                                      wuffs_base__slice_u8 a_workbuf);
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__slice_u8  //
@@ -3190,8 +3228,8 @@
   }
 
   inline wuffs_base__status  //
-  decode_io_writer(wuffs_base__io_writer a_dst,
-                   wuffs_base__io_reader a_src,
+  decode_io_writer(wuffs_base__io_buffer* a_dst,
+                   wuffs_base__io_buffer* a_src,
                    wuffs_base__slice_u8 a_workbuf) {
     return wuffs_lzw__decoder__decode_io_writer(this, a_dst, a_src, a_workbuf);
   }
@@ -3226,9 +3264,11 @@
 
 extern const char* wuffs_gif__error__bad_block;
 extern const char* wuffs_gif__error__bad_extension_label;
+extern const char* wuffs_gif__error__bad_frame_size;
 extern const char* wuffs_gif__error__bad_graphic_control;
 extern const char* wuffs_gif__error__bad_header;
 extern const char* wuffs_gif__error__bad_literal_width;
+extern const char* wuffs_gif__error__bad_palette;
 
 // ---------------- Public Consts
 
@@ -3238,6 +3278,49 @@
     wuffs_gif__decoder_workbuf_len_max_incl_worst_case  //
         WUFFS_BASE__POTENTIALLY_UNUSED = 1;
 
+#define WUFFS_GIF__QUIRK_DELAY_NUM_DECODED_FRAMES 1041635328
+
+static const uint32_t                          //
+    wuffs_gif__quirk_delay_num_decoded_frames  //
+        WUFFS_BASE__POTENTIALLY_UNUSED = 1041635328;
+
+#define WUFFS_GIF__QUIRK_FIRST_FRAME_LOCAL_PALETTE_MEANS_BLACK_BACKGROUND \
+  1041635329
+
+static const uint32_t                                                  //
+    wuffs_gif__quirk_first_frame_local_palette_means_black_background  //
+        WUFFS_BASE__POTENTIALLY_UNUSED = 1041635329;
+
+#define WUFFS_GIF__QUIRK_HONOR_BACKGROUND_COLOR 1041635330
+
+static const uint32_t                        //
+    wuffs_gif__quirk_honor_background_color  //
+        WUFFS_BASE__POTENTIALLY_UNUSED = 1041635330;
+
+#define WUFFS_GIF__QUIRK_IGNORE_TOO_MUCH_PIXEL_DATA 1041635331
+
+static const uint32_t                            //
+    wuffs_gif__quirk_ignore_too_much_pixel_data  //
+        WUFFS_BASE__POTENTIALLY_UNUSED = 1041635331;
+
+#define WUFFS_GIF__QUIRK_IMAGE_BOUNDS_ARE_STRICT 1041635332
+
+static const uint32_t                         //
+    wuffs_gif__quirk_image_bounds_are_strict  //
+        WUFFS_BASE__POTENTIALLY_UNUSED = 1041635332;
+
+#define WUFFS_GIF__QUIRK_REJECT_EMPTY_FRAME 1041635333
+
+static const uint32_t                    //
+    wuffs_gif__quirk_reject_empty_frame  //
+        WUFFS_BASE__POTENTIALLY_UNUSED = 1041635333;
+
+#define WUFFS_GIF__QUIRK_REJECT_EMPTY_PALETTE 1041635334
+
+static const uint32_t                      //
+    wuffs_gif__quirk_reject_empty_palette  //
+        WUFFS_BASE__POTENTIALLY_UNUSED = 1041635334;
+
 // ---------------- Struct Declarations
 
 typedef struct wuffs_gif__decoder__struct wuffs_gif__decoder;
@@ -3261,10 +3344,30 @@
 
 // ---------------- Public Function Prototypes
 
+WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct  //
+wuffs_gif__decoder__set_quirk_enabled(wuffs_gif__decoder* self,
+                                      uint32_t a_quirk,
+                                      bool a_enabled);
+
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_gif__decoder__decode_image_config(wuffs_gif__decoder* self,
                                         wuffs_base__image_config* a_dst,
-                                        wuffs_base__io_reader a_src);
+                                        wuffs_base__io_buffer* a_src);
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct  //
+wuffs_gif__decoder__set_report_metadata(wuffs_gif__decoder* self,
+                                        uint32_t a_fourcc,
+                                        bool a_report);
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
+wuffs_gif__decoder__ack_metadata_chunk(wuffs_gif__decoder* self,
+                                       wuffs_base__io_buffer* a_src);
+
+WUFFS_BASE__MAYBE_STATIC uint32_t  //
+wuffs_gif__decoder__metadata_fourcc(const wuffs_gif__decoder* self);
+
+WUFFS_BASE__MAYBE_STATIC uint64_t  //
+wuffs_gif__decoder__metadata_chunk_length(const wuffs_gif__decoder* self);
 
 WUFFS_BASE__MAYBE_STATIC uint32_t  //
 wuffs_gif__decoder__num_animation_loops(const wuffs_gif__decoder* self);
@@ -3289,12 +3392,12 @@
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_gif__decoder__decode_frame_config(wuffs_gif__decoder* self,
                                         wuffs_base__frame_config* a_dst,
-                                        wuffs_base__io_reader a_src);
+                                        wuffs_base__io_buffer* a_src);
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_gif__decoder__decode_frame(wuffs_gif__decoder* self,
                                  wuffs_base__pixel_buffer* a_dst,
-                                 wuffs_base__io_reader a_src,
+                                 wuffs_base__io_buffer* a_src,
                                  wuffs_base__slice_u8 a_workbuf,
                                  wuffs_base__decode_frame_options* a_opts);
 
@@ -3324,14 +3427,29 @@
     uint32_t f_width;
     uint32_t f_height;
     uint8_t f_call_sequence;
+    bool f_ignore_metadata;
+    bool f_report_metadata_iccp;
+    bool f_report_metadata_xmp;
+    uint32_t f_metadata_fourcc_value;
+    uint64_t f_metadata_chunk_length_value;
+    uint64_t f_metadata_io_position;
+    bool f_quirk_enabled_delay_num_decoded_frames;
+    bool f_quirk_enabled_first_frame_local_palette_means_black_background;
+    bool f_quirk_enabled_honor_background_color;
+    bool f_quirk_enabled_ignore_too_much_pixel_data;
+    bool f_quirk_enabled_image_bounds_are_strict;
+    bool f_quirk_enabled_reject_empty_frame;
+    bool f_quirk_enabled_reject_empty_palette;
+    bool f_delayed_num_decoded_frames;
     bool f_end_of_data;
     bool f_restarted;
     bool f_previous_lzw_decode_ended_abruptly;
-    uint8_t f_which_palette;
+    bool f_has_global_palette;
     uint8_t f_interlace;
     bool f_seen_num_loops;
     uint32_t f_num_loops;
-    bool f_seen_graphic_control;
+    uint32_t f_background_color_u32_argb_premul;
+    uint32_t f_black_color_u32_argb_premul;
     bool f_gc_has_transparent_index;
     uint8_t f_gc_transparent_index;
     uint8_t f_gc_disposal;
@@ -3351,6 +3469,7 @@
     wuffs_base__pixel_swizzler f_swizzler;
 
     uint32_t p_decode_image_config[1];
+    uint32_t p_ack_metadata_chunk[1];
     uint32_t p_decode_frame_config[1];
     uint32_t p_skip_frame[1];
     uint32_t p_decode_frame[1];
@@ -3373,6 +3492,10 @@
     wuffs_lzw__decoder f_lzw;
 
     struct {
+      uint8_t v_blend;
+      uint32_t v_background_color;
+    } s_decode_frame_config[1];
+    struct {
       uint64_t scratch;
     } s_skip_frame[1];
     struct {
@@ -3381,6 +3504,7 @@
     } s_decode_header[1];
     struct {
       uint8_t v_flags;
+      uint8_t v_background_color_index;
       uint32_t v_num_palette_entries;
       uint32_t v_i;
       uint64_t scratch;
@@ -3390,8 +3514,10 @@
     } s_skip_blocks[1];
     struct {
       uint8_t v_block_size;
-      bool v_not_animexts;
-      bool v_not_netscape;
+      bool v_is_animexts;
+      bool v_is_netscape;
+      bool v_is_iccp;
+      bool v_is_xmp;
       uint64_t scratch;
     } s_decode_ae[1];
     struct {
@@ -3401,6 +3527,7 @@
       uint64_t scratch;
     } s_decode_id_part0[1];
     struct {
+      uint8_t v_which_palette;
       uint32_t v_num_palette_entries;
       uint32_t v_i;
       uint64_t scratch;
@@ -3445,12 +3572,37 @@
                                           initialize_flags);
   }
 
+  inline wuffs_base__empty_struct  //
+  set_quirk_enabled(uint32_t a_quirk, bool a_enabled) {
+    return wuffs_gif__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_reader a_src) {
+                      wuffs_base__io_buffer* a_src) {
     return wuffs_gif__decoder__decode_image_config(this, a_dst, a_src);
   }
 
+  inline wuffs_base__empty_struct  //
+  set_report_metadata(uint32_t a_fourcc, bool a_report) {
+    return wuffs_gif__decoder__set_report_metadata(this, a_fourcc, a_report);
+  }
+
+  inline wuffs_base__status  //
+  ack_metadata_chunk(wuffs_base__io_buffer* a_src) {
+    return wuffs_gif__decoder__ack_metadata_chunk(this, a_src);
+  }
+
+  inline uint32_t  //
+  metadata_fourcc() const {
+    return wuffs_gif__decoder__metadata_fourcc(this);
+  }
+
+  inline uint64_t  //
+  metadata_chunk_length() const {
+    return wuffs_gif__decoder__metadata_chunk_length(this);
+  }
+
   inline uint32_t  //
   num_animation_loops() const {
     return wuffs_gif__decoder__num_animation_loops(this);
@@ -3483,13 +3635,13 @@
 
   inline wuffs_base__status  //
   decode_frame_config(wuffs_base__frame_config* a_dst,
-                      wuffs_base__io_reader a_src) {
+                      wuffs_base__io_buffer* a_src) {
     return wuffs_gif__decoder__decode_frame_config(this, a_dst, a_src);
   }
 
   inline wuffs_base__status  //
   decode_frame(wuffs_base__pixel_buffer* a_dst,
-               wuffs_base__io_reader a_src,
+               wuffs_base__io_buffer* a_src,
                wuffs_base__slice_u8 a_workbuf,
                wuffs_base__decode_frame_options* a_opts) {
     return wuffs_gif__decoder__decode_frame(this, a_dst, a_src, a_workbuf,
@@ -3563,8 +3715,8 @@
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_gzip__decoder__decode_io_writer(wuffs_gzip__decoder* self,
-                                      wuffs_base__io_writer a_dst,
-                                      wuffs_base__io_reader a_src,
+                                      wuffs_base__io_buffer* a_dst,
+                                      wuffs_base__io_buffer* a_src,
                                       wuffs_base__slice_u8 a_workbuf);
 
 // ---------------- Struct Definitions
@@ -3651,8 +3803,8 @@
   }
 
   inline wuffs_base__status  //
-  decode_io_writer(wuffs_base__io_writer a_dst,
-                   wuffs_base__io_reader a_src,
+  decode_io_writer(wuffs_base__io_buffer* a_dst,
+                   wuffs_base__io_buffer* a_src,
                    wuffs_base__slice_u8 a_workbuf) {
     return wuffs_gzip__decoder__decode_io_writer(this, a_dst, a_src, a_workbuf);
   }
@@ -3724,8 +3876,8 @@
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_zlib__decoder__decode_io_writer(wuffs_zlib__decoder* self,
-                                      wuffs_base__io_writer a_dst,
-                                      wuffs_base__io_reader a_src,
+                                      wuffs_base__io_buffer* a_dst,
+                                      wuffs_base__io_buffer* a_src,
                                       wuffs_base__slice_u8 a_workbuf);
 
 // ---------------- Struct Definitions
@@ -3809,8 +3961,8 @@
   }
 
   inline wuffs_base__status  //
-  decode_io_writer(wuffs_base__io_writer a_dst,
-                   wuffs_base__io_reader a_src,
+  decode_io_writer(wuffs_base__io_buffer* a_dst,
+                   wuffs_base__io_buffer* a_src,
                    wuffs_base__slice_u8 a_workbuf) {
     return wuffs_zlib__decoder__decode_io_writer(this, a_dst, a_src, a_workbuf);
   }
@@ -4305,65 +4457,52 @@
 
 // ---------------- I/O
 
-static inline bool  //
-wuffs_base__io_buffer__is_valid(wuffs_base__io_buffer buf) {
-  return (buf.data.ptr || (buf.data.len == 0)) &&
-         (buf.data.len >= buf.meta.wi) && (buf.meta.wi >= buf.meta.ri);
-}
-
-// TODO: wuffs_base__io_reader__is_eof is no longer used by Wuffs per se, but
-// it might be handy to programs that use Wuffs. Either delete it, or promote
-// it to the public API.
+// "Null" as in "/dev/null", not as in "nullptr".
 //
-// If making this function public (i.e. moving it to base-header.h), it also
-// needs to allow NULL (i.e. implicit, callee-calculated) mark/limit.
+// TODO: ensure that this is zero-initialized.
+static wuffs_base__io_buffer wuffs_base__global__null_io_buffer;
 
-static inline bool  //
-wuffs_base__io_reader__is_eof(wuffs_base__io_reader o) {
-  wuffs_base__io_buffer* buf = o.private_impl.buf;
-  return buf && buf->meta.closed &&
-         (buf->data.ptr + buf->meta.wi == o.private_impl.limit);
+static inline wuffs_base__io_buffer*  //
+wuffs_base__null_io_reader() {
+  return &wuffs_base__global__null_io_buffer;
 }
 
-static inline bool  //
-wuffs_base__io_reader__is_valid(wuffs_base__io_reader o) {
-  wuffs_base__io_buffer* buf = o.private_impl.buf;
-  // Note: if making this function public (i.e. moving it to base-header.h), it
-  // also needs to allow NULL (i.e. implicit, callee-calculated) mark/limit.
-  return buf ? ((buf->data.ptr <= o.private_impl.mark) &&
-                (o.private_impl.mark <= o.private_impl.limit) &&
-                (o.private_impl.limit <= buf->data.ptr + buf->data.len))
-             : ((o.private_impl.mark == NULL) &&
-                (o.private_impl.limit == NULL));
+static inline wuffs_base__io_buffer*  //
+wuffs_base__null_io_writer() {
+  return &wuffs_base__global__null_io_buffer;
 }
 
-static inline bool  //
-wuffs_base__io_writer__is_valid(wuffs_base__io_writer o) {
-  wuffs_base__io_buffer* buf = o.private_impl.buf;
-  // Note: if making this function public (i.e. moving it to base-header.h), it
-  // also needs to allow NULL (i.e. implicit, callee-calculated) mark/limit.
-  return buf ? ((buf->data.ptr <= o.private_impl.mark) &&
-                (o.private_impl.mark <= o.private_impl.limit) &&
-                (o.private_impl.limit <= buf->data.ptr + buf->data.len))
-             : ((o.private_impl.mark == NULL) &&
-                (o.private_impl.limit == NULL));
+static inline uint64_t  //
+wuffs_base__io__count_since(uint64_t mark, uint64_t index) {
+  if (index >= mark) {
+    return index - mark;
+  }
+  return 0;
+}
+
+static inline wuffs_base__slice_u8  //
+wuffs_base__io__since(uint64_t mark, uint64_t index, uint8_t* ptr) {
+  if (index >= mark) {
+    return wuffs_base__make_slice_u8(ptr + mark, index - mark);
+  }
+  return wuffs_base__make_slice_u8(NULL, 0);
 }
 
 static inline uint32_t  //
 wuffs_base__io_writer__copy_n_from_history(uint8_t** ptr_iop_w,
-                                           uint8_t* io0_w,
                                            uint8_t* io1_w,
+                                           uint8_t* io2_w,
                                            uint32_t length,
                                            uint32_t distance) {
   if (!distance) {
     return 0;
   }
   uint8_t* p = *ptr_iop_w;
-  if ((size_t)(p - io0_w) < (size_t)(distance)) {
+  if ((size_t)(p - io1_w) < (size_t)(distance)) {
     return 0;
   }
   uint8_t* q = p - distance;
-  size_t n = (size_t)(io1_w - p);
+  size_t n = (size_t)(io2_w - p);
   if ((size_t)(length) > n) {
     length = (uint32_t)(n);
   } else {
@@ -4395,12 +4534,12 @@
 // wuffs_base__io_writer__copy_n_from_history function above, but has stronger
 // pre-conditions. The caller needs to prove that:
 //  - distance >  0
-//  - distance <= (*ptr_iop_w - io0_w)
-//  - length   <= (io1_w      - *ptr_iop_w)
+//  - distance <= (*ptr_iop_w - io1_w)
+//  - length   <= (io2_w      - *ptr_iop_w)
 static inline uint32_t  //
 wuffs_base__io_writer__copy_n_from_history_fast(uint8_t** ptr_iop_w,
-                                                uint8_t* io0_w,
                                                 uint8_t* io1_w,
+                                                uint8_t* io2_w,
                                                 uint32_t length,
                                                 uint32_t distance) {
   uint8_t* p = *ptr_iop_w;
@@ -4420,18 +4559,18 @@
 
 static inline uint32_t  //
 wuffs_base__io_writer__copy_n_from_reader(uint8_t** ptr_iop_w,
-                                          uint8_t* io1_w,
+                                          uint8_t* io2_w,
                                           uint32_t length,
                                           uint8_t** ptr_iop_r,
-                                          uint8_t* io1_r) {
+                                          uint8_t* io2_r) {
   uint8_t* iop_w = *ptr_iop_w;
   size_t n = length;
-  if (n > ((size_t)(io1_w - iop_w))) {
-    n = (size_t)(io1_w - iop_w);
+  if (n > ((size_t)(io2_w - iop_w))) {
+    n = (size_t)(io2_w - iop_w);
   }
   uint8_t* iop_r = *ptr_iop_r;
-  if (n > ((size_t)(io1_r - iop_r))) {
-    n = (size_t)(io1_r - iop_r);
+  if (n > ((size_t)(io2_r - iop_r))) {
+    n = (size_t)(io2_r - iop_r);
   }
   if (n > 0) {
     memmove(iop_w, iop_r, n);
@@ -4443,12 +4582,12 @@
 
 static inline uint64_t  //
 wuffs_base__io_writer__copy_from_slice(uint8_t** ptr_iop_w,
-                                       uint8_t* io1_w,
+                                       uint8_t* io2_w,
                                        wuffs_base__slice_u8 src) {
   uint8_t* iop_w = *ptr_iop_w;
   size_t n = src.len;
-  if (n > ((size_t)(io1_w - iop_w))) {
-    n = (size_t)(io1_w - iop_w);
+  if (n > ((size_t)(io2_w - iop_w))) {
+    n = (size_t)(io2_w - iop_w);
   }
   if (n > 0) {
     memmove(iop_w, src.ptr, n);
@@ -4459,7 +4598,7 @@
 
 static inline uint32_t  //
 wuffs_base__io_writer__copy_n_from_slice(uint8_t** ptr_iop_w,
-                                         uint8_t* io1_w,
+                                         uint8_t* io2_w,
                                          uint32_t length,
                                          wuffs_base__slice_u8 src) {
   uint8_t* iop_w = *ptr_iop_w;
@@ -4467,8 +4606,8 @@
   if (n > length) {
     n = length;
   }
-  if (n > ((size_t)(io1_w - iop_w))) {
-    n = (size_t)(io1_w - iop_w);
+  if (n > ((size_t)(io2_w - iop_w))) {
+    n = (size_t)(io2_w - iop_w);
   }
   if (n > 0) {
     memmove(iop_w, src.ptr, n);
@@ -4477,11 +4616,12 @@
   return (uint32_t)(n);
 }
 
-static inline wuffs_base__empty_struct  //
-wuffs_base__io_reader__set(wuffs_base__io_reader* o,
-                           wuffs_base__io_buffer* b,
+static inline wuffs_base__io_buffer*  //
+wuffs_base__io_reader__set(wuffs_base__io_buffer* b,
                            uint8_t** ptr_iop_r,
+                           uint8_t** ptr_io0_r,
                            uint8_t** ptr_io1_r,
+                           uint8_t** ptr_io2_r,
                            wuffs_base__slice_u8 data) {
   b->data = data;
   b->meta.wi = data.len;
@@ -4489,42 +4629,17 @@
   b->meta.pos = 0;
   b->meta.closed = false;
 
-  o->private_impl.buf = b;
-  o->private_impl.mark = data.ptr;
-  o->private_impl.limit = data.ptr + data.len;
   *ptr_iop_r = data.ptr;
-  *ptr_io1_r = data.ptr + data.len;
+  *ptr_io0_r = data.ptr;
+  *ptr_io1_r = data.ptr;
+  *ptr_io2_r = data.ptr + data.len;
 
-  wuffs_base__empty_struct ret;
-  ret.private_impl = 0;
-  return ret;
-}
-
-static inline wuffs_base__empty_struct  //
-wuffs_base__io_reader__set_limit(wuffs_base__io_reader* o,
-                                 uint8_t* iop_r,
-                                 uint64_t limit) {
-  if (o && (((size_t)(o->private_impl.limit - iop_r)) > limit)) {
-    o->private_impl.limit = iop_r + limit;
-  }
-
-  wuffs_base__empty_struct ret;
-  ret.private_impl = 0;
-  return ret;
-}
-
-static inline wuffs_base__empty_struct  //
-wuffs_base__io_reader__set_mark(wuffs_base__io_reader* o, uint8_t* mark) {
-  o->private_impl.mark = mark;
-
-  wuffs_base__empty_struct ret;
-  ret.private_impl = 0;
-  return ret;
+  return b;
 }
 
 static inline wuffs_base__slice_u8  //
-wuffs_base__io_reader__take(uint8_t** ptr_iop_r, uint8_t* io1_r, uint64_t n) {
-  if (n <= ((size_t)(io1_r - *ptr_iop_r))) {
+wuffs_base__io_reader__take(uint8_t** ptr_iop_r, uint8_t* io2_r, uint64_t n) {
+  if (n <= ((size_t)(io2_r - *ptr_iop_r))) {
     uint8_t* p = *ptr_iop_r;
     *ptr_iop_r += n;
     return wuffs_base__make_slice_u8(p, n);
@@ -4532,11 +4647,12 @@
   return wuffs_base__make_slice_u8(NULL, 0);
 }
 
-static inline wuffs_base__empty_struct  //
-wuffs_base__io_writer__set(wuffs_base__io_writer* o,
-                           wuffs_base__io_buffer* b,
+static inline wuffs_base__io_buffer*  //
+wuffs_base__io_writer__set(wuffs_base__io_buffer* b,
                            uint8_t** ptr_iop_w,
+                           uint8_t** ptr_io0_w,
                            uint8_t** ptr_io1_w,
+                           uint8_t** ptr_io2_w,
                            wuffs_base__slice_u8 data) {
   b->data = data;
   b->meta.wi = 0;
@@ -4544,24 +4660,12 @@
   b->meta.pos = 0;
   b->meta.closed = false;
 
-  o->private_impl.buf = b;
-  o->private_impl.mark = data.ptr;
-  o->private_impl.limit = data.ptr + data.len;
   *ptr_iop_w = data.ptr;
-  *ptr_io1_w = data.ptr + data.len;
+  *ptr_io0_w = data.ptr;
+  *ptr_io1_w = data.ptr;
+  *ptr_io2_w = data.ptr + data.len;
 
-  wuffs_base__empty_struct ret;
-  ret.private_impl = 0;
-  return ret;
-}
-
-static inline wuffs_base__empty_struct  //
-wuffs_base__io_writer__set_mark(wuffs_base__io_writer* o, uint8_t* mark) {
-  o->private_impl.mark = mark;
-
-  wuffs_base__empty_struct ret;
-  ret.private_impl = 0;
-  return ret;
+  return b;
 }
 
   // ---------------- I/O (Utility)
@@ -4619,8 +4723,10 @@
 };
 
 const char* wuffs_base__warning__end_of_data = "@base: end of data";
+const char* wuffs_base__warning__metadata_reported = "@base: metadata reported";
 const char* wuffs_base__suspension__short_read = "$base: short read";
 const char* wuffs_base__suspension__short_write = "$base: short write";
+const char* wuffs_base__error__bad_i_o_position = "#base: bad I/O position";
 const char* wuffs_base__error__bad_argument_length_too_short =
     "#base: bad argument (length too short)";
 const char* wuffs_base__error__bad_argument = "#base: bad argument";
@@ -4642,6 +4748,7 @@
 const char* wuffs_base__error__interleaved_coroutine_calls =
     "#base: interleaved coroutine calls";
 const char* wuffs_base__error__not_enough_data = "#base: not enough data";
+const char* wuffs_base__error__unsupported_option = "#base: unsupported option";
 const char* wuffs_base__error__too_much_data = "#base: too much data";
 
 // ---------------- Images
@@ -4659,6 +4766,61 @@
 }
 
 static uint64_t  //
+wuffs_base__pixel_swizzler__copy_3_1(wuffs_base__slice_u8 dst,
+                                     wuffs_base__slice_u8 dst_palette,
+                                     wuffs_base__slice_u8 src) {
+  if (dst_palette.len != 1024) {
+    return 0;
+  }
+  size_t dst_len3 = dst.len / 3;
+  size_t len = dst_len3 < src.len ? dst_len3 : src.len;
+  uint8_t* d = dst.ptr;
+  uint8_t* s = src.ptr;
+  size_t n = len;
+
+  // N is the loop unroll count.
+  const int N = 4;
+
+  // The comparison in the while condition is ">", not ">=", because with ">=",
+  // the last 4-byte store could write past the end of the dst slice.
+  //
+  // Each 4-byte store writes one too many bytes, but a subsequent store will
+  // overwrite that with the correct byte. There is always another store,
+  // whether a 4-byte store in this loop or a 1-byte store in the next loop.
+  while (n > N) {
+    wuffs_base__store_u32le(
+        d + (0 * 3),
+        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[0]) * 4)));
+    wuffs_base__store_u32le(
+        d + (1 * 3),
+        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[1]) * 4)));
+    wuffs_base__store_u32le(
+        d + (2 * 3),
+        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[2]) * 4)));
+    wuffs_base__store_u32le(
+        d + (3 * 3),
+        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[3]) * 4)));
+
+    s += 1 * N;
+    d += 3 * N;
+    n -= (size_t)(1 * N);
+  }
+
+  while (n >= 1) {
+    uint32_t color =
+        wuffs_base__load_u32le(dst_palette.ptr + ((uint32_t)(s[0]) * 4));
+    d[0] = (uint8_t)(color >> 0);
+    d[1] = (uint8_t)(color >> 8);
+    d[2] = (uint8_t)(color >> 16);
+
+    s += 1 * 1;
+    d += 3 * 1;
+    n -= (size_t)(1 * 1);
+  }
+
+  return len;
+}
+static uint64_t  //
 wuffs_base__pixel_swizzler__copy_4_1(wuffs_base__slice_u8 dst,
                                      wuffs_base__slice_u8 dst_palette,
                                      wuffs_base__slice_u8 src) {
@@ -4669,8 +4831,9 @@
   size_t len = dst_len4 < src.len ? dst_len4 : src.len;
   uint8_t* d = dst.ptr;
   uint8_t* s = src.ptr;
-
   size_t n = len;
+
+  // N is the loop unroll count.
   const int N = 4;
 
   while (n >= N) {
@@ -4728,14 +4891,14 @@
   return len4 * 4;
 }
 
-void  //
+wuffs_base__status  //
 wuffs_base__pixel_swizzler__prepare(wuffs_base__pixel_swizzler* p,
                                     wuffs_base__pixel_format dst_format,
                                     wuffs_base__slice_u8 dst_palette,
                                     wuffs_base__pixel_format src_format,
                                     wuffs_base__slice_u8 src_palette) {
   if (!p) {
-    return;
+    return wuffs_base__error__bad_receiver;
   }
 
   // TODO: support many more formats.
@@ -4755,6 +4918,13 @@
           }
           func = wuffs_base__pixel_swizzler__copy_1_1;
           break;
+        case WUFFS_BASE__PIXEL_FORMAT__BGR:
+          if (wuffs_base__slice_u8__copy_from_slice(dst_palette, src_palette) !=
+              1024) {
+            break;
+          }
+          func = wuffs_base__pixel_swizzler__copy_3_1;
+          break;
         case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL:
         case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL:
         case WUFFS_BASE__PIXEL_FORMAT__BGRA_BINARY:
@@ -4764,6 +4934,13 @@
           }
           func = wuffs_base__pixel_swizzler__copy_4_1;
           break;
+        case WUFFS_BASE__PIXEL_FORMAT__RGB:
+          if (wuffs_base__pixel_swizzler__swap_rgbx_bgrx(dst_palette,
+                                                         src_palette) != 1024) {
+            break;
+          }
+          func = wuffs_base__pixel_swizzler__copy_3_1;
+          break;
         case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL:
         case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL:
         case WUFFS_BASE__PIXEL_FORMAT__RGBA_BINARY:
@@ -4783,13 +4960,15 @@
   }
 
   p->private_impl.func = func;
+  return func ? NULL : wuffs_base__error__unsupported_option;
 }
 
 uint64_t  //
-wuffs_base__pixel_swizzler__swizzle_packed(wuffs_base__pixel_swizzler* p,
-                                           wuffs_base__slice_u8 dst,
-                                           wuffs_base__slice_u8 dst_palette,
-                                           wuffs_base__slice_u8 src) {
+wuffs_base__pixel_swizzler__swizzle_interleaved(
+    const wuffs_base__pixel_swizzler* p,
+    wuffs_base__slice_u8 dst,
+    wuffs_base__slice_u8 dst_palette,
+    wuffs_base__slice_u8 src) {
   if (p && p->private_impl.func) {
     return (*(p->private_impl.func))(dst, dst_palette, src);
   }
@@ -6092,20 +6271,20 @@
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__decode_blocks(wuffs_deflate__decoder* self,
-                                      wuffs_base__io_writer a_dst,
-                                      wuffs_base__io_reader a_src);
+                                      wuffs_base__io_buffer* a_dst,
+                                      wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__decode_uncompressed(wuffs_deflate__decoder* self,
-                                            wuffs_base__io_writer a_dst,
-                                            wuffs_base__io_reader a_src);
+                                            wuffs_base__io_buffer* a_dst,
+                                            wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__init_fixed_huffman(wuffs_deflate__decoder* self);
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__init_dynamic_huffman(wuffs_deflate__decoder* self,
-                                             wuffs_base__io_reader a_src);
+                                             wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__init_huff(wuffs_deflate__decoder* self,
@@ -6116,13 +6295,13 @@
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__decode_huffman_fast(wuffs_deflate__decoder* self,
-                                            wuffs_base__io_writer a_dst,
-                                            wuffs_base__io_reader a_src);
+                                            wuffs_base__io_buffer* a_dst,
+                                            wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__decode_huffman_slow(wuffs_deflate__decoder* self,
-                                            wuffs_base__io_writer a_dst,
-                                            wuffs_base__io_reader a_src);
+                                            wuffs_base__io_buffer* a_dst,
+                                            wuffs_base__io_buffer* a_src);
 
 // ---------------- Initializer Implementations
 
@@ -6197,8 +6376,8 @@
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_deflate__decoder__decode_io_writer(wuffs_deflate__decoder* self,
-                                         wuffs_base__io_writer a_dst,
-                                         wuffs_base__io_reader a_src,
+                                         wuffs_base__io_buffer* a_dst,
+                                         wuffs_base__io_buffer* a_src,
                                          wuffs_base__slice_u8 a_workbuf) {
   if (!self) {
     return wuffs_base__error__bad_receiver;
@@ -6208,6 +6387,10 @@
                ? 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__error__bad_argument;
+  }
   if ((self->private_impl.active_coroutine != 0) &&
       (self->private_impl.active_coroutine != 1)) {
     self->private_impl.magic = WUFFS_BASE__DISABLED;
@@ -6216,6 +6399,7 @@
   self->private_impl.active_coroutine = 0;
   wuffs_base__status status = NULL;
 
+  uint64_t v_mark = 0;
   wuffs_base__status v_status = NULL;
   wuffs_base__slice_u8 v_written = {0};
   uint64_t v_n_copied = 0;
@@ -6224,19 +6408,15 @@
   uint8_t* iop_a_dst = NULL;
   uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_dst.private_impl.buf) {
-    iop_a_dst =
-        a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->meta.wi;
-    if (!a_dst.private_impl.mark) {
-      a_dst.private_impl.mark = iop_a_dst;
-      a_dst.private_impl.limit =
-          a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->data.len;
+  uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_dst) {
+    io0_a_dst = a_dst->data.ptr;
+    io1_a_dst = io0_a_dst + a_dst->meta.wi;
+    iop_a_dst = io1_a_dst;
+    io2_a_dst = io0_a_dst + a_dst->data.len;
+    if (a_dst->meta.closed) {
+      io2_a_dst = iop_a_dst;
     }
-    if (a_dst.private_impl.buf->meta.closed) {
-      a_dst.private_impl.limit = iop_a_dst;
-    }
-    io0_a_dst = a_dst.private_impl.mark;
-    io1_a_dst = a_dst.private_impl.limit;
   }
 
   uint32_t coro_susp_point = self->private_impl.p_decode_io_writer[0];
@@ -6246,17 +6426,15 @@
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
     while (true) {
-      wuffs_base__io_writer__set_mark(&a_dst, iop_a_dst);
+      v_mark = ((uint64_t)(iop_a_dst - io0_a_dst));
       {
-        if (a_dst.private_impl.buf) {
-          a_dst.private_impl.buf->meta.wi =
-              ((size_t)(iop_a_dst - a_dst.private_impl.buf->data.ptr));
+        if (a_dst) {
+          a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
         }
         wuffs_base__status t_0 =
             wuffs_deflate__decoder__decode_blocks(self, a_dst, a_src);
-        if (a_dst.private_impl.buf) {
-          iop_a_dst = a_dst.private_impl.buf->data.ptr +
-                      a_dst.private_impl.buf->meta.wi;
+        if (a_dst) {
+          iop_a_dst = a_dst->data.ptr + a_dst->meta.wi;
         }
         v_status = t_0;
       }
@@ -6270,9 +6448,8 @@
         }
         goto ok;
       }
-      v_written = wuffs_base__make_slice_u8(
-          a_dst.private_impl.mark,
-          (size_t)(iop_a_dst - a_dst.private_impl.mark));
+      v_written = wuffs_base__io__since(
+          v_mark, ((uint64_t)(iop_a_dst - io0_a_dst)), io0_a_dst);
       if (((uint64_t)(v_written.len)) >= 32768) {
         v_written = wuffs_base__slice_u8__suffix(v_written, 32768);
         wuffs_base__slice_u8__copy_from_slice(
@@ -6314,14 +6491,15 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_io_writer[0] = coro_susp_point;
-  self->private_impl.active_coroutine = 1;
+  self->private_impl.p_decode_io_writer[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
+  self->private_impl.active_coroutine =
+      wuffs_base__status__is_suspension(status) ? 1 : 0;
 
   goto exit;
 exit:
-  if (a_dst.private_impl.buf) {
-    a_dst.private_impl.buf->meta.wi =
-        ((size_t)(iop_a_dst - a_dst.private_impl.buf->data.ptr));
+  if (a_dst) {
+    a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
   }
 
   if (wuffs_base__status__is_error(status)) {
@@ -6334,8 +6512,8 @@
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__decode_blocks(wuffs_deflate__decoder* self,
-                                      wuffs_base__io_writer a_dst,
-                                      wuffs_base__io_reader a_src) {
+                                      wuffs_base__io_buffer* a_dst,
+                                      wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint32_t v_final = 0;
@@ -6346,16 +6524,12 @@
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_blocks[0];
@@ -6370,7 +6544,7 @@
       while (self->private_impl.f_n_bits < 3) {
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -6385,16 +6559,14 @@
       self->private_impl.f_bits >>= 3;
       self->private_impl.f_n_bits -= 3;
       if (v_type == 0) {
-        if (a_src.private_impl.buf) {
-          a_src.private_impl.buf->meta.ri =
-              ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
         }
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
         status =
             wuffs_deflate__decoder__decode_uncompressed(self, a_dst, a_src);
-        if (a_src.private_impl.buf) {
-          iop_a_src = a_src.private_impl.buf->data.ptr +
-                      a_src.private_impl.buf->meta.ri;
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
         }
         if (status) {
           goto suspend;
@@ -6413,15 +6585,13 @@
           goto ok;
         }
       } else if (v_type == 2) {
-        if (a_src.private_impl.buf) {
-          a_src.private_impl.buf->meta.ri =
-              ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
         }
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
         status = wuffs_deflate__decoder__init_dynamic_huffman(self, a_src);
-        if (a_src.private_impl.buf) {
-          iop_a_src = a_src.private_impl.buf->data.ptr +
-                      a_src.private_impl.buf->meta.ri;
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
         }
         if (status) {
           goto suspend;
@@ -6432,15 +6602,13 @@
       }
       self->private_impl.f_end_of_block = false;
       while (true) {
-        if (a_src.private_impl.buf) {
-          a_src.private_impl.buf->meta.ri =
-              ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
         }
         v_status =
             wuffs_deflate__decoder__decode_huffman_fast(self, a_dst, a_src);
-        if (a_src.private_impl.buf) {
-          iop_a_src = a_src.private_impl.buf->data.ptr +
-                      a_src.private_impl.buf->meta.ri;
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
         }
         if (wuffs_base__status__is_error(v_status)) {
           status = v_status;
@@ -6449,16 +6617,14 @@
         if (self->private_impl.f_end_of_block) {
           goto label_0_continue;
         }
-        if (a_src.private_impl.buf) {
-          a_src.private_impl.buf->meta.ri =
-              ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
         }
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
         status =
             wuffs_deflate__decoder__decode_huffman_slow(self, a_dst, a_src);
-        if (a_src.private_impl.buf) {
-          iop_a_src = a_src.private_impl.buf->data.ptr +
-                      a_src.private_impl.buf->meta.ri;
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
         }
         if (status) {
           goto suspend;
@@ -6477,14 +6643,14 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_blocks[0] = coro_susp_point;
+  self->private_impl.p_decode_blocks[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
   self->private_data.s_decode_blocks[0].v_final = v_final;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -6494,8 +6660,8 @@
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__decode_uncompressed(wuffs_deflate__decoder* self,
-                                            wuffs_base__io_writer a_dst,
-                                            wuffs_base__io_reader a_src) {
+                                            wuffs_base__io_buffer* a_dst,
+                                            wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint32_t v_length = 0;
@@ -6504,33 +6670,25 @@
   uint8_t* iop_a_dst = NULL;
   uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_dst.private_impl.buf) {
-    iop_a_dst =
-        a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->meta.wi;
-    if (!a_dst.private_impl.mark) {
-      a_dst.private_impl.mark = iop_a_dst;
-      a_dst.private_impl.limit =
-          a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->data.len;
+  uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_dst) {
+    io0_a_dst = a_dst->data.ptr;
+    io1_a_dst = io0_a_dst + a_dst->meta.wi;
+    iop_a_dst = io1_a_dst;
+    io2_a_dst = io0_a_dst + a_dst->data.len;
+    if (a_dst->meta.closed) {
+      io2_a_dst = iop_a_dst;
     }
-    if (a_dst.private_impl.buf->meta.closed) {
-      a_dst.private_impl.limit = iop_a_dst;
-    }
-    io0_a_dst = a_dst.private_impl.mark;
-    io1_a_dst = a_dst.private_impl.limit;
   }
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_uncompressed[0];
@@ -6551,14 +6709,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
       uint32_t t_0;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 4)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) {
         t_0 = wuffs_base__load_u32le(iop_a_src);
         iop_a_src += 4;
       } else {
         self->private_data.s_decode_uncompressed[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -6585,13 +6743,13 @@
     v_length = ((v_length)&0xFFFF);
     while (true) {
       v_n_copied = wuffs_base__io_writer__copy_n_from_reader(
-          &iop_a_dst, io1_a_dst, v_length, &iop_a_src, io1_a_src);
+          &iop_a_dst, io2_a_dst, v_length, &iop_a_src, io2_a_src);
       if (v_length <= v_n_copied) {
         status = NULL;
         goto ok;
       }
       v_length -= v_n_copied;
-      if (((uint64_t)(io1_a_dst - iop_a_dst)) == 0) {
+      if (((uint64_t)(io2_a_dst - iop_a_dst)) == 0) {
         status = wuffs_base__suspension__short_write;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(3);
       } else {
@@ -6608,18 +6766,17 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_uncompressed[0] = coro_susp_point;
+  self->private_impl.p_decode_uncompressed[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
   self->private_data.s_decode_uncompressed[0].v_length = v_length;
 
   goto exit;
 exit:
-  if (a_dst.private_impl.buf) {
-    a_dst.private_impl.buf->meta.wi =
-        ((size_t)(iop_a_dst - a_dst.private_impl.buf->data.ptr));
+  if (a_dst) {
+    a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
   }
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -6667,7 +6824,7 @@
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__init_dynamic_huffman(wuffs_deflate__decoder* self,
-                                             wuffs_base__io_reader a_src) {
+                                             wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint32_t v_bits = 0;
@@ -6691,16 +6848,12 @@
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_init_dynamic_huffman[0];
@@ -6726,7 +6879,7 @@
     while (v_n_bits < 14) {
       {
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-        if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+        if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
           status = wuffs_base__suspension__short_read;
           goto suspend;
         }
@@ -6756,7 +6909,7 @@
       while (v_n_bits < 3) {
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -6795,7 +6948,7 @@
         }
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -6844,7 +6997,7 @@
       while (v_n_bits < v_n_extra_bits) {
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -6897,7 +7050,8 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_init_dynamic_huffman[0] = coro_susp_point;
+  self->private_impl.p_init_dynamic_huffman[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
   self->private_data.s_init_dynamic_huffman[0].v_bits = v_bits;
   self->private_data.s_init_dynamic_huffman[0].v_n_bits = v_n_bits;
   self->private_data.s_init_dynamic_huffman[0].v_n_lit = v_n_lit;
@@ -6912,9 +7066,8 @@
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -7179,8 +7332,8 @@
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__decode_huffman_fast(wuffs_deflate__decoder* self,
-                                            wuffs_base__io_writer a_dst,
-                                            wuffs_base__io_reader a_src) {
+                                            wuffs_base__io_buffer* a_dst,
+                                            wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint32_t v_bits = 0;
@@ -7200,33 +7353,25 @@
   uint8_t* iop_a_dst = NULL;
   uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_dst.private_impl.buf) {
-    iop_a_dst =
-        a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->meta.wi;
-    if (!a_dst.private_impl.mark) {
-      a_dst.private_impl.mark = iop_a_dst;
-      a_dst.private_impl.limit =
-          a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->data.len;
+  uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_dst) {
+    io0_a_dst = a_dst->data.ptr;
+    io1_a_dst = io0_a_dst + a_dst->meta.wi;
+    iop_a_dst = io1_a_dst;
+    io2_a_dst = io0_a_dst + a_dst->data.len;
+    if (a_dst->meta.closed) {
+      io2_a_dst = iop_a_dst;
     }
-    if (a_dst.private_impl.buf->meta.closed) {
-      a_dst.private_impl.limit = iop_a_dst;
-    }
-    io0_a_dst = a_dst.private_impl.mark;
-    io1_a_dst = a_dst.private_impl.limit;
   }
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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;
   }
 
   if ((self->private_impl.f_n_bits >= 8) ||
@@ -7239,8 +7384,8 @@
   v_lmask = ((((uint32_t)(1)) << self->private_impl.f_n_huffs_bits[0]) - 1);
   v_dmask = ((((uint32_t)(1)) << self->private_impl.f_n_huffs_bits[1]) - 1);
 label_0_continue:;
-  while ((((uint64_t)(io1_a_dst - iop_a_dst)) >= 258) &&
-         (((uint64_t)(io1_a_src - iop_a_src)) >= 12)) {
+  while ((((uint64_t)(io2_a_dst - iop_a_dst)) >= 258) &&
+         (((uint64_t)(io2_a_src - iop_a_src)) >= 12)) {
     if (v_n_bits < 15) {
       v_bits |= (((uint32_t)(wuffs_base__load_u8be(iop_a_src))) << v_n_bits);
       (iop_a_src += 1, wuffs_base__make_empty_struct());
@@ -7392,11 +7537,10 @@
     v_n_copied = 0;
     while (true) {
       if (((uint64_t)((v_dist_minus_1 + 1))) >
-          ((uint64_t)(iop_a_dst - a_dst.private_impl.mark))) {
+          ((uint64_t)(iop_a_dst - a_dst->data.ptr))) {
         v_hlen = 0;
-        v_hdist =
-            ((uint32_t)((((uint64_t)((v_dist_minus_1 + 1))) -
-                         ((uint64_t)(iop_a_dst - a_dst.private_impl.mark)))));
+        v_hdist = ((uint32_t)((((uint64_t)((v_dist_minus_1 + 1))) -
+                               ((uint64_t)(iop_a_dst - a_dst->data.ptr)))));
         if (v_length > v_hdist) {
           v_length -= v_hdist;
           v_hlen = v_hdist;
@@ -7411,7 +7555,7 @@
         v_hdist = (self->private_impl.f_history_index - v_hdist);
         while (true) {
           v_n_copied = wuffs_base__io_writer__copy_n_from_slice(
-              &iop_a_dst, io1_a_dst, v_hlen,
+              &iop_a_dst, io2_a_dst, v_hlen,
               wuffs_base__slice_u8__subslice_i(
                   wuffs_base__make_slice_u8(self->private_data.f_history,
                                             32768),
@@ -7421,7 +7565,7 @@
           }
           v_hlen -= v_n_copied;
           wuffs_base__io_writer__copy_n_from_slice(
-              &iop_a_dst, io1_a_dst, v_hlen,
+              &iop_a_dst, io2_a_dst, v_hlen,
               wuffs_base__make_slice_u8(self->private_data.f_history, 32768));
           goto label_1_break;
         }
@@ -7430,13 +7574,13 @@
           goto label_0_continue;
         }
         if (((uint64_t)((v_dist_minus_1 + 1))) >
-            ((uint64_t)(iop_a_dst - a_dst.private_impl.mark))) {
+            ((uint64_t)(iop_a_dst - a_dst->data.ptr))) {
           status = wuffs_deflate__error__internal_error_inconsistent_distance;
           goto exit;
         }
       }
       wuffs_base__io_writer__copy_n_from_history_fast(
-          &iop_a_dst, a_dst.private_impl.mark, io1_a_dst, v_length,
+          &iop_a_dst, a_dst->data.ptr, io2_a_dst, v_length,
           (v_dist_minus_1 + 1));
       goto label_2_break;
     }
@@ -7445,7 +7589,7 @@
 label_0_break:;
   while (v_n_bits >= 8) {
     v_n_bits -= 8;
-    if (iop_a_src > io0_a_src) {
+    if (iop_a_src > io1_a_src) {
       (iop_a_src--, wuffs_base__make_empty_struct());
     } else {
       status = wuffs_deflate__error__internal_error_inconsistent_i_o;
@@ -7461,13 +7605,11 @@
   }
   goto exit;
 exit:
-  if (a_dst.private_impl.buf) {
-    a_dst.private_impl.buf->meta.wi =
-        ((size_t)(iop_a_dst - a_dst.private_impl.buf->data.ptr));
+  if (a_dst) {
+    a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
   }
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -7477,8 +7619,8 @@
 
 static wuffs_base__status  //
 wuffs_deflate__decoder__decode_huffman_slow(wuffs_deflate__decoder* self,
-                                            wuffs_base__io_writer a_dst,
-                                            wuffs_base__io_reader a_src) {
+                                            wuffs_base__io_buffer* a_dst,
+                                            wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint32_t v_bits = 0;
@@ -7504,33 +7646,25 @@
   uint8_t* iop_a_dst = NULL;
   uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_dst.private_impl.buf) {
-    iop_a_dst =
-        a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->meta.wi;
-    if (!a_dst.private_impl.mark) {
-      a_dst.private_impl.mark = iop_a_dst;
-      a_dst.private_impl.limit =
-          a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->data.len;
+  uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_dst) {
+    io0_a_dst = a_dst->data.ptr;
+    io1_a_dst = io0_a_dst + a_dst->meta.wi;
+    iop_a_dst = io1_a_dst;
+    io2_a_dst = io0_a_dst + a_dst->data.len;
+    if (a_dst->meta.closed) {
+      io2_a_dst = iop_a_dst;
     }
-    if (a_dst.private_impl.buf->meta.closed) {
-      a_dst.private_impl.limit = iop_a_dst;
-    }
-    io0_a_dst = a_dst.private_impl.mark;
-    io1_a_dst = a_dst.private_impl.limit;
   }
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_huffman_slow[0];
@@ -7574,7 +7708,7 @@
         }
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -7587,7 +7721,7 @@
     label_1_break:;
       if ((v_table_entry >> 31) != 0) {
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
-        if (iop_a_dst == io1_a_dst) {
+        if (iop_a_dst == io2_a_dst) {
           status = wuffs_base__suspension__short_write;
           goto suspend;
         }
@@ -7612,7 +7746,7 @@
           }
           {
             WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
-            if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+            if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
               status = wuffs_base__suspension__short_read;
               goto suspend;
             }
@@ -7625,7 +7759,7 @@
       label_2_break:;
         if ((v_table_entry >> 31) != 0) {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
-          if (iop_a_dst == io1_a_dst) {
+          if (iop_a_dst == io2_a_dst) {
             status = wuffs_base__suspension__short_write;
             goto suspend;
           }
@@ -7661,7 +7795,7 @@
         while (v_n_bits < v_table_entry_n_bits) {
           {
             WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
-            if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+            if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
               status = wuffs_base__suspension__short_read;
               goto suspend;
             }
@@ -7689,7 +7823,7 @@
         }
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -7715,7 +7849,7 @@
           }
           {
             WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
-            if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+            if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
               status = wuffs_base__suspension__short_read;
               goto suspend;
             }
@@ -7742,7 +7876,7 @@
         while (v_n_bits < v_table_entry_n_bits) {
           {
             WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
-            if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+            if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
               status = wuffs_base__suspension__short_read;
               goto suspend;
             }
@@ -7761,10 +7895,9 @@
       }
       while (true) {
         if (((uint64_t)((v_dist_minus_1 + 1))) >
-            ((uint64_t)(iop_a_dst - a_dst.private_impl.mark))) {
-          v_hdist =
-              ((uint32_t)((((uint64_t)((v_dist_minus_1 + 1))) -
-                           ((uint64_t)(iop_a_dst - a_dst.private_impl.mark)))));
+            ((uint64_t)(iop_a_dst - a_dst->data.ptr))) {
+          v_hdist = ((uint32_t)((((uint64_t)((v_dist_minus_1 + 1))) -
+                                 ((uint64_t)(iop_a_dst - a_dst->data.ptr)))));
           if (v_length > v_hdist) {
             v_length -= v_hdist;
             v_hlen = v_hdist;
@@ -7779,7 +7912,7 @@
           v_hdist = (self->private_impl.f_history_index - v_hdist);
           while (true) {
             v_n_copied = wuffs_base__io_writer__copy_n_from_slice(
-                &iop_a_dst, io1_a_dst, v_hlen,
+                &iop_a_dst, io2_a_dst, v_hlen,
                 wuffs_base__slice_u8__subslice_i(
                     wuffs_base__make_slice_u8(self->private_data.f_history,
                                               32768),
@@ -7802,7 +7935,7 @@
           if (v_hlen > 0) {
             while (true) {
               v_n_copied = wuffs_base__io_writer__copy_n_from_slice(
-                  &iop_a_dst, io1_a_dst, v_hlen,
+                  &iop_a_dst, io2_a_dst, v_hlen,
                   wuffs_base__slice_u8__subslice_i(
                       wuffs_base__make_slice_u8(self->private_data.f_history,
                                                 32768),
@@ -7823,7 +7956,7 @@
           }
         }
         v_n_copied = wuffs_base__io_writer__copy_n_from_history(
-            &iop_a_dst, a_dst.private_impl.mark, io1_a_dst, v_length,
+            &iop_a_dst, a_dst->data.ptr, io2_a_dst, v_length,
             (v_dist_minus_1 + 1));
         if (v_length <= v_n_copied) {
           v_length = 0;
@@ -7853,7 +7986,8 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_huffman_slow[0] = coro_susp_point;
+  self->private_impl.p_decode_huffman_slow[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
   self->private_data.s_decode_huffman_slow[0].v_bits = v_bits;
   self->private_data.s_decode_huffman_slow[0].v_n_bits = v_n_bits;
   self->private_data.s_decode_huffman_slow[0].v_table_entry = v_table_entry;
@@ -7870,13 +8004,11 @@
 
   goto exit;
 exit:
-  if (a_dst.private_impl.buf) {
-    a_dst.private_impl.buf->meta.wi =
-        ((size_t)(iop_a_dst - a_dst.private_impl.buf->data.ptr));
+  if (a_dst) {
+    a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
   }
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -7901,11 +8033,11 @@
 
 static wuffs_base__empty_struct  //
 wuffs_lzw__decoder__read_from(wuffs_lzw__decoder* self,
-                              wuffs_base__io_reader a_src);
+                              wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_lzw__decoder__write_to(wuffs_lzw__decoder* self,
-                             wuffs_base__io_writer a_dst);
+                             wuffs_base__io_buffer* a_dst);
 
 // ---------------- Initializer Implementations
 
@@ -7971,12 +8103,12 @@
   if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
     return wuffs_base__make_empty_struct();
   }
-  if (a_lw < 2 || a_lw > 8) {
+  if (a_lw > 8) {
     self->private_impl.magic = WUFFS_BASE__DISABLED;
     return wuffs_base__make_empty_struct();
   }
 
-  self->private_impl.f_set_literal_width_arg = a_lw;
+  self->private_impl.f_set_literal_width_arg = (a_lw + 1);
   return wuffs_base__make_empty_struct();
 }
 
@@ -7999,8 +8131,8 @@
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_lzw__decoder__decode_io_writer(wuffs_lzw__decoder* self,
-                                     wuffs_base__io_writer a_dst,
-                                     wuffs_base__io_reader a_src,
+                                     wuffs_base__io_buffer* a_dst,
+                                     wuffs_base__io_buffer* a_src,
                                      wuffs_base__slice_u8 a_workbuf) {
   if (!self) {
     return wuffs_base__error__bad_receiver;
@@ -8010,6 +8142,10 @@
                ? 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__error__bad_argument;
+  }
   if ((self->private_impl.active_coroutine != 0) &&
       (self->private_impl.active_coroutine != 1)) {
     self->private_impl.magic = WUFFS_BASE__DISABLED;
@@ -8027,9 +8163,9 @@
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
     self->private_impl.f_literal_width = 8;
-    if (self->private_impl.f_set_literal_width_arg >= 2) {
+    if (self->private_impl.f_set_literal_width_arg > 0) {
       self->private_impl.f_literal_width =
-          self->private_impl.f_set_literal_width_arg;
+          (self->private_impl.f_set_literal_width_arg - 1);
     }
     self->private_impl.f_clear_code =
         (((uint32_t)(1)) << self->private_impl.f_literal_width);
@@ -8082,8 +8218,10 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_io_writer[0] = coro_susp_point;
-  self->private_impl.active_coroutine = 1;
+  self->private_impl.p_decode_io_writer[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
+  self->private_impl.active_coroutine =
+      wuffs_base__status__is_suspension(status) ? 1 : 0;
 
   goto exit;
 exit:
@@ -8097,7 +8235,7 @@
 
 static wuffs_base__empty_struct  //
 wuffs_lzw__decoder__read_from(wuffs_lzw__decoder* self,
-                              wuffs_base__io_reader a_src) {
+                              wuffs_base__io_buffer* a_src) {
   uint32_t v_clear_code = 0;
   uint32_t v_end_code = 0;
   uint32_t v_save_code = 0;
@@ -8117,16 +8255,12 @@
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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;
   }
 
   v_clear_code = self->private_impl.f_clear_code;
@@ -8139,11 +8273,11 @@
   v_output_wi = self->private_impl.f_output_wi;
   while (true) {
     if (v_n_bits < v_width) {
-      if (((uint64_t)(io1_a_src - iop_a_src)) >= 4) {
+      if (((uint64_t)(io2_a_src - iop_a_src)) >= 4) {
         v_bits |= (wuffs_base__load_u32le(iop_a_src) << v_n_bits);
         (iop_a_src += ((31 - v_n_bits) >> 3), wuffs_base__make_empty_struct());
         v_n_bits |= 24;
-      } else if (((uint64_t)(io1_a_src - iop_a_src)) <= 0) {
+      } else if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) {
         self->private_impl.f_read_from_return_value = 2;
         goto label_0_break;
       } else {
@@ -8151,7 +8285,7 @@
         (iop_a_src += 1, wuffs_base__make_empty_struct());
         v_n_bits += 8;
         if (v_n_bits >= v_width) {
-        } else if (((uint64_t)(io1_a_src - iop_a_src)) <= 0) {
+        } else if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) {
           self->private_impl.f_read_from_return_value = 2;
           goto label_0_break;
         } else {
@@ -8266,7 +8400,7 @@
   if (self->private_impl.f_read_from_return_value != 2) {
     while (v_n_bits >= 8) {
       v_n_bits -= 8;
-      if (iop_a_src > io0_a_src) {
+      if (iop_a_src > io1_a_src) {
         (iop_a_src--, wuffs_base__make_empty_struct());
       } else {
         self->private_impl.f_read_from_return_value = 4;
@@ -8281,9 +8415,8 @@
   self->private_impl.f_bits = v_bits;
   self->private_impl.f_n_bits = v_n_bits;
   self->private_impl.f_output_wi = v_output_wi;
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return wuffs_base__make_empty_struct();
@@ -8293,7 +8426,7 @@
 
 static wuffs_base__status  //
 wuffs_lzw__decoder__write_to(wuffs_lzw__decoder* self,
-                             wuffs_base__io_writer a_dst) {
+                             wuffs_base__io_buffer* a_dst) {
   wuffs_base__status status = NULL;
 
   wuffs_base__slice_u8 v_s = {0};
@@ -8302,19 +8435,15 @@
   uint8_t* iop_a_dst = NULL;
   uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_dst.private_impl.buf) {
-    iop_a_dst =
-        a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->meta.wi;
-    if (!a_dst.private_impl.mark) {
-      a_dst.private_impl.mark = iop_a_dst;
-      a_dst.private_impl.limit =
-          a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->data.len;
+  uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_dst) {
+    io0_a_dst = a_dst->data.ptr;
+    io1_a_dst = io0_a_dst + a_dst->meta.wi;
+    iop_a_dst = io1_a_dst;
+    io2_a_dst = io0_a_dst + a_dst->data.len;
+    if (a_dst->meta.closed) {
+      io2_a_dst = iop_a_dst;
     }
-    if (a_dst.private_impl.buf->meta.closed) {
-      a_dst.private_impl.limit = iop_a_dst;
-    }
-    io0_a_dst = a_dst.private_impl.mark;
-    io1_a_dst = a_dst.private_impl.limit;
   }
 
   uint32_t coro_susp_point = self->private_impl.p_write_to[0];
@@ -8331,7 +8460,7 @@
       v_s = wuffs_base__slice_u8__subslice_ij(
           wuffs_base__make_slice_u8(self->private_data.f_output, 8199),
           self->private_impl.f_output_ri, self->private_impl.f_output_wi);
-      v_n = wuffs_base__io_writer__copy_from_slice(&iop_a_dst, io1_a_dst, v_s);
+      v_n = wuffs_base__io_writer__copy_from_slice(&iop_a_dst, io2_a_dst, v_s);
       if (v_n == ((uint64_t)(v_s.len))) {
         self->private_impl.f_output_ri = 0;
         self->private_impl.f_output_wi = 0;
@@ -8353,13 +8482,13 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_write_to[0] = coro_susp_point;
+  self->private_impl.p_write_to[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
 
   goto exit;
 exit:
-  if (a_dst.private_impl.buf) {
-    a_dst.private_impl.buf->meta.wi =
-        ((size_t)(iop_a_dst - a_dst.private_impl.buf->data.ptr));
+  if (a_dst) {
+    a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
   }
 
   return status;
@@ -8396,9 +8525,11 @@
 
 const char* wuffs_gif__error__bad_block = "#gif: bad block";
 const char* wuffs_gif__error__bad_extension_label = "#gif: bad extension label";
+const char* wuffs_gif__error__bad_frame_size = "#gif: bad frame size";
 const char* wuffs_gif__error__bad_graphic_control = "#gif: bad graphic control";
 const char* wuffs_gif__error__bad_header = "#gif: bad header";
 const char* wuffs_gif__error__bad_literal_width = "#gif: bad literal width";
+const char* wuffs_gif__error__bad_palette = "#gif: bad palette";
 const char* wuffs_gif__error__internal_error_inconsistent_ri_wi =
     "#gif: internal error: inconsistent ri/wi";
 
@@ -8428,58 +8559,70 @@
         78, 69, 84, 83, 67, 65, 80, 69, 50, 46, 48,
 };
 
+static const uint8_t            //
+    wuffs_gif__iccrgbg1012[11]  //
+    WUFFS_BASE__POTENTIALLY_UNUSED = {
+        73, 67, 67, 82, 71, 66, 71, 49, 48, 49, 50,
+};
+
+static const uint8_t           //
+    wuffs_gif__xmpdataxmp[11]  //
+    WUFFS_BASE__POTENTIALLY_UNUSED = {
+        88, 77, 80, 32, 68, 97, 116, 97, 88, 77, 80,
+};
+
 // ---------------- Private Initializer Prototypes
 
 // ---------------- Private Function Prototypes
 
 static wuffs_base__status  //
 wuffs_gif__decoder__skip_frame(wuffs_gif__decoder* self,
-                               wuffs_base__io_reader a_src);
+                               wuffs_base__io_buffer* a_src);
 
 static wuffs_base__empty_struct  //
 wuffs_gif__decoder__reset_gc(wuffs_gif__decoder* self);
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_up_to_id_part1(wuffs_gif__decoder* self,
-                                          wuffs_base__io_reader a_src);
+                                          wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_header(wuffs_gif__decoder* self,
-                                  wuffs_base__io_reader a_src);
+                                  wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_lsd(wuffs_gif__decoder* self,
-                               wuffs_base__io_reader a_src);
+                               wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_extension(wuffs_gif__decoder* self,
-                                     wuffs_base__io_reader a_src);
+                                     wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_gif__decoder__skip_blocks(wuffs_gif__decoder* self,
-                                wuffs_base__io_reader a_src);
+                                wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_ae(wuffs_gif__decoder* self,
-                              wuffs_base__io_reader a_src);
+                              wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_gc(wuffs_gif__decoder* self,
-                              wuffs_base__io_reader a_src);
+                              wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_id_part0(wuffs_gif__decoder* self,
-                                    wuffs_base__io_reader a_src);
+                                    wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_id_part1(wuffs_gif__decoder* self,
                                     wuffs_base__pixel_buffer* a_dst,
-                                    wuffs_base__io_reader a_src);
+                                    wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_id_part2(wuffs_gif__decoder* self,
                                     wuffs_base__pixel_buffer* a_dst,
-                                    wuffs_base__io_reader a_src,
+                                    wuffs_base__io_buffer* a_src,
                                     wuffs_base__slice_u8 a_workbuf);
 
 static wuffs_base__status  //
@@ -8549,12 +8692,47 @@
 
 // ---------------- Function Implementations
 
+// -------- func gif.decoder.set_quirk_enabled
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct  //
+wuffs_gif__decoder__set_quirk_enabled(wuffs_gif__decoder* self,
+                                      uint32_t a_quirk,
+                                      bool a_enabled) {
+  if (!self) {
+    return wuffs_base__make_empty_struct();
+  }
+  if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+    return wuffs_base__make_empty_struct();
+  }
+
+  if (self->private_impl.f_call_sequence == 0) {
+    if (a_quirk == 1041635328) {
+      self->private_impl.f_quirk_enabled_delay_num_decoded_frames = a_enabled;
+    } else if (a_quirk == 1041635329) {
+      self->private_impl
+          .f_quirk_enabled_first_frame_local_palette_means_black_background =
+          a_enabled;
+    } else if (a_quirk == 1041635330) {
+      self->private_impl.f_quirk_enabled_honor_background_color = a_enabled;
+    } else if (a_quirk == 1041635331) {
+      self->private_impl.f_quirk_enabled_ignore_too_much_pixel_data = a_enabled;
+    } else if (a_quirk == 1041635332) {
+      self->private_impl.f_quirk_enabled_image_bounds_are_strict = a_enabled;
+    } else if (a_quirk == 1041635333) {
+      self->private_impl.f_quirk_enabled_reject_empty_frame = a_enabled;
+    } else if (a_quirk == 1041635334) {
+      self->private_impl.f_quirk_enabled_reject_empty_palette = a_enabled;
+    }
+  }
+  return wuffs_base__make_empty_struct();
+}
+
 // -------- func gif.decoder.decode_image_config
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_gif__decoder__decode_image_config(wuffs_gif__decoder* self,
                                         wuffs_base__image_config* a_dst,
-                                        wuffs_base__io_reader a_src) {
+                                        wuffs_base__io_buffer* a_src) {
   if (!self) {
     return wuffs_base__error__bad_receiver;
   }
@@ -8563,6 +8741,10 @@
                ? 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__error__bad_argument;
+  }
   if ((self->private_impl.active_coroutine != 0) &&
       (self->private_impl.active_coroutine != 1)) {
     self->private_impl.magic = WUFFS_BASE__DISABLED;
@@ -8579,38 +8761,47 @@
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
-    if (self->private_impl.f_call_sequence >= 1) {
+    if (self->private_impl.f_call_sequence == 0) {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
+      status = wuffs_gif__decoder__decode_header(self, a_src);
+      if (status) {
+        goto suspend;
+      }
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
+      status = wuffs_gif__decoder__decode_lsd(self, a_src);
+      if (status) {
+        goto suspend;
+      }
+    } else if (self->private_impl.f_call_sequence != 2) {
       status = wuffs_base__error__bad_call_sequence;
       goto exit;
     }
-    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-    status = wuffs_gif__decoder__decode_header(self, a_src);
-    if (status) {
-      goto suspend;
-    }
-    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
-    status = wuffs_gif__decoder__decode_lsd(self, a_src);
-    if (status) {
-      goto suspend;
-    }
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
     status = wuffs_gif__decoder__decode_up_to_id_part1(self, a_src);
     if (status) {
       goto suspend;
     }
-    v_ffio =
-        (!self->private_impl.f_gc_has_transparent_index &&
-         (self->private_impl.f_frame_rect_x0 == 0) &&
-         (self->private_impl.f_frame_rect_y0 == 0) &&
-         (self->private_impl.f_frame_rect_x1 == self->private_impl.f_width) &&
-         (self->private_impl.f_frame_rect_y1 == self->private_impl.f_height));
+    v_ffio = !self->private_impl.f_gc_has_transparent_index;
+    if (!self->private_impl.f_quirk_enabled_honor_background_color) {
+      v_ffio =
+          (v_ffio && (self->private_impl.f_frame_rect_x0 == 0) &&
+           (self->private_impl.f_frame_rect_y0 == 0) &&
+           (self->private_impl.f_frame_rect_x1 == self->private_impl.f_width) &&
+           (self->private_impl.f_frame_rect_y1 == self->private_impl.f_height));
+    } else if (v_ffio) {
+      self->private_impl.f_black_color_u32_argb_premul = 4278190080;
+    }
+    if (self->private_impl.f_background_color_u32_argb_premul == 77) {
+      self->private_impl.f_background_color_u32_argb_premul =
+          self->private_impl.f_black_color_u32_argb_premul;
+    }
     if (a_dst != NULL) {
       wuffs_base__image_config__set(
           a_dst, 1191444488, 0, self->private_impl.f_width,
           self->private_impl.f_height,
           self->private_impl.f_frame_config_io_position, v_ffio);
     }
-    self->private_impl.f_call_sequence = 1;
+    self->private_impl.f_call_sequence = 3;
 
     goto ok;
   ok:
@@ -8620,8 +8811,10 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_image_config[0] = coro_susp_point;
-  self->private_impl.active_coroutine = 1;
+  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;
 
   goto exit;
 exit:
@@ -8631,6 +8824,172 @@
   return status;
 }
 
+// -------- func gif.decoder.set_report_metadata
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct  //
+wuffs_gif__decoder__set_report_metadata(wuffs_gif__decoder* self,
+                                        uint32_t a_fourcc,
+                                        bool a_report) {
+  if (!self) {
+    return wuffs_base__make_empty_struct();
+  }
+  if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+    return wuffs_base__make_empty_struct();
+  }
+
+  if (a_fourcc == 1229144912) {
+    self->private_impl.f_report_metadata_iccp = a_report;
+  } else if (a_fourcc == 1481461792) {
+    self->private_impl.f_report_metadata_xmp = a_report;
+  }
+  return wuffs_base__make_empty_struct();
+}
+
+// -------- func gif.decoder.ack_metadata_chunk
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
+wuffs_gif__decoder__ack_metadata_chunk(wuffs_gif__decoder* self,
+                                       wuffs_base__io_buffer* a_src) {
+  if (!self) {
+    return wuffs_base__error__bad_receiver;
+  }
+  if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+    return (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__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__error__interleaved_coroutine_calls;
+  }
+  self->private_impl.active_coroutine = 0;
+  wuffs_base__status status = NULL;
+
+  uint8_t* iop_a_src = NULL;
+  uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  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_ack_metadata_chunk[0];
+  if (coro_susp_point) {
+  }
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    if (self->private_impl.f_call_sequence != 1) {
+      status = wuffs_base__error__bad_call_sequence;
+      goto exit;
+    }
+    if ((a_src ? wuffs_base__u64__sat_add(
+                     a_src->meta.pos, ((uint64_t)(iop_a_src - a_src->data.ptr)))
+               : 0) != self->private_impl.f_metadata_io_position) {
+      status = wuffs_base__error__bad_i_o_position;
+      goto exit;
+    }
+    while (((uint64_t)(io2_a_src - iop_a_src)) <= 0) {
+      status = wuffs_base__suspension__short_read;
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1);
+    }
+    if (self->private_impl.f_metadata_fourcc_value == 1481461792) {
+      self->private_impl.f_metadata_chunk_length_value =
+          (((uint64_t)(wuffs_base__load_u8be(iop_a_src))) + 1);
+      if (self->private_impl.f_metadata_chunk_length_value > 1) {
+        self->private_impl.f_metadata_io_position = wuffs_base__u64__sat_add(
+            (a_src ? wuffs_base__u64__sat_add(
+                         a_src->meta.pos,
+                         ((uint64_t)(iop_a_src - a_src->data.ptr)))
+                   : 0),
+            self->private_impl.f_metadata_chunk_length_value);
+        status = wuffs_base__warning__metadata_reported;
+        goto ok;
+      }
+    } else {
+      self->private_impl.f_metadata_chunk_length_value =
+          ((uint64_t)(wuffs_base__load_u8be(iop_a_src)));
+      if (self->private_impl.f_metadata_chunk_length_value > 0) {
+        (iop_a_src += 1, wuffs_base__make_empty_struct());
+        self->private_impl.f_metadata_io_position = wuffs_base__u64__sat_add(
+            (a_src ? wuffs_base__u64__sat_add(
+                         a_src->meta.pos,
+                         ((uint64_t)(iop_a_src - a_src->data.ptr)))
+                   : 0),
+            self->private_impl.f_metadata_chunk_length_value);
+        status = wuffs_base__warning__metadata_reported;
+        goto ok;
+      }
+    }
+    (iop_a_src += 1, wuffs_base__make_empty_struct());
+    self->private_impl.f_call_sequence = 2;
+    self->private_impl.f_metadata_fourcc_value = 0;
+    self->private_impl.f_metadata_io_position = 0;
+    status = NULL;
+    goto ok;
+    goto ok;
+  ok:
+    self->private_impl.p_ack_metadata_chunk[0] = 0;
+    goto exit;
+  }
+
+  goto suspend;
+suspend:
+  self->private_impl.p_ack_metadata_chunk[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 gif.decoder.metadata_fourcc
+
+WUFFS_BASE__MAYBE_STATIC uint32_t  //
+wuffs_gif__decoder__metadata_fourcc(const wuffs_gif__decoder* self) {
+  if (!self) {
+    return 0;
+  }
+  if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&
+      (self->private_impl.magic != WUFFS_BASE__DISABLED)) {
+    return 0;
+  }
+
+  return self->private_impl.f_metadata_fourcc_value;
+}
+
+// -------- func gif.decoder.metadata_chunk_length
+
+WUFFS_BASE__MAYBE_STATIC uint64_t  //
+wuffs_gif__decoder__metadata_chunk_length(const wuffs_gif__decoder* self) {
+  if (!self) {
+    return 0;
+  }
+  if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&
+      (self->private_impl.magic != WUFFS_BASE__DISABLED)) {
+    return 0;
+  }
+
+  return self->private_impl.f_metadata_chunk_length_value;
+}
+
 // -------- func gif.decoder.num_animation_loops
 
 WUFFS_BASE__MAYBE_STATIC uint32_t  //
@@ -8733,6 +9092,7 @@
   if (self->private_impl.f_call_sequence == 0) {
     return wuffs_base__error__bad_call_sequence;
   }
+  self->private_impl.f_delayed_num_decoded_frames = false;
   self->private_impl.f_end_of_data = false;
   self->private_impl.f_restarted = true;
   self->private_impl.f_frame_config_io_position = a_io_position;
@@ -8747,7 +9107,7 @@
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_gif__decoder__decode_frame_config(wuffs_gif__decoder* self,
                                         wuffs_base__frame_config* a_dst,
-                                        wuffs_base__io_reader a_src) {
+                                        wuffs_base__io_buffer* a_src) {
   if (!self) {
     return wuffs_base__error__bad_receiver;
   }
@@ -8756,8 +9116,12 @@
                ? 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__error__bad_argument;
+  }
   if ((self->private_impl.active_coroutine != 0) &&
-      (self->private_impl.active_coroutine != 2)) {
+      (self->private_impl.active_coroutine != 3)) {
     self->private_impl.magic = WUFFS_BASE__DISABLED;
     return wuffs_base__error__interleaved_coroutine_calls;
   }
@@ -8765,32 +9129,67 @@
   wuffs_base__status status = NULL;
 
   uint8_t v_blend = 0;
+  uint32_t v_background_color = 0;
+  uint8_t v_flags = 0;
+
+  uint8_t* iop_a_src = NULL;
+  uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  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];
   if (coro_susp_point) {
+    v_blend = self->private_data.s_decode_frame_config[0].v_blend;
+    v_background_color =
+        self->private_data.s_decode_frame_config[0].v_background_color;
   }
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
+    self->private_impl.f_ignore_metadata = true;
     (memset(&self->private_impl.f_dirty_y, 0, sizeof(wuffs_base__range_ie_u32)),
      wuffs_base__make_empty_struct());
     if (!self->private_impl.f_end_of_data) {
       if (self->private_impl.f_call_sequence == 0) {
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+        }
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
         status = wuffs_gif__decoder__decode_image_config(self, NULL, a_src);
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
+        }
         if (status) {
           goto suspend;
         }
-      } else if (self->private_impl.f_call_sequence != 1) {
-        if (self->private_impl.f_call_sequence == 2) {
+      } else if (self->private_impl.f_call_sequence != 3) {
+        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(2);
           status = wuffs_gif__decoder__skip_frame(self, a_src);
+          if (a_src) {
+            iop_a_src = a_src->data.ptr + a_src->meta.ri;
+          }
           if (status) {
             goto suspend;
           }
         }
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+        }
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
         status = wuffs_gif__decoder__decode_up_to_id_part1(self, a_src);
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
+        }
         if (status) {
           goto suspend;
         }
@@ -8801,8 +9200,23 @@
       goto ok;
     }
     v_blend = 0;
+    v_background_color = self->private_impl.f_black_color_u32_argb_premul;
     if (!self->private_impl.f_gc_has_transparent_index) {
       v_blend = 2;
+      v_background_color =
+          self->private_impl.f_background_color_u32_argb_premul;
+      if (self->private_impl
+              .f_quirk_enabled_first_frame_local_palette_means_black_background &&
+          (self->private_impl.f_num_decoded_frame_configs_value == 0)) {
+        while (((uint64_t)(io2_a_src - iop_a_src)) <= 0) {
+          status = wuffs_base__suspension__short_read;
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(4);
+        }
+        v_flags = wuffs_base__load_u8be(iop_a_src);
+        if ((v_flags & 128) != 0) {
+          v_background_color = self->private_impl.f_black_color_u32_argb_premul;
+        }
+      }
     }
     if (a_dst != NULL) {
       wuffs_base__frame_config__update(
@@ -8819,11 +9233,11 @@
           ((wuffs_base__flicks)(self->private_impl.f_gc_duration)),
           self->private_impl.f_num_decoded_frame_configs_value,
           self->private_impl.f_frame_config_io_position, v_blend,
-          self->private_impl.f_gc_disposal);
+          self->private_impl.f_gc_disposal, v_background_color);
     }
     wuffs_base__u64__sat_add_indirect(
         &self->private_impl.f_num_decoded_frame_configs_value, 1);
-    self->private_impl.f_call_sequence = 2;
+    self->private_impl.f_call_sequence = 4;
 
     goto ok;
   ok:
@@ -8833,11 +9247,20 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_frame_config[0] = coro_susp_point;
-  self->private_impl.active_coroutine = 2;
+  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) ? 3 : 0;
+  self->private_data.s_decode_frame_config[0].v_blend = v_blend;
+  self->private_data.s_decode_frame_config[0].v_background_color =
+      v_background_color;
 
   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;
   }
@@ -8848,24 +9271,21 @@
 
 static wuffs_base__status  //
 wuffs_gif__decoder__skip_frame(wuffs_gif__decoder* self,
-                               wuffs_base__io_reader a_src) {
+                               wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint8_t v_flags = 0;
+  uint8_t v_lw = 0;
 
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_skip_frame[0];
@@ -8876,7 +9296,7 @@
 
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -8888,36 +9308,45 @@
           (((uint32_t)(3)) << (1 + (v_flags & 7)));
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
       if (self->private_data.s_skip_frame[0].scratch >
-          ((uint64_t)(io1_a_src - iop_a_src))) {
+          ((uint64_t)(io2_a_src - iop_a_src))) {
         self->private_data.s_skip_frame[0].scratch -=
-            ((uint64_t)(io1_a_src - iop_a_src));
-        iop_a_src = io1_a_src;
+            ((uint64_t)(io2_a_src - iop_a_src));
+        iop_a_src = io2_a_src;
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
       iop_a_src += self->private_data.s_skip_frame[0].scratch;
     }
-    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
-    if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
-      status = wuffs_base__suspension__short_read;
-      goto suspend;
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+        status = wuffs_base__suspension__short_read;
+        goto suspend;
+      }
+      uint8_t t_1 = *iop_a_src++;
+      v_lw = t_1;
     }
-    iop_a_src++;
-    if (a_src.private_impl.buf) {
-      a_src.private_impl.buf->meta.ri =
-          ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+    if (v_lw > 8) {
+      status = wuffs_gif__error__bad_literal_width;
+      goto exit;
+    }
+    if (a_src) {
+      a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
     }
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
     status = wuffs_gif__decoder__skip_blocks(self, a_src);
-    if (a_src.private_impl.buf) {
-      iop_a_src =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
+    if (a_src) {
+      iop_a_src = a_src->data.ptr + a_src->meta.ri;
     }
     if (status) {
       goto suspend;
     }
-    wuffs_base__u64__sat_add_indirect(
-        &self->private_impl.f_num_decoded_frames_value, 1);
+    if (self->private_impl.f_quirk_enabled_delay_num_decoded_frames) {
+      self->private_impl.f_delayed_num_decoded_frames = true;
+    } else {
+      wuffs_base__u64__sat_add_indirect(
+          &self->private_impl.f_num_decoded_frames_value, 1);
+    }
     wuffs_gif__decoder__reset_gc(self);
 
     goto ok;
@@ -8928,13 +9357,13 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_skip_frame[0] = coro_susp_point;
+  self->private_impl.p_skip_frame[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -8945,7 +9374,7 @@
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_gif__decoder__decode_frame(wuffs_gif__decoder* self,
                                  wuffs_base__pixel_buffer* a_dst,
-                                 wuffs_base__io_reader a_src,
+                                 wuffs_base__io_buffer* a_src,
                                  wuffs_base__slice_u8 a_workbuf,
                                  wuffs_base__decode_frame_options* a_opts) {
   if (!self) {
@@ -8956,12 +9385,12 @@
                ? wuffs_base__error__disabled_by_previous_error
                : wuffs_base__error__initialize_not_called;
   }
-  if (!a_dst) {
+  if (!a_dst || !a_src) {
     self->private_impl.magic = WUFFS_BASE__DISABLED;
     return wuffs_base__error__bad_argument;
   }
   if ((self->private_impl.active_coroutine != 0) &&
-      (self->private_impl.active_coroutine != 3)) {
+      (self->private_impl.active_coroutine != 4)) {
     self->private_impl.magic = WUFFS_BASE__DISABLED;
     return wuffs_base__error__interleaved_coroutine_calls;
   }
@@ -8974,13 +9403,22 @@
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
-    if (self->private_impl.f_call_sequence != 2) {
+    self->private_impl.f_ignore_metadata = true;
+    if (self->private_impl.f_call_sequence != 4) {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
       status = wuffs_gif__decoder__decode_frame_config(self, NULL, a_src);
       if (status) {
         goto suspend;
       }
     }
+    if (self->private_impl.f_quirk_enabled_reject_empty_frame &&
+        ((self->private_impl.f_frame_rect_x0 ==
+          self->private_impl.f_frame_rect_x1) ||
+         (self->private_impl.f_frame_rect_y0 ==
+          self->private_impl.f_frame_rect_y1))) {
+      status = wuffs_gif__error__bad_frame_size;
+      goto exit;
+    }
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
     status = wuffs_gif__decoder__decode_id_part1(self, a_dst, a_src);
     if (status) {
@@ -9003,8 +9441,10 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_frame[0] = coro_susp_point;
-  self->private_impl.active_coroutine = 3;
+  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) ? 4 : 0;
 
   goto exit;
 exit:
@@ -9018,8 +9458,7 @@
 
 static wuffs_base__empty_struct  //
 wuffs_gif__decoder__reset_gc(wuffs_gif__decoder* self) {
-  self->private_impl.f_call_sequence = 3;
-  self->private_impl.f_seen_graphic_control = false;
+  self->private_impl.f_call_sequence = 5;
   self->private_impl.f_gc_has_transparent_index = false;
   self->private_impl.f_gc_transparent_index = 0;
   self->private_impl.f_gc_disposal = 0;
@@ -9031,7 +9470,7 @@
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_up_to_id_part1(wuffs_gif__decoder* self,
-                                          wuffs_base__io_reader a_src) {
+                                          wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint8_t v_block_type = 0;
@@ -9039,16 +9478,12 @@
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_up_to_id_part1[0];
@@ -9058,19 +9493,18 @@
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
     if (!self->private_impl.f_restarted) {
-      self->private_impl.f_frame_config_io_position =
-          (a_src.private_impl.buf
-               ? wuffs_base__u64__sat_add(
-                     a_src.private_impl.buf->meta.pos,
-                     ((uint64_t)(iop_a_src - a_src.private_impl.buf->data.ptr)))
-               : 0);
+      if (self->private_impl.f_call_sequence != 2) {
+        self->private_impl.f_frame_config_io_position =
+            (a_src ? wuffs_base__u64__sat_add(
+                         a_src->meta.pos,
+                         ((uint64_t)(iop_a_src - a_src->data.ptr)))
+                   : 0);
+      }
     } else if (self->private_impl.f_frame_config_io_position !=
-               (a_src.private_impl.buf
-                    ? wuffs_base__u64__sat_add(
-                          a_src.private_impl.buf->meta.pos,
-                          ((uint64_t)(iop_a_src -
-                                      a_src.private_impl.buf->data.ptr)))
-                    : 0)) {
+               (a_src ? wuffs_base__u64__sat_add(
+                            a_src->meta.pos,
+                            ((uint64_t)(iop_a_src - a_src->data.ptr)))
+                      : 0)) {
       status = wuffs_base__error__bad_restart;
       goto exit;
     } else {
@@ -9079,7 +9513,7 @@
     while (true) {
       {
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-        if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+        if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
           status = wuffs_base__suspension__short_read;
           goto suspend;
         }
@@ -9087,35 +9521,41 @@
         v_block_type = t_0;
       }
       if (v_block_type == 33) {
-        if (a_src.private_impl.buf) {
-          a_src.private_impl.buf->meta.ri =
-              ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
         }
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
         status = wuffs_gif__decoder__decode_extension(self, a_src);
-        if (a_src.private_impl.buf) {
-          iop_a_src = a_src.private_impl.buf->data.ptr +
-                      a_src.private_impl.buf->meta.ri;
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
         }
         if (status) {
           goto suspend;
         }
       } else if (v_block_type == 44) {
-        if (a_src.private_impl.buf) {
-          a_src.private_impl.buf->meta.ri =
-              ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+        if (self->private_impl.f_delayed_num_decoded_frames) {
+          self->private_impl.f_delayed_num_decoded_frames = false;
+          wuffs_base__u64__sat_add_indirect(
+              &self->private_impl.f_num_decoded_frames_value, 1);
+        }
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
         }
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
         status = wuffs_gif__decoder__decode_id_part0(self, a_src);
-        if (a_src.private_impl.buf) {
-          iop_a_src = a_src.private_impl.buf->data.ptr +
-                      a_src.private_impl.buf->meta.ri;
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
         }
         if (status) {
           goto suspend;
         }
         goto label_0_break;
       } else if (v_block_type == 59) {
+        if (self->private_impl.f_delayed_num_decoded_frames) {
+          self->private_impl.f_delayed_num_decoded_frames = false;
+          wuffs_base__u64__sat_add_indirect(
+              &self->private_impl.f_num_decoded_frames_value, 1);
+        }
         self->private_impl.f_end_of_data = true;
         goto label_0_break;
       } else {
@@ -9133,13 +9573,13 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_up_to_id_part1[0] = coro_susp_point;
+  self->private_impl.p_decode_up_to_id_part1[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -9149,7 +9589,7 @@
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_header(wuffs_gif__decoder* self,
-                                  wuffs_base__io_reader a_src) {
+                                  wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint8_t v_c[6] = {0};
@@ -9158,16 +9598,12 @@
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_header[0];
@@ -9181,7 +9617,7 @@
     while (v_i < 6) {
       {
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-        if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+        if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
           status = wuffs_base__suspension__short_read;
           goto suspend;
         }
@@ -9204,15 +9640,15 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_header[0] = coro_susp_point;
+  self->private_impl.p_decode_header[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
   memcpy(self->private_data.s_decode_header[0].v_c, v_c, sizeof(v_c));
   self->private_data.s_decode_header[0].v_i = v_i;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -9222,32 +9658,32 @@
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_lsd(wuffs_gif__decoder* self,
-                               wuffs_base__io_reader a_src) {
+                               wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint8_t v_flags = 0;
+  uint8_t v_background_color_index = 0;
   uint32_t v_num_palette_entries = 0;
   uint32_t v_i = 0;
+  uint32_t v_j = 0;
   uint32_t v_argb = 0;
 
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_lsd[0];
   if (coro_susp_point) {
     v_flags = self->private_data.s_decode_lsd[0].v_flags;
+    v_background_color_index =
+        self->private_data.s_decode_lsd[0].v_background_color_index;
     v_num_palette_entries =
         self->private_data.s_decode_lsd[0].v_num_palette_entries;
     v_i = self->private_data.s_decode_lsd[0].v_i;
@@ -9258,14 +9694,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
       uint32_t t_0;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
         t_0 = ((uint32_t)(wuffs_base__load_u16le(iop_a_src)));
         iop_a_src += 2;
       } else {
         self->private_data.s_decode_lsd[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -9287,14 +9723,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
       uint32_t t_1;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
         t_1 = ((uint32_t)(wuffs_base__load_u16le(iop_a_src)));
         iop_a_src += 2;
       } else {
         self->private_data.s_decode_lsd[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -9315,56 +9751,61 @@
     }
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
       uint8_t t_2 = *iop_a_src++;
       v_flags = t_2;
     }
-    self->private_data.s_decode_lsd[0].scratch = 2;
-    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
-    if (self->private_data.s_decode_lsd[0].scratch >
-        ((uint64_t)(io1_a_src - iop_a_src))) {
-      self->private_data.s_decode_lsd[0].scratch -=
-          ((uint64_t)(io1_a_src - iop_a_src));
-      iop_a_src = io1_a_src;
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+        status = wuffs_base__suspension__short_read;
+        goto suspend;
+      }
+      uint8_t t_3 = *iop_a_src++;
+      v_background_color_index = t_3;
+    }
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
+    if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
       status = wuffs_base__suspension__short_read;
       goto suspend;
     }
-    iop_a_src += self->private_data.s_decode_lsd[0].scratch;
-    if ((v_flags & 128) != 0) {
+    iop_a_src++;
+    v_i = 0;
+    self->private_impl.f_has_global_palette = ((v_flags & 128) != 0);
+    if (self->private_impl.f_has_global_palette) {
       v_num_palette_entries = (((uint32_t)(1)) << (1 + (v_flags & 7)));
-      v_i = 0;
       while (v_i < v_num_palette_entries) {
         {
-          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
-          uint32_t t_3;
-          if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 3)) {
-            t_3 = ((uint32_t)(wuffs_base__load_u24be(iop_a_src)));
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
+          uint32_t t_4;
+          if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 3)) {
+            t_4 = ((uint32_t)(wuffs_base__load_u24be(iop_a_src)));
             iop_a_src += 3;
           } else {
             self->private_data.s_decode_lsd[0].scratch = 0;
-            WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
+            WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9);
             while (true) {
-              if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+              if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
                 status = wuffs_base__suspension__short_read;
                 goto suspend;
               }
               uint64_t* scratch = &self->private_data.s_decode_lsd[0].scratch;
-              uint32_t num_bits_3 = ((uint32_t)(*scratch & 0xFF));
+              uint32_t num_bits_4 = ((uint32_t)(*scratch & 0xFF));
               *scratch >>= 8;
               *scratch <<= 8;
-              *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_3);
-              if (num_bits_3 == 16) {
-                t_3 = ((uint32_t)(*scratch >> 40));
+              *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_4);
+              if (num_bits_4 == 16) {
+                t_4 = ((uint32_t)(*scratch >> 40));
                 break;
               }
-              num_bits_3 += 8;
-              *scratch |= ((uint64_t)(num_bits_3));
+              num_bits_4 += 8;
+              *scratch |= ((uint64_t)(num_bits_4));
             }
           }
-          v_argb = t_3;
+          v_argb = t_4;
         }
         v_argb |= 4278190080;
         self->private_data.f_palettes[0][((4 * v_i) + 0)] =
@@ -9377,14 +9818,31 @@
             ((uint8_t)(((v_argb >> 24) & 255)));
         v_i += 1;
       }
-      while (v_i < 256) {
-        self->private_data.f_palettes[0][((4 * v_i) + 0)] = 0;
-        self->private_data.f_palettes[0][((4 * v_i) + 1)] = 0;
-        self->private_data.f_palettes[0][((4 * v_i) + 2)] = 0;
-        self->private_data.f_palettes[0][((4 * v_i) + 3)] = 255;
-        v_i += 1;
+      if (self->private_impl.f_quirk_enabled_honor_background_color) {
+        if ((v_background_color_index != 0) &&
+            (((uint32_t)(v_background_color_index)) < v_num_palette_entries)) {
+          v_j = (4 * ((uint32_t)(v_background_color_index)));
+          self->private_impl.f_background_color_u32_argb_premul =
+              ((((uint32_t)(self->private_data.f_palettes[0][(v_j + 0)]))
+                << 0) |
+               (((uint32_t)(self->private_data.f_palettes[0][(v_j + 1)]))
+                << 8) |
+               (((uint32_t)(self->private_data.f_palettes[0][(v_j + 2)]))
+                << 16) |
+               (((uint32_t)(self->private_data.f_palettes[0][(v_j + 3)]))
+                << 24));
+        } else {
+          self->private_impl.f_background_color_u32_argb_premul = 77;
+        }
       }
     }
+    while (v_i < 256) {
+      self->private_data.f_palettes[0][((4 * v_i) + 0)] = 0;
+      self->private_data.f_palettes[0][((4 * v_i) + 1)] = 0;
+      self->private_data.f_palettes[0][((4 * v_i) + 2)] = 0;
+      self->private_data.f_palettes[0][((4 * v_i) + 3)] = 255;
+      v_i += 1;
+    }
 
     goto ok;
   ok:
@@ -9394,17 +9852,19 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_lsd[0] = coro_susp_point;
+  self->private_impl.p_decode_lsd[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
   self->private_data.s_decode_lsd[0].v_flags = v_flags;
+  self->private_data.s_decode_lsd[0].v_background_color_index =
+      v_background_color_index;
   self->private_data.s_decode_lsd[0].v_num_palette_entries =
       v_num_palette_entries;
   self->private_data.s_decode_lsd[0].v_i = v_i;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -9414,7 +9874,7 @@
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_extension(wuffs_gif__decoder* self,
-                                     wuffs_base__io_reader a_src) {
+                                     wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint8_t v_label = 0;
@@ -9422,16 +9882,12 @@
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_extension[0];
@@ -9442,7 +9898,7 @@
 
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -9450,15 +9906,13 @@
       v_label = t_0;
     }
     if (v_label == 249) {
-      if (a_src.private_impl.buf) {
-        a_src.private_impl.buf->meta.ri =
-            ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+      if (a_src) {
+        a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
       }
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
       status = wuffs_gif__decoder__decode_gc(self, a_src);
-      if (a_src.private_impl.buf) {
-        iop_a_src =
-            a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
+      if (a_src) {
+        iop_a_src = a_src->data.ptr + a_src->meta.ri;
       }
       if (status) {
         goto suspend;
@@ -9466,15 +9920,13 @@
       status = NULL;
       goto ok;
     } else if (v_label == 255) {
-      if (a_src.private_impl.buf) {
-        a_src.private_impl.buf->meta.ri =
-            ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+      if (a_src) {
+        a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
       }
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
       status = wuffs_gif__decoder__decode_ae(self, a_src);
-      if (a_src.private_impl.buf) {
-        iop_a_src =
-            a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
+      if (a_src) {
+        iop_a_src = a_src->data.ptr + a_src->meta.ri;
       }
       if (status) {
         goto suspend;
@@ -9482,15 +9934,13 @@
       status = NULL;
       goto ok;
     }
-    if (a_src.private_impl.buf) {
-      a_src.private_impl.buf->meta.ri =
-          ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+    if (a_src) {
+      a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
     }
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
     status = wuffs_gif__decoder__skip_blocks(self, a_src);
-    if (a_src.private_impl.buf) {
-      iop_a_src =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
+    if (a_src) {
+      iop_a_src = a_src->data.ptr + a_src->meta.ri;
     }
     if (status) {
       goto suspend;
@@ -9504,13 +9954,13 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_extension[0] = coro_susp_point;
+  self->private_impl.p_decode_extension[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -9520,7 +9970,7 @@
 
 static wuffs_base__status  //
 wuffs_gif__decoder__skip_blocks(wuffs_gif__decoder* self,
-                                wuffs_base__io_reader a_src) {
+                                wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint8_t v_block_size = 0;
@@ -9528,16 +9978,12 @@
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_skip_blocks[0];
@@ -9549,7 +9995,7 @@
     while (true) {
       {
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-        if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+        if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
           status = wuffs_base__suspension__short_read;
           goto suspend;
         }
@@ -9563,10 +10009,10 @@
       self->private_data.s_skip_blocks[0].scratch = ((uint32_t)(v_block_size));
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
       if (self->private_data.s_skip_blocks[0].scratch >
-          ((uint64_t)(io1_a_src - iop_a_src))) {
+          ((uint64_t)(io2_a_src - iop_a_src))) {
         self->private_data.s_skip_blocks[0].scratch -=
-            ((uint64_t)(io1_a_src - iop_a_src));
-        iop_a_src = io1_a_src;
+            ((uint64_t)(io2_a_src - iop_a_src));
+        iop_a_src = io2_a_src;
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -9581,13 +10027,13 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_skip_blocks[0] = coro_susp_point;
+  self->private_impl.p_skip_blocks[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -9597,34 +10043,34 @@
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_ae(wuffs_gif__decoder* self,
-                              wuffs_base__io_reader a_src) {
+                              wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint8_t v_c = 0;
   uint8_t v_block_size = 0;
-  bool v_not_animexts = false;
-  bool v_not_netscape = false;
+  bool v_is_animexts = false;
+  bool v_is_netscape = false;
+  bool v_is_iccp = false;
+  bool v_is_xmp = false;
 
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_ae[0];
   if (coro_susp_point) {
     v_block_size = self->private_data.s_decode_ae[0].v_block_size;
-    v_not_animexts = self->private_data.s_decode_ae[0].v_not_animexts;
-    v_not_netscape = self->private_data.s_decode_ae[0].v_not_netscape;
+    v_is_animexts = self->private_data.s_decode_ae[0].v_is_animexts;
+    v_is_netscape = self->private_data.s_decode_ae[0].v_is_netscape;
+    v_is_iccp = self->private_data.s_decode_ae[0].v_is_iccp;
+    v_is_xmp = self->private_data.s_decode_ae[0].v_is_xmp;
   }
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
@@ -9632,7 +10078,7 @@
     while (true) {
       {
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-        if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+        if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
           status = wuffs_base__suspension__short_read;
           goto suspend;
         }
@@ -9647,33 +10093,38 @@
         self->private_data.s_decode_ae[0].scratch = ((uint32_t)(v_block_size));
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
         if (self->private_data.s_decode_ae[0].scratch >
-            ((uint64_t)(io1_a_src - iop_a_src))) {
+            ((uint64_t)(io2_a_src - iop_a_src))) {
           self->private_data.s_decode_ae[0].scratch -=
-              ((uint64_t)(io1_a_src - iop_a_src));
-          iop_a_src = io1_a_src;
+              ((uint64_t)(io2_a_src - iop_a_src));
+          iop_a_src = io2_a_src;
           status = wuffs_base__suspension__short_read;
           goto suspend;
         }
         iop_a_src += self->private_data.s_decode_ae[0].scratch;
         goto label_0_break;
       }
-      v_not_animexts = false;
-      v_not_netscape = false;
+      v_is_animexts = true;
+      v_is_netscape = true;
+      v_is_iccp = true;
+      v_is_xmp = true;
       v_block_size = 0;
       while (v_block_size < 11) {
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
           uint8_t t_1 = *iop_a_src++;
           v_c = t_1;
         }
-        v_not_animexts =
-            (v_not_animexts || (v_c != wuffs_gif__animexts1dot0[v_block_size]));
-        v_not_netscape =
-            (v_not_netscape || (v_c != wuffs_gif__netscape2dot0[v_block_size]));
+        v_is_animexts =
+            (v_is_animexts && (v_c == wuffs_gif__animexts1dot0[v_block_size]));
+        v_is_netscape =
+            (v_is_netscape && (v_c == wuffs_gif__netscape2dot0[v_block_size]));
+        v_is_iccp =
+            (v_is_iccp && (v_c == wuffs_gif__iccrgbg1012[v_block_size]));
+        v_is_xmp = (v_is_xmp && (v_c == wuffs_gif__xmpdataxmp[v_block_size]));
 #if defined(__GNUC__)
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wconversion"
@@ -9683,101 +10134,135 @@
 #pragma GCC diagnostic pop
 #endif
       }
-      if (v_not_animexts && v_not_netscape) {
-        goto label_0_break;
-      }
-      {
-        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
-        if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
-          status = wuffs_base__suspension__short_read;
-          goto suspend;
-        }
-        uint8_t t_2 = *iop_a_src++;
-        v_block_size = t_2;
-      }
-      if (v_block_size != 3) {
-        self->private_data.s_decode_ae[0].scratch = ((uint32_t)(v_block_size));
-        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
-        if (self->private_data.s_decode_ae[0].scratch >
-            ((uint64_t)(io1_a_src - iop_a_src))) {
-          self->private_data.s_decode_ae[0].scratch -=
-              ((uint64_t)(io1_a_src - iop_a_src));
-          iop_a_src = io1_a_src;
-          status = wuffs_base__suspension__short_read;
-          goto suspend;
-        }
-        iop_a_src += self->private_data.s_decode_ae[0].scratch;
-        goto label_0_break;
-      }
-      {
-        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
-        if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
-          status = wuffs_base__suspension__short_read;
-          goto suspend;
-        }
-        uint8_t t_3 = *iop_a_src++;
-        v_c = t_3;
-      }
-      if (v_c != 1) {
-        self->private_data.s_decode_ae[0].scratch = 2;
-        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
-        if (self->private_data.s_decode_ae[0].scratch >
-            ((uint64_t)(io1_a_src - iop_a_src))) {
-          self->private_data.s_decode_ae[0].scratch -=
-              ((uint64_t)(io1_a_src - iop_a_src));
-          iop_a_src = io1_a_src;
-          status = wuffs_base__suspension__short_read;
-          goto suspend;
-        }
-        iop_a_src += self->private_data.s_decode_ae[0].scratch;
-        goto label_0_break;
-      }
-      {
-        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
-        uint32_t t_4;
-        if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
-          t_4 = ((uint32_t)(wuffs_base__load_u16le(iop_a_src)));
-          iop_a_src += 2;
-        } else {
-          self->private_data.s_decode_ae[0].scratch = 0;
-          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9);
-          while (true) {
-            if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
-              status = wuffs_base__suspension__short_read;
-              goto suspend;
-            }
-            uint64_t* scratch = &self->private_data.s_decode_ae[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 = ((uint32_t)(*scratch));
-              break;
-            }
-            num_bits_4 += 8;
-            *scratch |= ((uint64_t)(num_bits_4)) << 56;
+      if (v_is_animexts || v_is_netscape) {
+        {
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+            status = wuffs_base__suspension__short_read;
+            goto suspend;
           }
+          uint8_t t_2 = *iop_a_src++;
+          v_block_size = t_2;
         }
-        self->private_impl.f_num_loops = t_4;
-      }
-      self->private_impl.f_seen_num_loops = true;
-      if ((0 < self->private_impl.f_num_loops) &&
-          (self->private_impl.f_num_loops <= 65535)) {
-        self->private_impl.f_num_loops += 1;
+        if (v_block_size != 3) {
+          self->private_data.s_decode_ae[0].scratch =
+              ((uint32_t)(v_block_size));
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
+          if (self->private_data.s_decode_ae[0].scratch >
+              ((uint64_t)(io2_a_src - iop_a_src))) {
+            self->private_data.s_decode_ae[0].scratch -=
+                ((uint64_t)(io2_a_src - iop_a_src));
+            iop_a_src = io2_a_src;
+            status = wuffs_base__suspension__short_read;
+            goto suspend;
+          }
+          iop_a_src += self->private_data.s_decode_ae[0].scratch;
+          goto label_0_break;
+        }
+        {
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+            status = wuffs_base__suspension__short_read;
+            goto suspend;
+          }
+          uint8_t t_3 = *iop_a_src++;
+          v_c = t_3;
+        }
+        if (v_c != 1) {
+          self->private_data.s_decode_ae[0].scratch = 2;
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
+          if (self->private_data.s_decode_ae[0].scratch >
+              ((uint64_t)(io2_a_src - iop_a_src))) {
+            self->private_data.s_decode_ae[0].scratch -=
+                ((uint64_t)(io2_a_src - iop_a_src));
+            iop_a_src = io2_a_src;
+            status = wuffs_base__suspension__short_read;
+            goto suspend;
+          }
+          iop_a_src += self->private_data.s_decode_ae[0].scratch;
+          goto label_0_break;
+        }
+        {
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
+          uint32_t t_4;
+          if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
+            t_4 = ((uint32_t)(wuffs_base__load_u16le(iop_a_src)));
+            iop_a_src += 2;
+          } else {
+            self->private_data.s_decode_ae[0].scratch = 0;
+            WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9);
+            while (true) {
+              if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+                status = wuffs_base__suspension__short_read;
+                goto suspend;
+              }
+              uint64_t* scratch = &self->private_data.s_decode_ae[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 = ((uint32_t)(*scratch));
+                break;
+              }
+              num_bits_4 += 8;
+              *scratch |= ((uint64_t)(num_bits_4)) << 56;
+            }
+          }
+          self->private_impl.f_num_loops = t_4;
+        }
+        self->private_impl.f_seen_num_loops = true;
+        if ((0 < self->private_impl.f_num_loops) &&
+            (self->private_impl.f_num_loops <= 65535)) {
+          self->private_impl.f_num_loops += 1;
+        }
+      } else if (self->private_impl.f_ignore_metadata) {
+      } else if (v_is_iccp && self->private_impl.f_report_metadata_iccp) {
+        while (((uint64_t)(io2_a_src - iop_a_src)) <= 0) {
+          status = wuffs_base__suspension__short_read;
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(10);
+        }
+        self->private_impl.f_metadata_chunk_length_value =
+            ((uint64_t)(wuffs_base__load_u8be(iop_a_src)));
+        (iop_a_src += 1, wuffs_base__make_empty_struct());
+        self->private_impl.f_metadata_fourcc_value = 1229144912;
+        self->private_impl.f_metadata_io_position = wuffs_base__u64__sat_add(
+            (a_src ? wuffs_base__u64__sat_add(
+                         a_src->meta.pos,
+                         ((uint64_t)(iop_a_src - a_src->data.ptr)))
+                   : 0),
+            self->private_impl.f_metadata_chunk_length_value);
+        self->private_impl.f_call_sequence = 1;
+        status = wuffs_base__warning__metadata_reported;
+        goto ok;
+      } else if (v_is_xmp && self->private_impl.f_report_metadata_xmp) {
+        while (((uint64_t)(io2_a_src - iop_a_src)) <= 0) {
+          status = wuffs_base__suspension__short_read;
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(11);
+        }
+        self->private_impl.f_metadata_chunk_length_value =
+            (((uint64_t)(wuffs_base__load_u8be(iop_a_src))) + 1);
+        self->private_impl.f_metadata_fourcc_value = 1481461792;
+        self->private_impl.f_metadata_io_position = wuffs_base__u64__sat_add(
+            (a_src ? wuffs_base__u64__sat_add(
+                         a_src->meta.pos,
+                         ((uint64_t)(iop_a_src - a_src->data.ptr)))
+                   : 0),
+            self->private_impl.f_metadata_chunk_length_value);
+        self->private_impl.f_call_sequence = 1;
+        status = wuffs_base__warning__metadata_reported;
+        goto ok;
       }
       goto label_0_break;
     }
   label_0_break:;
-    if (a_src.private_impl.buf) {
-      a_src.private_impl.buf->meta.ri =
-          ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+    if (a_src) {
+      a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
     }
-    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(10);
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(12);
     status = wuffs_gif__decoder__skip_blocks(self, a_src);
-    if (a_src.private_impl.buf) {
-      iop_a_src =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
+    if (a_src) {
+      iop_a_src = a_src->data.ptr + a_src->meta.ri;
     }
     if (status) {
       goto suspend;
@@ -9791,16 +10276,18 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_ae[0] = coro_susp_point;
+  self->private_impl.p_decode_ae[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
   self->private_data.s_decode_ae[0].v_block_size = v_block_size;
-  self->private_data.s_decode_ae[0].v_not_animexts = v_not_animexts;
-  self->private_data.s_decode_ae[0].v_not_netscape = v_not_netscape;
+  self->private_data.s_decode_ae[0].v_is_animexts = v_is_animexts;
+  self->private_data.s_decode_ae[0].v_is_netscape = v_is_netscape;
+  self->private_data.s_decode_ae[0].v_is_iccp = v_is_iccp;
+  self->private_data.s_decode_ae[0].v_is_xmp = v_is_xmp;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -9810,7 +10297,7 @@
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_gc(wuffs_gif__decoder* self,
-                              wuffs_base__io_reader a_src) {
+                              wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint8_t v_c = 0;
@@ -9820,16 +10307,12 @@
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_gc[0];
@@ -9838,13 +10321,9 @@
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
-    if (self->private_impl.f_seen_graphic_control) {
-      status = wuffs_gif__error__bad_graphic_control;
-      goto exit;
-    }
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -9857,7 +10336,7 @@
     }
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -9876,14 +10355,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
       uint16_t t_2;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
         t_2 = wuffs_base__load_u16le(iop_a_src);
         iop_a_src += 2;
       } else {
         self->private_data.s_decode_gc[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -9906,7 +10385,7 @@
         (((uint64_t)(v_gc_duration_centiseconds)) * 7056000);
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -9915,7 +10394,7 @@
     }
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -9926,7 +10405,6 @@
       status = wuffs_gif__error__bad_graphic_control;
       goto exit;
     }
-    self->private_impl.f_seen_graphic_control = true;
 
     goto ok;
   ok:
@@ -9936,13 +10414,13 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_gc[0] = coro_susp_point;
+  self->private_impl.p_decode_gc[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -9952,22 +10430,18 @@
 
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_id_part0(wuffs_gif__decoder* self,
-                                    wuffs_base__io_reader a_src) {
+                                    wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_id_part0[0];
@@ -9979,14 +10453,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
       uint32_t t_0;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
         t_0 = ((uint32_t)(wuffs_base__load_u16le(iop_a_src)));
         iop_a_src += 2;
       } else {
         self->private_data.s_decode_id_part0[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -10008,14 +10482,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
       uint32_t t_1;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
         t_1 = ((uint32_t)(wuffs_base__load_u16le(iop_a_src)));
         iop_a_src += 2;
       } else {
         self->private_data.s_decode_id_part0[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -10037,14 +10511,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
       uint32_t t_2;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
         t_2 = ((uint32_t)(wuffs_base__load_u16le(iop_a_src)));
         iop_a_src += 2;
       } else {
         self->private_data.s_decode_id_part0[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -10067,14 +10541,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
       uint32_t t_3;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
         t_3 = ((uint32_t)(wuffs_base__load_u16le(iop_a_src)));
         iop_a_src += 2;
       } else {
         self->private_data.s_decode_id_part0[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -10096,7 +10570,8 @@
     self->private_impl.f_frame_rect_y1 += self->private_impl.f_frame_rect_y0;
     self->private_impl.f_dst_x = self->private_impl.f_frame_rect_x0;
     self->private_impl.f_dst_y = self->private_impl.f_frame_rect_y0;
-    if (self->private_impl.f_call_sequence == 0) {
+    if ((self->private_impl.f_call_sequence == 0) &&
+        !self->private_impl.f_quirk_enabled_image_bounds_are_strict) {
       self->private_impl.f_width = wuffs_base__u32__max(
           self->private_impl.f_width, self->private_impl.f_frame_rect_x1);
       self->private_impl.f_height = wuffs_base__u32__max(
@@ -10111,13 +10586,13 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_id_part0[0] = coro_susp_point;
+  self->private_impl.p_decode_id_part0[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -10128,33 +10603,32 @@
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_id_part1(wuffs_gif__decoder* self,
                                     wuffs_base__pixel_buffer* a_dst,
-                                    wuffs_base__io_reader a_src) {
+                                    wuffs_base__io_buffer* a_src) {
   wuffs_base__status status = NULL;
 
   uint8_t v_flags = 0;
+  uint8_t v_which_palette = 0;
   uint32_t v_num_palette_entries = 0;
   uint32_t v_i = 0;
   uint32_t v_argb = 0;
   wuffs_base__slice_u8 v_dst_palette = {0};
+  wuffs_base__status v_status = NULL;
   uint8_t v_lw = 0;
 
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_id_part1[0];
   if (coro_susp_point) {
+    v_which_palette = self->private_data.s_decode_id_part1[0].v_which_palette;
     v_num_palette_entries =
         self->private_data.s_decode_id_part1[0].v_num_palette_entries;
     v_i = self->private_data.s_decode_id_part1[0].v_i;
@@ -10164,7 +10638,7 @@
 
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -10176,7 +10650,7 @@
     } else {
       self->private_impl.f_interlace = 0;
     }
-    self->private_impl.f_which_palette = 1;
+    v_which_palette = 1;
     if ((v_flags & 128) != 0) {
       v_num_palette_entries = (((uint32_t)(1)) << (1 + (v_flags & 7)));
       v_i = 0;
@@ -10184,14 +10658,14 @@
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
           uint32_t t_1;
-          if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 3)) {
+          if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 3)) {
             t_1 = ((uint32_t)(wuffs_base__load_u24be(iop_a_src)));
             iop_a_src += 3;
           } else {
             self->private_data.s_decode_id_part1[0].scratch = 0;
             WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
             while (true) {
-              if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+              if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
                 status = wuffs_base__suspension__short_read;
                 goto suspend;
               }
@@ -10229,12 +10703,16 @@
         self->private_data.f_palettes[1][((4 * v_i) + 3)] = 255;
         v_i += 1;
       }
+    } else if (self->private_impl.f_quirk_enabled_reject_empty_palette &&
+               !self->private_impl.f_has_global_palette) {
+      status = wuffs_gif__error__bad_palette;
+      goto exit;
     } else if (self->private_impl.f_gc_has_transparent_index) {
       wuffs_base__slice_u8__copy_from_slice(
           wuffs_base__make_slice_u8(self->private_data.f_palettes[1], 1024),
           wuffs_base__make_slice_u8(self->private_data.f_palettes[0], 1024));
     } else {
-      self->private_impl.f_which_palette = 0;
+      v_which_palette = 0;
     }
     if (self->private_impl.f_gc_has_transparent_index) {
       self->private_data.f_palettes[1][(
@@ -10255,13 +10733,22 @@
       v_dst_palette =
           wuffs_base__make_slice_u8(self->private_data.f_dst_palette, 1024);
     }
-    wuffs_base__pixel_swizzler__prepare(
+    v_status = wuffs_base__pixel_swizzler__prepare(
         &self->private_impl.f_swizzler,
         wuffs_base__pixel_buffer__pixel_format(a_dst), v_dst_palette,
         1191444488,
         wuffs_base__make_slice_u8(
-            self->private_data.f_palettes[self->private_impl.f_which_palette],
-            1024));
+            self->private_data.f_palettes[v_which_palette], 1024));
+    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__error__cannot_return_a_suspension;
+        goto exit;
+      }
+      goto ok;
+    }
     if (self->private_impl.f_previous_lzw_decode_ended_abruptly) {
       wuffs_base__ignore_status(wuffs_lzw__decoder__initialize(
           &self->private_data.f_lzw, sizeof(wuffs_lzw__decoder), WUFFS_VERSION,
@@ -10269,14 +10756,14 @@
     }
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
       uint8_t t_2 = *iop_a_src++;
       v_lw = t_2;
     }
-    if ((v_lw < 2) || (8 < v_lw)) {
+    if (v_lw > 8) {
       status = wuffs_gif__error__bad_literal_width;
       goto exit;
     }
@@ -10292,16 +10779,17 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_id_part1[0] = coro_susp_point;
+  self->private_impl.p_decode_id_part1[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
+  self->private_data.s_decode_id_part1[0].v_which_palette = v_which_palette;
   self->private_data.s_decode_id_part1[0].v_num_palette_entries =
       v_num_palette_entries;
   self->private_data.s_decode_id_part1[0].v_i = v_i;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -10312,7 +10800,7 @@
 static wuffs_base__status  //
 wuffs_gif__decoder__decode_id_part2(wuffs_gif__decoder* self,
                                     wuffs_base__pixel_buffer* a_dst,
-                                    wuffs_base__io_reader a_src,
+                                    wuffs_base__io_buffer* a_src,
                                     wuffs_base__slice_u8 a_workbuf) {
   wuffs_base__status status = NULL;
 
@@ -10320,11 +10808,13 @@
   bool v_need_block_size = false;
   uint64_t v_n_compressed = 0;
   wuffs_base__slice_u8 v_compressed = {0};
-  wuffs_base__io_reader v_r = wuffs_base__null_io_reader();
-  wuffs_base__io_buffer u_r WUFFS_BASE__POTENTIALLY_UNUSED =
-      wuffs_base__null_io_buffer();
+  wuffs_base__io_buffer u_r = wuffs_base__null_io_buffer();
+  wuffs_base__io_buffer* v_r = &u_r;
   uint8_t* iop_v_r WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  uint8_t* io0_v_r WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_v_r WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  uint8_t* io2_v_r WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  uint64_t v_mark = 0;
   wuffs_base__status v_lzw_status = NULL;
   wuffs_base__status v_copy_status = NULL;
   wuffs_base__slice_u8 v_uncompressed = {0};
@@ -10332,16 +10822,12 @@
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_id_part2[0];
@@ -10361,7 +10847,7 @@
         v_need_block_size = false;
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -10372,7 +10858,7 @@
       if (v_block_size == 0) {
         goto label_0_break;
       }
-      while (((uint64_t)(io1_a_src - iop_a_src)) == 0) {
+      while (((uint64_t)(io2_a_src - iop_a_src)) == 0) {
         status = wuffs_base__suspension__short_read;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(2);
       }
@@ -10383,12 +10869,12 @@
       }
       while (self->private_impl.f_compressed_wi <= 3841) {
         v_n_compressed = wuffs_base__u64__min(
-            v_block_size, ((uint64_t)(io1_a_src - iop_a_src)));
+            v_block_size, ((uint64_t)(io2_a_src - iop_a_src)));
         if (v_n_compressed <= 0) {
           goto label_1_break;
         }
         v_compressed =
-            wuffs_base__io_reader__take(&iop_a_src, io1_a_src, v_n_compressed);
+            wuffs_base__io_reader__take(&iop_a_src, io2_a_src, v_n_compressed);
         wuffs_base__slice_u8__copy_from_slice(
             wuffs_base__slice_u8__subslice_i(
                 wuffs_base__make_slice_u8(self->private_data.f_compressed,
@@ -10401,7 +10887,7 @@
         if (v_block_size > 0) {
           goto label_1_break;
         }
-        if (((uint64_t)(io1_a_src - iop_a_src)) <= 0) {
+        if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) {
           v_need_block_size = true;
           goto label_1_break;
         }
@@ -10422,16 +10908,19 @@
           goto exit;
         }
         {
-          wuffs_base__io_reader o_0_v_r = v_r;
+          wuffs_base__io_buffer* o_0_v_r = v_r;
           uint8_t* o_0_iop_v_r = iop_v_r;
+          uint8_t* o_0_io0_v_r = io0_v_r;
           uint8_t* o_0_io1_v_r = io1_v_r;
-          wuffs_base__io_reader__set(
-              &v_r, &u_r, &iop_v_r, &io1_v_r,
+          uint8_t* o_0_io2_v_r = io2_v_r;
+          v_r = wuffs_base__io_reader__set(
+              &u_r, &iop_v_r, &io0_v_r, &io1_v_r, &io2_v_r,
               wuffs_base__slice_u8__subslice_ij(
                   wuffs_base__make_slice_u8(self->private_data.f_compressed,
                                             4096),
                   self->private_impl.f_compressed_ri,
                   self->private_impl.f_compressed_wi));
+          v_mark = ((uint64_t)(iop_v_r - io0_v_r));
           {
             u_r.meta.ri = ((size_t)(iop_v_r - u_r.data.ptr));
             wuffs_base__status t_1 = wuffs_lzw__decoder__decode_io_writer(
@@ -10443,10 +10932,13 @@
           }
           wuffs_base__u64__sat_add_indirect(
               &self->private_impl.f_compressed_ri,
-              ((uint64_t)(iop_v_r - v_r.private_impl.mark)));
+              wuffs_base__io__count_since(v_mark,
+                                          ((uint64_t)(iop_v_r - io0_v_r))));
           v_r = o_0_v_r;
           iop_v_r = o_0_iop_v_r;
+          io0_v_r = o_0_io0_v_r;
           io1_v_r = o_0_io1_v_r;
+          io2_v_r = o_0_io2_v_r;
         }
         v_uncompressed = wuffs_lzw__decoder__flush(&self->private_data.f_lzw);
         if (((uint64_t)(v_uncompressed.len)) > 0) {
@@ -10464,23 +10956,21 @@
                 ((uint32_t)(v_block_size));
             WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
             if (self->private_data.s_decode_id_part2[0].scratch >
-                ((uint64_t)(io1_a_src - iop_a_src))) {
+                ((uint64_t)(io2_a_src - iop_a_src))) {
               self->private_data.s_decode_id_part2[0].scratch -=
-                  ((uint64_t)(io1_a_src - iop_a_src));
-              iop_a_src = io1_a_src;
+                  ((uint64_t)(io2_a_src - iop_a_src));
+              iop_a_src = io2_a_src;
               status = wuffs_base__suspension__short_read;
               goto suspend;
             }
             iop_a_src += self->private_data.s_decode_id_part2[0].scratch;
-            if (a_src.private_impl.buf) {
-              a_src.private_impl.buf->meta.ri =
-                  ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+            if (a_src) {
+              a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
             }
             WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
             status = wuffs_gif__decoder__skip_blocks(self, a_src);
-            if (a_src.private_impl.buf) {
-              iop_a_src = a_src.private_impl.buf->data.ptr +
-                          a_src.private_impl.buf->meta.ri;
+            if (a_src) {
+              iop_a_src = a_src->data.ptr + a_src->meta.ri;
             }
             if (status) {
               goto suspend;
@@ -10522,16 +11012,16 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_id_part2[0] = coro_susp_point;
+  self->private_impl.p_decode_id_part2[0] =
+      wuffs_base__status__is_suspension(status) ? coro_susp_point : 0;
   self->private_data.s_decode_id_part2[0].v_block_size = v_block_size;
   self->private_data.s_decode_id_part2[0].v_need_block_size = v_need_block_size;
   self->private_data.s_decode_id_part2[0].v_lzw_status = v_lzw_status;
 
   goto exit;
 exit:
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   return status;
@@ -10548,23 +11038,29 @@
   uint64_t v_n = 0;
   uint64_t v_src_ri = 0;
   uint32_t v_bytes_per_pixel = 0;
-  uint32_t v_pixfmt = 0;
+  uint32_t v_pixfmt_channels = 0;
   wuffs_base__table_u8 v_tab = {0};
   uint64_t v_i = 0;
   uint64_t v_j = 0;
 
-  v_bytes_per_pixel = 1;
-  v_pixfmt = wuffs_base__pixel_buffer__pixel_format(a_pb);
-  if ((v_pixfmt == 1157662856) || (v_pixfmt == 1426098312) ||
-      (v_pixfmt == 1174440072) || (v_pixfmt == 1442875528) ||
-      (v_pixfmt == 1191217288) || (v_pixfmt == 1459652744)) {
+  v_pixfmt_channels = (wuffs_base__pixel_buffer__pixel_format(a_pb) & 65535);
+  if (v_pixfmt_channels == 34952) {
     v_bytes_per_pixel = 4;
+  } else if (v_pixfmt_channels == 2184) {
+    v_bytes_per_pixel = 3;
+  } else if (v_pixfmt_channels == 8) {
+    v_bytes_per_pixel = 1;
+  } else {
+    return wuffs_base__error__unsupported_option;
   }
   v_tab = wuffs_base__pixel_buffer__plane(a_pb, 0);
 label_0_continue:;
   while (v_src_ri < ((uint64_t)(a_src.len))) {
     v_src = wuffs_base__slice_u8__subslice_i(a_src, v_src_ri);
     if (self->private_impl.f_dst_y >= self->private_impl.f_frame_rect_y1) {
+      if (self->private_impl.f_quirk_enabled_ignore_too_much_pixel_data) {
+        return NULL;
+      }
       return wuffs_base__error__too_much_data;
     }
     v_dst = wuffs_base__table_u8__row(v_tab, self->private_impl.f_dst_y);
@@ -10578,7 +11074,7 @@
       } else {
         v_dst = wuffs_base__slice_u8__subslice_i(v_dst, v_i);
       }
-      v_n = wuffs_base__pixel_swizzler__swizzle_packed(
+      v_n = wuffs_base__pixel_swizzler__swizzle_interleaved(
           &self->private_impl.f_swizzler, v_dst,
           wuffs_base__make_slice_u8(self->private_data.f_dst_palette, 1024),
           v_src);
@@ -10779,8 +11275,8 @@
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_gzip__decoder__decode_io_writer(wuffs_gzip__decoder* self,
-                                      wuffs_base__io_writer a_dst,
-                                      wuffs_base__io_reader a_src,
+                                      wuffs_base__io_buffer* a_dst,
+                                      wuffs_base__io_buffer* a_src,
                                       wuffs_base__slice_u8 a_workbuf) {
   if (!self) {
     return wuffs_base__error__bad_receiver;
@@ -10790,6 +11286,10 @@
                ? 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__error__bad_argument;
+  }
   if ((self->private_impl.active_coroutine != 0) &&
       (self->private_impl.active_coroutine != 1)) {
     self->private_impl.magic = WUFFS_BASE__DISABLED;
@@ -10801,6 +11301,7 @@
   uint8_t v_c = 0;
   uint8_t v_flags = 0;
   uint16_t v_xlen = 0;
+  uint64_t v_mark = 0;
   uint32_t v_checksum_got = 0;
   uint32_t v_decoded_length_got = 0;
   wuffs_base__status v_status = NULL;
@@ -10810,33 +11311,25 @@
   uint8_t* iop_a_dst = NULL;
   uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_dst.private_impl.buf) {
-    iop_a_dst =
-        a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->meta.wi;
-    if (!a_dst.private_impl.mark) {
-      a_dst.private_impl.mark = iop_a_dst;
-      a_dst.private_impl.limit =
-          a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->data.len;
+  uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_dst) {
+    io0_a_dst = a_dst->data.ptr;
+    io1_a_dst = io0_a_dst + a_dst->meta.wi;
+    iop_a_dst = io1_a_dst;
+    io2_a_dst = io0_a_dst + a_dst->data.len;
+    if (a_dst->meta.closed) {
+      io2_a_dst = iop_a_dst;
     }
-    if (a_dst.private_impl.buf->meta.closed) {
-      a_dst.private_impl.limit = iop_a_dst;
-    }
-    io0_a_dst = a_dst.private_impl.mark;
-    io1_a_dst = a_dst.private_impl.limit;
   }
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_io_writer[0];
@@ -10852,7 +11345,7 @@
 
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -10865,7 +11358,7 @@
     }
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -10878,7 +11371,7 @@
     }
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -10891,7 +11384,7 @@
     }
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
-      if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -10901,10 +11394,10 @@
     self->private_data.s_decode_io_writer[0].scratch = 6;
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
     if (self->private_data.s_decode_io_writer[0].scratch >
-        ((uint64_t)(io1_a_src - iop_a_src))) {
+        ((uint64_t)(io2_a_src - iop_a_src))) {
       self->private_data.s_decode_io_writer[0].scratch -=
-          ((uint64_t)(io1_a_src - iop_a_src));
-      iop_a_src = io1_a_src;
+          ((uint64_t)(io2_a_src - iop_a_src));
+      iop_a_src = io2_a_src;
       status = wuffs_base__suspension__short_read;
       goto suspend;
     }
@@ -10913,14 +11406,14 @@
       {
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
         uint16_t t_4;
-        if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
+        if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
           t_4 = wuffs_base__load_u16le(iop_a_src);
           iop_a_src += 2;
         } else {
           self->private_data.s_decode_io_writer[0].scratch = 0;
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
           while (true) {
-            if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+            if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
               status = wuffs_base__suspension__short_read;
               goto suspend;
             }
@@ -10943,10 +11436,10 @@
       self->private_data.s_decode_io_writer[0].scratch = ((uint32_t)(v_xlen));
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
       if (self->private_data.s_decode_io_writer[0].scratch >
-          ((uint64_t)(io1_a_src - iop_a_src))) {
+          ((uint64_t)(io2_a_src - iop_a_src))) {
         self->private_data.s_decode_io_writer[0].scratch -=
-            ((uint64_t)(io1_a_src - iop_a_src));
-        iop_a_src = io1_a_src;
+            ((uint64_t)(io2_a_src - iop_a_src));
+        iop_a_src = io2_a_src;
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -10956,7 +11449,7 @@
       while (true) {
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9);
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -10973,7 +11466,7 @@
       while (true) {
         {
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(10);
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -10990,10 +11483,10 @@
       self->private_data.s_decode_io_writer[0].scratch = 2;
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(11);
       if (self->private_data.s_decode_io_writer[0].scratch >
-          ((uint64_t)(io1_a_src - iop_a_src))) {
+          ((uint64_t)(io2_a_src - iop_a_src))) {
         self->private_data.s_decode_io_writer[0].scratch -=
-            ((uint64_t)(io1_a_src - iop_a_src));
-        iop_a_src = io1_a_src;
+            ((uint64_t)(io2_a_src - iop_a_src));
+        iop_a_src = io2_a_src;
         status = wuffs_base__suspension__short_read;
         goto suspend;
       }
@@ -11004,36 +11497,33 @@
       goto exit;
     }
     while (true) {
-      wuffs_base__io_writer__set_mark(&a_dst, iop_a_dst);
+      v_mark = ((uint64_t)(iop_a_dst - io0_a_dst));
       {
-        if (a_dst.private_impl.buf) {
-          a_dst.private_impl.buf->meta.wi =
-              ((size_t)(iop_a_dst - a_dst.private_impl.buf->data.ptr));
+        if (a_dst) {
+          a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
         }
-        if (a_src.private_impl.buf) {
-          a_src.private_impl.buf->meta.ri =
-              ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
         }
         wuffs_base__status t_7 = wuffs_deflate__decoder__decode_io_writer(
             &self->private_data.f_flate, a_dst, a_src, a_workbuf);
-        if (a_dst.private_impl.buf) {
-          iop_a_dst = a_dst.private_impl.buf->data.ptr +
-                      a_dst.private_impl.buf->meta.wi;
+        if (a_dst) {
+          iop_a_dst = a_dst->data.ptr + a_dst->meta.wi;
         }
-        if (a_src.private_impl.buf) {
-          iop_a_src = a_src.private_impl.buf->data.ptr +
-                      a_src.private_impl.buf->meta.ri;
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
         }
         v_status = t_7;
       }
       if (!self->private_impl.f_ignore_checksum) {
         v_checksum_got = wuffs_crc32__ieee_hasher__update(
             &self->private_data.f_checksum,
-            wuffs_base__make_slice_u8(
-                a_dst.private_impl.mark,
-                (size_t)(iop_a_dst - a_dst.private_impl.mark)));
-        v_decoded_length_got += ((uint32_t)(
-            (((uint64_t)(iop_a_dst - a_dst.private_impl.mark)) & 4294967295)));
+            wuffs_base__io__since(v_mark, ((uint64_t)(iop_a_dst - io0_a_dst)),
+                                  io0_a_dst));
+        v_decoded_length_got +=
+            ((uint32_t)((wuffs_base__io__count_since(
+                             v_mark, ((uint64_t)(iop_a_dst - io0_a_dst))) &
+                         4294967295)));
       }
       if (wuffs_base__status__is_ok(v_status)) {
         goto label_2_break;
@@ -11045,14 +11535,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(13);
       uint32_t t_8;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 4)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) {
         t_8 = wuffs_base__load_u32le(iop_a_src);
         iop_a_src += 4;
       } else {
         self->private_data.s_decode_io_writer[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(14);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -11074,14 +11564,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(15);
       uint32_t t_9;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 4)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) {
         t_9 = wuffs_base__load_u32le(iop_a_src);
         iop_a_src += 4;
       } else {
         self->private_data.s_decode_io_writer[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(16);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -11115,8 +11605,10 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_io_writer[0] = coro_susp_point;
-  self->private_impl.active_coroutine = 1;
+  self->private_impl.p_decode_io_writer[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_io_writer[0].v_flags = v_flags;
   self->private_data.s_decode_io_writer[0].v_checksum_got = v_checksum_got;
   self->private_data.s_decode_io_writer[0].v_decoded_length_got =
@@ -11125,13 +11617,11 @@
 
   goto exit;
 exit:
-  if (a_dst.private_impl.buf) {
-    a_dst.private_impl.buf->meta.wi =
-        ((size_t)(iop_a_dst - a_dst.private_impl.buf->data.ptr));
+  if (a_dst) {
+    a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
   }
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   if (wuffs_base__status__is_error(status)) {
@@ -11266,8 +11756,8 @@
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
 wuffs_zlib__decoder__decode_io_writer(wuffs_zlib__decoder* self,
-                                      wuffs_base__io_writer a_dst,
-                                      wuffs_base__io_reader a_src,
+                                      wuffs_base__io_buffer* a_dst,
+                                      wuffs_base__io_buffer* a_src,
                                       wuffs_base__slice_u8 a_workbuf) {
   if (!self) {
     return wuffs_base__error__bad_receiver;
@@ -11277,6 +11767,10 @@
                ? 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__error__bad_argument;
+  }
   if ((self->private_impl.active_coroutine != 0) &&
       (self->private_impl.active_coroutine != 1)) {
     self->private_impl.magic = WUFFS_BASE__DISABLED;
@@ -11289,37 +11783,30 @@
   uint32_t v_checksum_got = 0;
   wuffs_base__status v_status = NULL;
   uint32_t v_checksum_want = 0;
+  uint64_t v_mark = 0;
 
   uint8_t* iop_a_dst = NULL;
   uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_dst.private_impl.buf) {
-    iop_a_dst =
-        a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->meta.wi;
-    if (!a_dst.private_impl.mark) {
-      a_dst.private_impl.mark = iop_a_dst;
-      a_dst.private_impl.limit =
-          a_dst.private_impl.buf->data.ptr + a_dst.private_impl.buf->data.len;
+  uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_dst) {
+    io0_a_dst = a_dst->data.ptr;
+    io1_a_dst = io0_a_dst + a_dst->meta.wi;
+    iop_a_dst = io1_a_dst;
+    io2_a_dst = io0_a_dst + a_dst->data.len;
+    if (a_dst->meta.closed) {
+      io2_a_dst = iop_a_dst;
     }
-    if (a_dst.private_impl.buf->meta.closed) {
-      a_dst.private_impl.limit = iop_a_dst;
-    }
-    io0_a_dst = a_dst.private_impl.mark;
-    io1_a_dst = a_dst.private_impl.limit;
   }
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
-  if (a_src.private_impl.buf) {
-    iop_a_src =
-        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-    if (!a_src.private_impl.mark) {
-      a_src.private_impl.mark = iop_a_src;
-      a_src.private_impl.limit =
-          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
-    }
-    io0_a_src = a_src.private_impl.mark;
-    io1_a_src = a_src.private_impl.limit;
+  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_io_writer[0];
@@ -11332,14 +11819,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
       uint16_t t_0;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) {
         t_0 = wuffs_base__load_u16be(iop_a_src);
         iop_a_src += 2;
       } else {
         self->private_data.s_decode_io_writer[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -11375,34 +11862,29 @@
       goto exit;
     }
     while (true) {
-      wuffs_base__io_writer__set_mark(&a_dst, iop_a_dst);
+      v_mark = ((uint64_t)(iop_a_dst - io0_a_dst));
       {
-        if (a_dst.private_impl.buf) {
-          a_dst.private_impl.buf->meta.wi =
-              ((size_t)(iop_a_dst - a_dst.private_impl.buf->data.ptr));
+        if (a_dst) {
+          a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
         }
-        if (a_src.private_impl.buf) {
-          a_src.private_impl.buf->meta.ri =
-              ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
         }
         wuffs_base__status t_1 = wuffs_deflate__decoder__decode_io_writer(
             &self->private_data.f_flate, a_dst, a_src, a_workbuf);
-        if (a_dst.private_impl.buf) {
-          iop_a_dst = a_dst.private_impl.buf->data.ptr +
-                      a_dst.private_impl.buf->meta.wi;
+        if (a_dst) {
+          iop_a_dst = a_dst->data.ptr + a_dst->meta.wi;
         }
-        if (a_src.private_impl.buf) {
-          iop_a_src = a_src.private_impl.buf->data.ptr +
-                      a_src.private_impl.buf->meta.ri;
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
         }
         v_status = t_1;
       }
       if (!self->private_impl.f_ignore_checksum) {
         v_checksum_got = wuffs_adler32__hasher__update(
             &self->private_data.f_checksum,
-            wuffs_base__make_slice_u8(
-                a_dst.private_impl.mark,
-                (size_t)(iop_a_dst - a_dst.private_impl.mark)));
+            wuffs_base__io__since(v_mark, ((uint64_t)(iop_a_dst - io0_a_dst)),
+                                  io0_a_dst));
       }
       if (wuffs_base__status__is_ok(v_status)) {
         goto label_0_break;
@@ -11414,14 +11896,14 @@
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
       uint32_t t_2;
-      if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 4)) {
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) {
         t_2 = wuffs_base__load_u32be(iop_a_src);
         iop_a_src += 4;
       } else {
         self->private_data.s_decode_io_writer[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
         while (true) {
-          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__suspension__short_read;
             goto suspend;
           }
@@ -11454,19 +11936,19 @@
 
   goto suspend;
 suspend:
-  self->private_impl.p_decode_io_writer[0] = coro_susp_point;
-  self->private_impl.active_coroutine = 1;
+  self->private_impl.p_decode_io_writer[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_io_writer[0].v_checksum_got = v_checksum_got;
 
   goto exit;
 exit:
-  if (a_dst.private_impl.buf) {
-    a_dst.private_impl.buf->meta.wi =
-        ((size_t)(iop_a_dst - a_dst.private_impl.buf->data.ptr));
+  if (a_dst) {
+    a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
   }
-  if (a_src.private_impl.buf) {
-    a_src.private_impl.buf->meta.ri =
-        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
   if (wuffs_base__status__is_error(status)) {
diff --git a/script/make-artificial.go b/script/make-artificial.go
index d436b99..23d37bb 100644
--- a/script/make-artificial.go
+++ b/script/make-artificial.go
@@ -118,6 +118,15 @@
 		}
 	}
 
+	if state == nil {
+		return fmt.Errorf("no 'make' line")
+	} else {
+		state, err = state("")
+		if err != nil {
+			return err
+		}
+	}
+
 	_, err = os.Stdout.Write(out)
 	return err
 }
@@ -224,22 +233,189 @@
 	formats["deflate"] = stateDeflate
 }
 
+var deflateCodeOrder = [19]uint32{
+	16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15,
+}
+
+type deflateHuffmanTable map[uint32]string
+
 var deflateGlobals struct {
 	bncData []byte
 	stream  deflateBitStream
+
+	// Dynamic Huffman state.
+	numLCodes        uint32
+	numDCodes        uint32
+	numCLCodeLengths uint32
+	whichHuffman     uint32
+	// 0=Unused, 1=CodeLength, 2=Length/Literal, 3=Distance.
+	huffmans [4]deflateHuffmanTable
+}
+
+func deflateGlobalsClearDynamicHuffmanState() {
+	deflateGlobals.numLCodes = 0
+	deflateGlobals.numDCodes = 0
+	deflateGlobals.numCLCodeLengths = 0
+	deflateGlobals.whichHuffman = 0
+	deflateGlobals.huffmans = [4]deflateHuffmanTable{}
+}
+
+func deflateGlobalsWriteDynamicHuffmanTables() error {
+	g := &deflateGlobals
+	if (g.numLCodes < 257) || (257+31 < g.numLCodes) {
+		return fmt.Errorf("bad numLCodes: %d", g.numLCodes)
+	}
+	g.stream.writeBits(g.numLCodes-257, 5)
+	if (g.numDCodes < 1) || (1+31 < g.numDCodes) {
+		return fmt.Errorf("bad numDCodes: %d", g.numDCodes)
+	}
+	g.stream.writeBits(g.numDCodes-1, 5)
+	if (g.numCLCodeLengths < 4) || (4+15 < g.numCLCodeLengths) {
+		return fmt.Errorf("bad numCLCodeLengths: %d", g.numCLCodeLengths)
+	}
+	g.stream.writeBits(g.numCLCodeLengths-4, 4)
+
+	// Write the Huffman table for CodeLength.
+	{
+		for i := uint32(0); i < g.numCLCodeLengths; i++ {
+			n := len(g.huffmans[1][deflateCodeOrder[i]])
+			g.stream.writeBits(uint32(n), 3)
+		}
+		for i := g.numCLCodeLengths; i < uint32(len(deflateCodeOrder)); i++ {
+			n := len(g.huffmans[1][deflateCodeOrder[i]])
+			if n > 0 {
+				return fmt.Errorf("short numCLCodeLengths: %d", g.numCLCodeLengths)
+			}
+		}
+	}
+
+	// Write the Huffman tables for Length/Literal and Distance.
+	{
+		numZeroes := uint32(0)
+		for i := uint32(0); i < g.numLCodes+g.numDCodes; i++ {
+			s := ""
+			if i < g.numLCodes {
+				s = g.huffmans[2][i]
+			} else {
+				s = g.huffmans[3][i-g.numLCodes]
+			}
+			if s == "" {
+				numZeroes++
+				continue
+			}
+
+			if err := deflateGlobalsWriteDynamicHuffmanZeroes(numZeroes); err != nil {
+				return err
+			}
+			numZeroes = 0
+
+			deflateGlobalsWriteDynamicHuffmanBits(g.huffmans[1][uint32(len(s))])
+		}
+		if err := deflateGlobalsWriteDynamicHuffmanZeroes(numZeroes); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func deflateGlobalsWriteDynamicHuffmanZeroes(numZeroes uint32) error {
+	g := &deflateGlobals
+	if numZeroes == 0 {
+		return nil
+	}
+
+	if s := g.huffmans[1][18]; s != "" {
+		for numZeroes >= 11 {
+			extra := numZeroes - 11
+			if extra > 127 {
+				extra = 127
+			}
+			deflateGlobalsWriteDynamicHuffmanBits(s)
+			g.stream.writeBits(extra, 7)
+			numZeroes -= 11 + extra
+		}
+	}
+
+	if s := g.huffmans[1][17]; s != "" {
+		for numZeroes >= 3 {
+			extra := numZeroes - 3
+			if extra > 7 {
+				extra = 7
+			}
+			deflateGlobalsWriteDynamicHuffmanBits(s)
+			g.stream.writeBits(extra, 3)
+			numZeroes -= 3 + extra
+		}
+	}
+
+	if s := g.huffmans[1][0]; s != "" {
+		for ; numZeroes > 0; numZeroes-- {
+			deflateGlobalsWriteDynamicHuffmanBits(s)
+		}
+	}
+
+	if numZeroes > 0 {
+		return fmt.Errorf("could not write a run of zero-valued code lengths")
+	}
+	return nil
+}
+
+func deflateGlobalsWriteDynamicHuffmanBits(s string) {
+	g := &deflateGlobals
+	for i := 0; i < len(s); i++ {
+		g.stream.writeBits(uint32(s[i]&1), 1)
+	}
+}
+
+func parseDeflateWhichHuffman(s string) (num uint32, remaining string, ok bool) {
+	if i := strings.IndexByte(s, ' '); i >= 0 {
+		s, remaining = s[:i], s[i+1:]
+		for len(remaining) > 0 && remaining[0] == ' ' {
+			remaining = remaining[1:]
+		}
+	}
+
+	switch s {
+	case "CodeLength":
+		return 1, remaining, true
+	case "Length/Literal":
+		return 2, remaining, true
+	case "Distance":
+		return 3, remaining, true
+	}
+	return 0, "", false
 }
 
 func stateDeflate(line string) (stateFunc, error) {
 	g := &deflateGlobals
 	const (
-		cmdBNC = "blockNoCompression "
+		cmdB   = "bytes "
+		cmdBDH = "blockDynamicHuffman "
 		cmdBFH = "blockFixedHuffman "
+		cmdBNC = "blockNoCompression "
 	)
 	bits := uint32(0)
 	s := ""
 
 	retState := stateFunc(nil)
 	switch {
+	case line == "":
+		g.stream.flush()
+		return stateDeflate, nil
+
+	case strings.HasPrefix(line, cmdB):
+		s := line[len(cmdB):]
+		for s != "" {
+			x, ok := uint32(0), false
+			x, s, ok = parseHex(s)
+			if !ok {
+				return nil, fmt.Errorf("bad stateDeflate command: %q", line)
+			}
+			out = append(out, uint8(x))
+		}
+		return stateDeflate, nil
+
 	case strings.HasPrefix(line, cmdBNC):
 		s = line[len(cmdBNC):]
 		retState = stateDeflateNoCompression
@@ -248,6 +424,10 @@
 		s = line[len(cmdBFH):]
 		retState = stateDeflateFixedHuffman
 		bits |= 1 << 1
+	case strings.HasPrefix(line, cmdBDH):
+		s = line[len(cmdBDH):]
+		retState = stateDeflateDynamicHuffman
+		bits |= 2 << 1
 	default:
 		return nil, fmt.Errorf("bad stateDeflate command: %q", line)
 	}
@@ -291,7 +471,6 @@
 func stateDeflateFixedHuffman(line string) (stateFunc, error) {
 	g := &deflateGlobals
 	if line == "}" {
-		g.stream.flush()
 		return stateDeflate, nil
 	}
 
@@ -301,16 +480,15 @@
 	}
 
 	if lit, ok := deflateParseLiteral(line); ok {
-		// TODO: support "\xAB" escape codes in the script?
 		for i := 0; i < len(lit); i++ {
-			g.stream.writeLCode(uint32(lit[i]))
+			g.stream.writeFixedHuffmanLCode(uint32(lit[i]))
 		}
 		return stateDeflateFixedHuffman, nil
 	}
 
 	if line == "len 3 distCode 31" {
 		lCode, lExtra, lNExtra := deflateEncodeLength(3)
-		g.stream.writeLCode(lCode)
+		g.stream.writeFixedHuffmanLCode(lCode)
 		g.stream.writeBits(lExtra, lNExtra)
 		dCode, dExtra, dNExtra := uint32(31), uint32(0), uint32(0)
 		g.stream.writeBits(reverse(dCode, 5), 5)
@@ -320,7 +498,7 @@
 
 	if l, d, ok := deflateParseLenDist(line); ok {
 		lCode, lExtra, lNExtra := deflateEncodeLength(l)
-		g.stream.writeLCode(lCode)
+		g.stream.writeFixedHuffmanLCode(lCode)
 		g.stream.writeBits(lExtra, lNExtra)
 		dCode, dExtra, dNExtra := deflateEncodeDistance(d)
 		g.stream.writeBits(reverse(dCode, 5), 5)
@@ -331,6 +509,120 @@
 	return nil, fmt.Errorf("bad stateDeflateFixedHuffman command: %q", line)
 }
 
+func stateDeflateDynamicHuffman(line string) (stateFunc, error) {
+	g := &deflateGlobals
+	const (
+		cmdH     = "huffman "
+		cmdNCLCL = "numCLCodeLengths "
+		cmdNDC   = "numDCodes "
+		cmdNLC   = "numLCodes "
+	)
+	switch {
+	case line == "}":
+		deflateGlobalsClearDynamicHuffmanState()
+		return stateDeflate, nil
+
+	case strings.HasPrefix(line, cmdH):
+		s := line[len(cmdH):]
+		n, s, ok := parseDeflateWhichHuffman(s)
+		if !ok {
+			break
+		}
+		g.whichHuffman = n
+		return stateDeflateDynamicHuffmanHuffman, nil
+
+	case strings.HasPrefix(line, cmdNLC):
+		s := line[len(cmdNLC):]
+		n, s, ok := parseNum(s)
+		if !ok {
+			break
+		}
+		g.numLCodes = n
+		return stateDeflateDynamicHuffman, nil
+
+	case strings.HasPrefix(line, cmdNDC):
+		s := line[len(cmdNDC):]
+		n, s, ok := parseNum(s)
+		if !ok {
+			break
+		}
+		g.numDCodes = n
+		return stateDeflateDynamicHuffman, nil
+
+	case strings.HasPrefix(line, cmdNCLCL):
+		s := line[len(cmdNCLCL):]
+		n, s, ok := parseNum(s)
+		if !ok {
+			break
+		}
+		g.numCLCodeLengths = n
+		return stateDeflateDynamicHuffman, nil
+	}
+
+	if lit, ok := deflateParseLiteral(line); ok {
+		for i := 0; i < len(lit); i++ {
+			s := g.huffmans[2][uint32(lit[i])]
+			if s == "" {
+				return nil, fmt.Errorf("no code for literal %q (%d)", lit[i:i+1], lit[i])
+			}
+			deflateGlobalsWriteDynamicHuffmanBits(s)
+		}
+		return stateDeflateDynamicHuffman, nil
+	} else if line == "endOfBlock" {
+		s := g.huffmans[2][256]
+		if s == "" {
+			return nil, fmt.Errorf("no code for end-of-block (256)")
+		}
+		deflateGlobalsWriteDynamicHuffmanBits(s)
+		return stateDeflateDynamicHuffman, nil
+	}
+
+	return nil, fmt.Errorf("bad stateDeflateDynamicHuffman command: %q", line)
+}
+
+func stateDeflateDynamicHuffmanHuffman(line string) (stateFunc, error) {
+	g := &deflateGlobals
+outer:
+	switch {
+	case line == "}":
+		g.whichHuffman = 0
+
+		// If we have all three Huffman tables, write them.
+		for i := 1; ; i++ {
+			if i == 4 {
+				if err := deflateGlobalsWriteDynamicHuffmanTables(); err != nil {
+					return nil, err
+				}
+				break
+			}
+			if g.huffmans[i] == nil {
+				break
+			}
+		}
+
+		return stateDeflateDynamicHuffman, nil
+
+	default:
+		s := line
+		n, s, ok := parseNum(s)
+		if !ok || s == "" {
+			break
+		}
+		for i := 0; i < len(s); i++ {
+			if c := s[i]; c != '0' && c != '1' {
+				break outer
+			}
+		}
+		if g.huffmans[g.whichHuffman] == nil {
+			g.huffmans[g.whichHuffman] = deflateHuffmanTable{}
+		}
+		g.huffmans[g.whichHuffman][n] = s
+		return stateDeflateDynamicHuffmanHuffman, nil
+	}
+
+	return nil, fmt.Errorf("bad stateDeflateDynamicHuffmanHuffman command: %q", line)
+}
+
 type deflateBitStream struct {
 	bits  uint32
 	nBits uint32 // Always within [0, 7].
@@ -355,7 +647,7 @@
 	out = append(out, b...)
 }
 
-func (z *deflateBitStream) writeLCode(lCode uint32) {
+func (z *deflateBitStream) writeFixedHuffmanLCode(lCode uint32) {
 	switch {
 	case lCode < 144: // 0b._0011_0000 through 0b._1011_1111
 		lCode += 0x030
@@ -459,6 +751,7 @@
 }
 
 func deflateParseLiteral(s string) (lit string, ok bool) {
+	// TODO: support "\xAB" escape codes in the script?
 	const (
 		prefix = `literal "`
 		suffix = `"`
@@ -499,8 +792,9 @@
 }
 
 var gifGlobals struct {
-	imageWidth  uint32
-	imageHeight uint32
+	imageWidth                uint32
+	imageHeight               uint32
+	imageBackgroundColorIndex uint32
 
 	frameLeft   uint32
 	frameTop    uint32
@@ -508,16 +802,23 @@
 	frameHeight uint32
 
 	globalPalette [][4]uint8
+	localPalette  [][4]uint8
 }
 
 func stateGif(line string) (stateFunc, error) {
 	const (
+		cmdB  = "bytes "
+		cmdGC = "graphicControl "
 		cmdL  = "lzw "
 		cmdLC = "loopCount "
 	)
 outer:
 	switch {
+	case line == "":
+		return stateGif, nil
+
 	case line == "frame {":
+		gifGlobals.localPalette = nil
 		out = append(out, 0x2C)
 		return stateGifFrame, nil
 
@@ -532,6 +833,60 @@
 		out = append(out, 0x3B)
 		return stateGif, nil
 
+	case strings.HasPrefix(line, cmdB):
+		s := line[len(cmdB):]
+		for s != "" {
+			x, ok := uint32(0), false
+			x, s, ok = parseHex(s)
+			if !ok {
+				break outer
+			}
+			out = append(out, uint8(x))
+		}
+		return stateGif, nil
+
+	case strings.HasPrefix(line, cmdGC):
+		s := line[len(cmdGC):]
+
+		flags := uint8(0)
+		if i := strings.IndexByte(s, ' '); i < 0 {
+			break
+		} else {
+			switch s[:i] {
+			case "animationDisposalNone":
+				flags |= 0x00
+			case "animationDisposalRestoreBackground":
+				flags |= 0x08
+			case "animationDisposalRestorePrevious":
+				flags |= 0x0C
+			default:
+				break outer
+			}
+			s = s[i+1:]
+		}
+
+		if !strings.HasSuffix(s, "ms") {
+			break
+		}
+		s = s[:len(s)-2]
+		duration, s, ok := parseNum(s)
+		if !ok || s != "" {
+			break
+		}
+		duration /= 10 // GIF's unit of time is 10ms.
+
+		transparentIndex := uint8(0)
+
+		out = append(out,
+			0x21, 0xF9, 0x04,
+			flags,
+			uint8(duration>>0),
+			uint8(duration>>8),
+			transparentIndex,
+			0x00,
+		)
+		return stateGif, nil
+
 	case strings.HasPrefix(line, cmdL):
 		s := line[len(cmdL):]
 		litWidth, s, ok := parseNum(s)
@@ -611,7 +966,7 @@
 		} else {
 			out = append(out, 0x80|uint8(n-1))
 		}
-		out = append(out, 0x00) // TODO: background index.
+		out = append(out, uint8(g.imageBackgroundColorIndex))
 		out = append(out, 0x00)
 		for _, x := range g.globalPalette {
 			out = append(out, x[0], x[1], x[2])
@@ -620,10 +975,18 @@
 	}
 
 	const (
+		cmdBCI = "backgroundColorIndex "
 		cmdIWH = "imageWidthHeight "
 		cmdP   = "palette {"
 	)
 	switch {
+	case strings.HasPrefix(line, cmdBCI):
+		s := line[len(cmdBCI):]
+		if i, _, ok := parseNum(s); ok {
+			g.imageBackgroundColorIndex = i
+		}
+		return stateGifImage, nil
+
 	case strings.HasPrefix(line, cmdIWH):
 		s := line[len(cmdIWH):]
 		if w, s, ok := parseNum(s); ok {
@@ -648,12 +1011,22 @@
 		out = appendU16LE(out, uint16(g.frameTop))
 		out = appendU16LE(out, uint16(g.frameWidth))
 		out = appendU16LE(out, uint16(g.frameHeight))
-		out = append(out, 0x00) // TODO: flags.
+		if g.localPalette == nil {
+			out = append(out, 0x00)
+		} else if n := log2(uint32(len(g.localPalette))); n < 2 || 8 < n {
+			return nil, fmt.Errorf("bad len(g.localPalette): %d", len(g.localPalette))
+		} else {
+			out = append(out, 0x80|uint8(n-1))
+		}
+		for _, x := range g.localPalette {
+			out = append(out, x[0], x[1], x[2])
+		}
 		return stateGif, nil
 	}
 
 	const (
 		cmdFLTWH = "frameLeftTopWidthHeight "
+		cmdP     = "palette {"
 	)
 	switch {
 	case strings.HasPrefix(line, cmdFLTWH):
@@ -671,6 +1044,9 @@
 				}
 			}
 		}
+
+	case strings.HasPrefix(line, cmdP):
+		return stateGifFramePalette, nil
 	}
 
 	return nil, fmt.Errorf("bad stateGifFrame command: %q", line)
@@ -695,3 +1071,23 @@
 
 	return nil, fmt.Errorf("bad stateGifImagePalette command: %q", line)
 }
+
+func stateGifFramePalette(line string) (stateFunc, error) {
+	g := &gifGlobals
+	if line == "}" {
+		return stateGifFrame, nil
+	}
+
+	s := line
+	if rgb0, s, ok := parseHex(s); ok {
+		if rgb1, s, ok := parseHex(s); ok {
+			if rgb2, _, ok := parseHex(s); ok {
+				g.localPalette = append(g.localPalette,
+					[4]uint8{uint8(rgb0), uint8(rgb1), uint8(rgb2), 0xFF})
+				return stateGifFramePalette, nil
+			}
+		}
+	}
+
+	return nil, fmt.Errorf("bad stateGifFramePalette command: %q", line)
+}
diff --git a/std/deflate/decode_deflate.wuffs b/std/deflate/decode_deflate.wuffs
index a91e9b7..89117ab 100644
--- a/std/deflate/decode_deflate.wuffs
+++ b/std/deflate/decode_deflate.wuffs
@@ -139,21 +139,22 @@
 }
 
 pub func decoder.decode_io_writer?(dst base.io_writer, src base.io_reader, workbuf slice base.u8) {
+	var mark         base.u64
 	var status       base.status
 	var written      slice base.u8
 	var n_copied     base.u64
 	var already_full base.u32[..0x8000]
 
 	while true {
-		args.dst.set_mark!()
+		mark = args.dst.mark()
 		status =? this.decode_blocks?(dst:args.dst, src:args.src)
 		if not status.is_suspension() {
 			return status
 		}
-		// TODO: should "since_mark" be "since_mark!", as the return value lets
-		// you modify the state of args.dst, so future mutations (via the
-		// slice) can change the veracity of any args.dst assertions?
-		written = args.dst.since_mark()
+		// TODO: should "since" be "since!", as the return value lets you
+		// modify the state of args.dst, so future mutations (via the slice)
+		// can change the veracity of any args.dst assertions?
+		written = args.dst.since(mark:mark)
 		// Append written, the decoded output, to the history ringbuffer.
 		if written.length() >= 0x8000 {
 			// If written is longer than the ringbuffer, we can ignore the
diff --git a/std/deflate/decode_huffman_fast.wuffs b/std/deflate/decode_huffman_fast.wuffs
index d98af1c..6fa3c5f 100644
--- a/std/deflate/decode_huffman_fast.wuffs
+++ b/std/deflate/decode_huffman_fast.wuffs
@@ -292,12 +292,12 @@
 			pre args.dst.available() >= 258,
 		{
 			// Copy from this.history.
-			if ((dist_minus_1 + 1) as base.u64) > args.dst.since_mark().length() {
+			if ((dist_minus_1 + 1) as base.u64) > args.dst.history_available() {
 				// Set (hlen, hdist) to be the length-distance pair to copy
 				// from this.history, and (length, distance) to be the
 				// remaining length-distance pair to copy from args.dst.
 				hlen = 0
-				hdist = (((dist_minus_1 + 1) as base.u64) - args.dst.since_mark().length()) as base.u32
+				hdist = (((dist_minus_1 + 1) as base.u64) - args.dst.history_available()) as base.u32
 				if length > hdist {
 					assert hdist < length via "a < b: b > a"()
 					assert hdist < 0x8000 via "a < b: a < c; c <= b"(c:length)
@@ -345,12 +345,12 @@
 					continue:loop
 				}
 
-				if ((dist_minus_1 + 1) as base.u64) > args.dst.since_mark().length() {
+				if ((dist_minus_1 + 1) as base.u64) > args.dst.history_available() {
 					return "#internal error: inconsistent distance"
 				}
 			}
 			// Once again, redundant but explicit assertions.
-			assert ((dist_minus_1 + 1) as base.u64) <= args.dst.since_mark().length()
+			assert ((dist_minus_1 + 1) as base.u64) <= args.dst.history_available()
 			assert args.dst.available() >= 258
 
 			// We can therefore prove:
diff --git a/std/deflate/decode_huffman_slow.wuffs b/std/deflate/decode_huffman_slow.wuffs
index 2fb085f..2c66e9d 100644
--- a/std/deflate/decode_huffman_slow.wuffs
+++ b/std/deflate/decode_huffman_slow.wuffs
@@ -203,11 +203,11 @@
 
 		while true {
 			// Copy from this.history.
-			if ((dist_minus_1 + 1) as base.u64) > args.dst.since_mark().length() {
+			if ((dist_minus_1 + 1) as base.u64) > args.dst.history_available() {
 				// Set (hlen, hdist) to be the length-distance pair to copy
 				// from this.history, and (length, distance) to be the
 				// remaining length-distance pair to copy from args.dst.
-				hdist = (((dist_minus_1 + 1) as base.u64) - args.dst.since_mark().length()) as base.u32
+				hdist = (((dist_minus_1 + 1) as base.u64) - args.dst.history_available()) as base.u32
 				if length > hdist {
 					assert hdist < length via "a < b: b > a"()
 					assert hdist < 0x8000 via "a < b: a < c; c <= b"(c:length)
diff --git a/std/gif/decode_gif.wuffs b/std/gif/decode_gif.wuffs
index 38b8271..203f3e0 100644
--- a/std/gif/decode_gif.wuffs
+++ b/std/gif/decode_gif.wuffs
@@ -16,9 +16,11 @@
 
 pub status "#bad block"
 pub status "#bad extension label"
+pub status "#bad frame size"
 pub status "#bad graphic control"
 pub status "#bad header"
 pub status "#bad literal width"
+pub status "#bad palette"
 
 pri status "#internal error: inconsistent ri/wi"
 
@@ -27,6 +29,80 @@
 // need a per-pixel-row workbuf.
 pub const decoder_workbuf_len_max_incl_worst_case base.u64 = 1
 
+// --------
+
+// Quirks are discussed in (/doc/note/quirks.md).
+//
+// The base38 encoding of "gif " is 0xF8586.
+
+// When this quirk is enabled, when skipping over frames, the number of frames
+// visited isn't incremented when the last byte of the N'th frame is seen.
+// Instead, it is incremented when the first byte of the N+1'th frame's header
+// is seen. There may be zero or more GIF extensions between the N'th frame's
+// payload and the N+1'th frame's header.
+//
+// For a well-formed GIF, this won't have much effect. For a malformed GIF,
+// this can affect the number of valid frames, if there is an error detected in
+// the extensions between one frame's payload and the next frame's header.
+//
+// Some other GIF decoders don't register the N'th frame as complete until they
+// see the N+1'th frame's header (or the end-of-animation terminator), so that
+// e.g. the API for visiting the N'th frame can also return whether it's the
+// final frame. Enabling this quirk allows for matching that behavior.
+pub const quirk_delay_num_decoded_frames base.u32 = (0xF8586 << 10) | 0
+
+// When this quirk is enabled, the background color of the first frame is set
+// to black whenever that first frame has a local (frame-specific) palette.
+// That black can be either opaque black or transparent black, depending on
+// whether or not that first frame is opaque: whether that local palette
+// contains a transparent color.
+//
+// This has no effect unless quirk_honor_background_color is also enabled.
+//
+// There isn't really much of a rationale for this, other than it matches the
+// behavior of another GIF implementation.
+pub const quirk_first_frame_local_palette_means_black_background base.u32 = (0xF8586 << 10) | 1
+
+// When this quirk is enabled, the background color is taken from the GIF
+// instead of always being transparent black. If the background color index in
+// the GIF header is non-zero but less than the global palette's size, the
+// global background color is that global palette's entry. Otherwise, it is
+// opaque black. A frame's background color is transparent if the frame palette
+// contains a transparent color. Otherwise, it is the global background color.
+// Note that different frames can have different background colors.
+//
+// Specifically, if the initial frame bounds is smaller than the image bounds,
+// those pixels outside the initial frame bounds are assumed to start as that
+// frame background color. The frame background color should also be used when
+// processing WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_BACKGROUND. In both
+// cases, the caller of Wuffs, not Wuffs itself, is responsible for filling the
+// pixel buffer with that color.
+pub const quirk_honor_background_color base.u32 = (0xF8586 << 10) | 2
+
+// When this quirk is enabled, silently ignore e.g. a frame that reports a
+// width and height of 6 pixels each, followed by 50 pixel values. In that
+// case, we process the first 36 pixel values and discard the excess 14.
+pub const quirk_ignore_too_much_pixel_data base.u32 = (0xF8586 << 10) | 3
+
+// When this quirk is enabled, if the initial frame bounds extends beyond the
+// image bounds, then the image bounds stay unchanged. By default (with this
+// quirk disabled), the image bounds are adjusted to always contain the first
+// frame's bounds (but not necessarily subsequent frame's bounds).
+//
+// For more discussion, see
+// https://github.com/google/wuffs/blob/master/test/data/artificial/gif-frame-out-of-bounds.gif.make-artificial.txt
+pub const quirk_image_bounds_are_strict base.u32 = (0xF8586 << 10) | 4
+
+// When this quirk is enabled, a frame with zero width or height is rejected
+// during decode_frame (but accepted during decode_frame_config).
+pub const quirk_reject_empty_frame base.u32 = (0xF8586 << 10) | 5
+
+// When this quirk is enabled, a frame with no explicit palette is rejected,
+// instead of implicitly having a palette with every entry being opaque black.
+pub const quirk_reject_empty_palette base.u32 = (0xF8586 << 10) | 6
+
+// --------
+
 // See the spec appendix E "Interlaced Images" on page 29. The first element
 // represents either that the frame was non-interlaced, or that all interlace
 // stages are complete. Otherwise, the four interlace stages are elements 4, 3,
@@ -49,37 +125,62 @@
 
 	// Call sequence states:
 	//  - 0: initial state.
-	//  - 1: image config decoded, including the first frame's bounds, but not
+	//  - 1: metadata reported; image config decode is in progress.
+	//  - 2: metadata finished; image config decode is in progress.
+	//  - 3: image config decoded, including the first frame's bounds, but not
 	//       the first frame's pixels.
-	//  - 2: frame config decoded.
-	//  - 3: frame decoded.
+	//  - 4: frame config decoded.
+	//  - 5: frame decoded.
 	//
 	// State transitions:
 	//
-	//  - 0 -> 1: via IC
-	//  - 0 -> 2: via FC with implicit IC
-	//  - 0 -> 3: via F  with implicit IC and FC
+	//  - 0 -> 1: via IC (metadata reported)
+	//  - 0 -> 3: via IC (metadata not reported)
+	//  - 0 -> 4: via FC with implicit IC
+	//  - 0 -> 5: via F  with implicit IC and FC
 	//
-	//  - 1 -> 2: via FC
-	//  - 1 -> 3: via F  with implicit FC
+	//  - 1 -> 2: via AMC
 	//
-	//  - 2 -> 2: via FC with implicit F
-	//  - 2 -> 3: via F
+	//  - 2 -> 1: via IC (metadata reported)
+	//  - 2 -> 3: via IC (metadata not reported)
 	//
-	//  - 3 -> 2: via FC
-	//  - 3 -> 3: via F  with implicit FC
+	//  - 3 -> 4: via FC
+	//  - 3 -> 5: via F  with implicit FC
+	//
+	//  - 4 -> 4: via FC with implicit F
+	//  - 4 -> 5: via F
+	//
+	//  - 5 -> 4: via FC
+	//  - 5 -> 5: via F  with implicit FC
 	//
 	// Where:
-	//  - F  is decode_frame,        implicit means skip_frame
-	//  - FC is decode_frame_config, implicit means nullptr args.dst
-	//  - IC is decode_image_config, implicit means nullptr args.dst
+	//  - AMC is ack_metadata_chunk
+	//  - F   is decode_frame,        implicit means skip_frame
+	//  - FC  is decode_frame_config, implicit means nullptr args.dst
+	//  - IC  is decode_image_config, implicit means nullptr args.dst
 	call_sequence base.u8,
 
+	ignore_metadata             base.bool,
+	report_metadata_iccp        base.bool,
+	report_metadata_xmp         base.bool,
+	metadata_fourcc_value       base.u32,
+	metadata_chunk_length_value base.u64,
+	metadata_io_position        base.u64,
+
+	quirk_enabled_delay_num_decoded_frames                         base.bool,
+	quirk_enabled_first_frame_local_palette_means_black_background base.bool,
+	quirk_enabled_honor_background_color                           base.bool,
+	quirk_enabled_ignore_too_much_pixel_data                       base.bool,
+	quirk_enabled_image_bounds_are_strict                          base.bool,
+	quirk_enabled_reject_empty_frame                               base.bool,
+	quirk_enabled_reject_empty_palette                             base.bool,
+
+	delayed_num_decoded_frames         base.bool,
 	end_of_data                        base.bool,
 	restarted                          base.bool,
 	previous_lzw_decode_ended_abruptly base.bool,
 
-	which_palette base.u8[..1],
+	has_global_palette base.bool,
 
 	// interlace indexes the interlace_start and interlace_delta arrays.
 	interlace base.u8[..4],
@@ -89,7 +190,9 @@
 	seen_num_loops base.bool,
 	num_loops      base.u32,
 
-	seen_graphic_control     base.bool,
+	background_color_u32_argb_premul base.u32,
+	black_color_u32_argb_premul      base.u32,
+
 	gc_has_transparent_index base.bool,
 	gc_transparent_index     base.u8,
 	gc_disposal              base.u8,
@@ -127,24 +230,55 @@
 	lzw lzw.decoder,
 )
 
+pub func decoder.set_quirk_enabled!(quirk base.u32, enabled base.bool) {
+	if this.call_sequence == 0 {
+		if args.quirk == quirk_delay_num_decoded_frames {
+			this.quirk_enabled_delay_num_decoded_frames = args.enabled
+		} else if args.quirk == quirk_first_frame_local_palette_means_black_background {
+			this.quirk_enabled_first_frame_local_palette_means_black_background = args.enabled
+		} else if args.quirk == quirk_honor_background_color {
+			this.quirk_enabled_honor_background_color = args.enabled
+		} else if args.quirk == quirk_ignore_too_much_pixel_data {
+			this.quirk_enabled_ignore_too_much_pixel_data = args.enabled
+		} else if args.quirk == quirk_image_bounds_are_strict {
+			this.quirk_enabled_image_bounds_are_strict = args.enabled
+		} else if args.quirk == quirk_reject_empty_frame {
+			this.quirk_enabled_reject_empty_frame = args.enabled
+		} else if args.quirk == quirk_reject_empty_palette {
+			this.quirk_enabled_reject_empty_palette = args.enabled
+		}
+	}
+}
+
 pub func decoder.decode_image_config?(dst nptr base.image_config, src base.io_reader) {
 	var ffio base.bool
 
-	if this.call_sequence >= 1 {
+	if this.call_sequence == 0 {
+		this.decode_header?(src:args.src)
+		this.decode_lsd?(src:args.src)
+	} else if this.call_sequence <> 2 {
 		return base."#bad call sequence"
 	}
 
-	this.decode_header?(src:args.src)
-	this.decode_lsd?(src:args.src)
 	this.decode_up_to_id_part1?(src:args.src)
 
 	// TODO: if this.end_of_data, return an error and/or set dst to zero?
 
-	ffio = (not this.gc_has_transparent_index) and
-		(this.frame_rect_x0 == 0) and
-		(this.frame_rect_y0 == 0) and
-		(this.frame_rect_x1 == this.width) and
-		(this.frame_rect_y1 == this.height)
+	ffio = not this.gc_has_transparent_index
+	if not this.quirk_enabled_honor_background_color {
+		ffio = ffio and
+			(this.frame_rect_x0 == 0) and
+			(this.frame_rect_y0 == 0) and
+			(this.frame_rect_x1 == this.width) and
+			(this.frame_rect_y1 == this.height)
+	} else if ffio {
+		// Use opaque black, not transparent black.
+		this.black_color_u32_argb_premul = 0xFF000000
+	}
+
+	if this.background_color_u32_argb_premul == 77 {
+		this.background_color_u32_argb_premul = this.black_color_u32_argb_premul
+	}
 
 	if args.dst <> nullptr {
 		// TODO: a Wuffs (not just C) name for the
@@ -158,7 +292,60 @@
 			first_frame_is_opaque:ffio)
 	}
 
-	this.call_sequence = 1
+	this.call_sequence = 3
+}
+
+pub func decoder.set_report_metadata!(fourcc base.u32, report base.bool) {
+	if args.fourcc == 0x49434350 {  // "ICCP"
+		this.report_metadata_iccp = args.report
+	} else if args.fourcc == 0x584D5020 {  // "XMP "
+		this.report_metadata_xmp = args.report
+	}
+}
+
+pub func decoder.ack_metadata_chunk?(src base.io_reader) {
+	if this.call_sequence <> 1 {
+		return base."#bad call sequence"
+	}
+	if args.src.position() <> this.metadata_io_position {
+		return base."#bad I/O position"
+	}
+	while args.src.available() <= 0,
+		post args.src.available() > 0,
+	{
+		yield? base."$short read"
+	}
+
+	if this.metadata_fourcc_value == 0x584D5020 {  // "XMP "
+		// The +1 is because XMP metadata's encoding includes each block's leading
+		// byte (the block size) as part of the metadata passed to the caller.
+		this.metadata_chunk_length_value = args.src.peek_u8_as_u64() + 1
+		if this.metadata_chunk_length_value > 1 {
+			this.metadata_io_position = args.src.position() ~sat+ this.metadata_chunk_length_value
+			return base."@metadata reported"
+		}
+	} else {
+		this.metadata_chunk_length_value = args.src.peek_u8_as_u64()
+		if this.metadata_chunk_length_value > 0 {
+			args.src.skip_fast!(actual:1, worst_case:1)
+			this.metadata_io_position = args.src.position() ~sat+ this.metadata_chunk_length_value
+			return base."@metadata reported"
+		}
+	}
+
+	args.src.skip_fast!(actual:1, worst_case:1)
+	this.call_sequence = 2
+	this.metadata_fourcc_value = 0
+	this.metadata_io_position = 0
+	return ok
+}
+
+pub func decoder.metadata_fourcc() base.u32 {
+	return this.metadata_fourcc_value
+}
+
+pub func decoder.metadata_chunk_length() base.u64 {
+	return this.metadata_chunk_length_value
 }
 
 pub func decoder.num_animation_loops() base.u32 {
@@ -200,6 +387,7 @@
 	if this.call_sequence == 0 {
 		return base."#bad call sequence"
 	}
+	this.delayed_num_decoded_frames = false
 	this.end_of_data = false
 	this.restarted = true
 	this.frame_config_io_position = args.io_position
@@ -210,15 +398,18 @@
 }
 
 pub func decoder.decode_frame_config?(dst nptr base.frame_config, src base.io_reader) {
-	var blend base.u8
+	var blend            base.u8
+	var background_color base.u32
+	var flags            base.u8
 
+	this.ignore_metadata = true
 	this.dirty_y.reset!()
 
 	if not this.end_of_data {
 		if this.call_sequence == 0 {
 			this.decode_image_config?(dst:nullptr, src:args.src)
-		} else if this.call_sequence <> 1 {
-			if this.call_sequence == 2 {
+		} else if this.call_sequence <> 3 {
+			if this.call_sequence == 4 {
 				this.skip_frame?(src:args.src)
 			}
 			this.decode_up_to_id_part1?(src:args.src)
@@ -232,8 +423,26 @@
 	}
 
 	blend = 0
+	background_color = this.black_color_u32_argb_premul
 	if not this.gc_has_transparent_index {
 		blend = 2  // 2 is WUFFS_BASE__ANIMATION_BLEND__OPAQUE.
+		background_color = this.background_color_u32_argb_premul
+
+		// If the quirk is enabled and the first frame has a local color
+		// palette, its background color is black.
+		if this.quirk_enabled_first_frame_local_palette_means_black_background and
+			(this.num_decoded_frame_configs_value == 0) {
+
+			while args.src.available() <= 0,
+				post args.src.available() > 0,
+			{
+				yield? base."$short read"
+			}
+			flags = args.src.peek_u8()
+			if (flags & 0x80) <> 0 {
+				background_color = this.black_color_u32_argb_premul
+			}
+		}
 	}
 
 	if args.dst <> nullptr {
@@ -246,35 +455,51 @@
 			index:this.num_decoded_frame_configs_value,
 			io_position:this.frame_config_io_position,
 			blend:blend,
-			disposal:this.gc_disposal)
+			disposal:this.gc_disposal,
+			background_color:background_color)
 	}
 
 	this.num_decoded_frame_configs_value ~sat+= 1
-	this.call_sequence = 2
+	this.call_sequence = 4
 }
 
 pri func decoder.skip_frame?(src base.io_reader) {
 	var flags base.u8
+	var lw    base.u8
 
 	// Skip the optional Local Color Table, 3 bytes (RGB) per entry.
 	flags = args.src.read_u8?()
 	if (flags & 0x80) <> 0 {
 		args.src.skip?(n:(3 as base.u32) << (1 + (flags & 0x07)))
 	}
-	// Skip the literal width.
-	args.src.skip?(n:1)
+
+	// Process the LZW literal width.
+	lw = args.src.read_u8?()
+	if lw > 8 {
+		return "#bad literal width"
+	}
+
 	// Skip the blocks of LZW-compressed data.
 	this.skip_blocks?(src:args.src)
 
-	this.num_decoded_frames_value ~sat+= 1
+	if this.quirk_enabled_delay_num_decoded_frames {
+		this.delayed_num_decoded_frames = true
+	} else {
+		this.num_decoded_frames_value ~sat+= 1
+	}
 	this.reset_gc!()
 }
 
 // TODO: honor args.opts.
 pub func decoder.decode_frame?(dst ptr base.pixel_buffer, src base.io_reader, workbuf slice base.u8, opts nptr base.decode_frame_options) {
-	if this.call_sequence <> 2 {
+	this.ignore_metadata = true
+	if this.call_sequence <> 4 {
 		this.decode_frame_config?(dst:nullptr, src:args.src)
 	}
+	if this.quirk_enabled_reject_empty_frame and
+		((this.frame_rect_x0 == this.frame_rect_x1) or (this.frame_rect_y0 == this.frame_rect_y1)) {
+		return "#bad frame size"
+	}
 	this.decode_id_part1?(dst:args.dst, src:args.src)
 	this.decode_id_part2?(dst:args.dst, src:args.src, workbuf:args.workbuf)
 
@@ -283,10 +508,9 @@
 }
 
 pri func decoder.reset_gc!() {
-	this.call_sequence = 3
+	this.call_sequence = 5
 	// The Image Descriptor is mandatory, but the Graphic Control extension is
 	// optional. Reset the GC related fields for the next decode_frame call.
-	this.seen_graphic_control = false
 	this.gc_has_transparent_index = false
 	this.gc_transparent_index = 0
 	this.gc_disposal = 0
@@ -297,7 +521,9 @@
 	var block_type base.u8
 
 	if not this.restarted {
-		this.frame_config_io_position = args.src.position()
+		if this.call_sequence <> 2 {
+			this.frame_config_io_position = args.src.position()
+		}
 	} else if this.frame_config_io_position <> args.src.position() {
 		return base."#bad restart"
 	} else {
@@ -309,9 +535,17 @@
 		if block_type == 0x21 {  // The spec calls 0x21 the "Extension Introducer".
 			this.decode_extension?(src:args.src)
 		} else if block_type == 0x2C {  // The spec calls 0x2C the "Image Separator".
+			if this.delayed_num_decoded_frames {
+				this.delayed_num_decoded_frames = false
+				this.num_decoded_frames_value ~sat+= 1
+			}
 			this.decode_id_part0?(src:args.src)
 			break
 		} else if block_type == 0x3B {  // The spec calls 0x3B the "Trailer".
+			if this.delayed_num_decoded_frames {
+				this.delayed_num_decoded_frames = false
+				this.num_decoded_frames_value ~sat+= 1
+			}
 			this.end_of_data = true
 			break
 		} else {
@@ -341,21 +575,25 @@
 //
 // See the spec section 18 "Logical Screen Descriptor" on page 8.
 pri func decoder.decode_lsd?(src base.io_reader) {
-	var flags               base.u8
-	var num_palette_entries base.u32[..256]
-	var i                   base.u32
-	var argb                base.u32
+	var flags                  base.u8
+	var background_color_index base.u8
+	var num_palette_entries    base.u32[..256]
+	var i                      base.u32
+	var j                      base.u32[..1020]
+	var argb                   base.u32
 
 	this.width = args.src.read_u16le_as_u32?()
 	this.height = args.src.read_u16le_as_u32?()
 	flags = args.src.read_u8?()
-	// Ignore the Background Color Index and Pixel Aspect Ratio bytes.
-	args.src.skip?(n:2)
+	background_color_index = args.src.read_u8?()
+	// Ignore the Pixel Aspect Ratio byte.
+	args.src.skip?(n:1)
 
 	// Read the optional Global Color Table.
-	if (flags & 0x80) <> 0 {
+	i = 0
+	this.has_global_palette = (flags & 0x80) <> 0
+	if this.has_global_palette {
 		num_palette_entries = (1 as base.u32) << (1 + (flags & 0x07))
-		i = 0
 		while i < num_palette_entries {
 			assert i < 256 via "a < b: a < c; c <= b"(c:num_palette_entries)
 			// Convert from RGB (in memory order) to ARGB (in native u32 order)
@@ -368,15 +606,35 @@
 			this.palettes[0][(4 * i) + 3] = ((argb >> 24) & 0xFF) as base.u8
 			i += 1
 		}
-		// Set the remaining palette entries to opaque black.
-		while i < 256 {
-			this.palettes[0][(4 * i) + 0] = 0x00
-			this.palettes[0][(4 * i) + 1] = 0x00
-			this.palettes[0][(4 * i) + 2] = 0x00
-			this.palettes[0][(4 * i) + 3] = 0xFF
-			i += 1
+
+		if this.quirk_enabled_honor_background_color {
+			if (background_color_index <> 0) and
+				((background_color_index as base.u32) < num_palette_entries) {
+
+				j = 4 * (background_color_index as base.u32)
+				this.background_color_u32_argb_premul =
+					((this.palettes[0][j + 0] as base.u32) << 0) |
+					((this.palettes[0][j + 1] as base.u32) << 8) |
+					((this.palettes[0][j + 2] as base.u32) << 16) |
+					((this.palettes[0][j + 3] as base.u32) << 24)
+			} else {
+				// The background color is either opaque black or transparent
+				// black. We set it to an arbitrary nonsense value (77) for
+				// now, and set it to its real value later, once we know
+				// whether the first frame is opaque (the ffio value).
+				this.background_color_u32_argb_premul = 77
+			}
 		}
 	}
+
+	// Set the remaining palette entries to opaque black.
+	while i < 256 {
+		this.palettes[0][(4 * i) + 0] = 0x00
+		this.palettes[0][(4 * i) + 1] = 0x00
+		this.palettes[0][(4 * i) + 2] = 0x00
+		this.palettes[0][(4 * i) + 3] = 0xFF
+		i += 1
+	}
 }
 
 // decode_extension reads an extension. The Extension Introducer byte has
@@ -425,12 +683,24 @@
 	0x4E, 0x45, 0x54, 0x53, 0x43, 0x41, 0x50, 0x45, 0x32, 0x2E, 0x30,
 ]
 
+// iccrgbg1012 is "ICCRGBG1012" as bytes.
+pri const iccrgbg1012 array[11] base.u8 = [
+	0x49, 0x43, 0x43, 0x52, 0x47, 0x42, 0x47, 0x31, 0x30, 0x31, 0x32,
+]
+
+// xmpdataxmp is "XMP DataXMP" as bytes.
+pri const xmpdataxmp array[11] base.u8 = [
+	0x58, 0x4D, 0x50, 0x20, 0x44, 0x61, 0x74, 0x61, 0x58, 0x4D, 0x50,
+]
+
 // decode_ae reads an Application Extension.
 pri func decoder.decode_ae?(src base.io_reader) {
-	var c            base.u8
-	var block_size   base.u8
-	var not_animexts base.bool
-	var not_netscape base.bool
+	var c           base.u8
+	var block_size  base.u8
+	var is_animexts base.bool
+	var is_netscape base.bool
+	var is_iccp     base.bool
+	var is_xmp      base.bool
 
 	// This "while true" always executes exactly once, as it ends with a
 	// "break", but using "break"s throughout simplifies the control flow.
@@ -440,54 +710,90 @@
 			return ok
 		}
 
-		// Look only for an 11 byte "ANIMEXTS1.0" or "NETSCAPE2.0" extension,
-		// as per:
+		// Look only for an 11 byte "ANIMEXTS1.0", "NETSCAPE2.0" or other
+		// extension, as per:
 		//  - http://www.vurdalakov.net/misc/gif/animexts-looping-application-extension
 		//  - http://www.vurdalakov.net/misc/gif/netscape-looping-application-extension
+		//
+		// Other extensions include XMP metadata.
 		if block_size <> 11 {
 			args.src.skip?(n:block_size as base.u32)
 			break
 		}
-		not_animexts = false
-		not_netscape = false
+		is_animexts = true
+		is_netscape = true
+		is_iccp = true
+		is_xmp = true
 		block_size = 0  // Re-purpose the block_size variable as a counter.
 		while block_size < 11 {
 			c = args.src.read_u8?()
-			not_animexts = not_animexts or (c <> animexts1dot0[block_size])
-			not_netscape = not_netscape or (c <> netscape2dot0[block_size])
+			is_animexts = is_animexts and (c == animexts1dot0[block_size])
+			is_netscape = is_netscape and (c == netscape2dot0[block_size])
+			is_iccp = is_iccp and (c == iccrgbg1012[block_size])
+			is_xmp = is_xmp and (c == xmpdataxmp[block_size])
 			block_size += 1
 		}
-		if not_animexts and not_netscape {
-			break
-		}
 
-		// Those 11 bytes should be followed by 0x03, 0x01 and then the loop
-		// count.
-		block_size = args.src.read_u8?()
-		if block_size <> 3 {
-			args.src.skip?(n:block_size as base.u32)
-			break
-		}
-		c = args.src.read_u8?()
-		if c <> 0x01 {
-			args.src.skip?(n:2)
-			break
-		}
-		this.num_loops = args.src.read_u16le_as_u32?()
-		this.seen_num_loops = true
+		if is_animexts or is_netscape {
+			// Those 11 bytes should be followed by 0x03, 0x01 and then the loop
+			// count.
+			block_size = args.src.read_u8?()
+			if block_size <> 3 {
+				args.src.skip?(n:block_size as base.u32)
+				break
+			}
+			c = args.src.read_u8?()
+			if c <> 0x01 {
+				args.src.skip?(n:2)
+				break
+			}
+			this.num_loops = args.src.read_u16le_as_u32?()
+			this.seen_num_loops = true
 
-		// A loop count of N, in the wire format, actually means "repeat N
-		// times after the first play", if N is positive. A zero N means to
-		// loop forever. Playing the frames exactly once is denoted by the
-		// *absence* of this NETSCAPE2.0 application extension.
-		//
-		// For example, if there are four frames: A, B, C, D, and N is 2, then
-		// each frame is actually played N+1 or 3 times: ABCDABCDABCD.
-		//
-		// Thus, we increment N if it is positive. The comparison against
-		// 0xFFFF will never fail, but is necessary for the overflow checker.
-		if (0 < this.num_loops) and (this.num_loops <= 0xFFFF) {
-			this.num_loops += 1
+			// A loop count of N, in the wire format, actually means "repeat N
+			// times after the first play", if N is positive. A zero N means to
+			// loop forever. Playing the frames exactly once is denoted by the
+			// *absence* of this NETSCAPE2.0 application extension.
+			//
+			// For example, if there are four frames: A, B, C, D, and N is 2, then
+			// each frame is actually played N+1 or 3 times: ABCDABCDABCD.
+			//
+			// Thus, we increment N if it is positive. The comparison against
+			// 0xFFFF will never fail, but is necessary for the overflow checker.
+			if (0 < this.num_loops) and (this.num_loops <= 0xFFFF) {
+				this.num_loops += 1
+			}
+
+		} else if this.ignore_metadata {
+			// No-op.
+
+		} else if is_iccp and this.report_metadata_iccp {
+			while args.src.available() <= 0,
+				post args.src.available() > 0,
+			{
+				yield? base."$short read"
+			}
+			this.metadata_chunk_length_value = args.src.peek_u8_as_u64()
+			args.src.skip_fast!(actual:1, worst_case:1)
+			this.metadata_fourcc_value = 0x49434350  // "ICCP"
+			this.metadata_io_position = args.src.position() ~sat+ this.metadata_chunk_length_value
+			this.call_sequence = 1
+			return base."@metadata reported"
+
+		} else if is_xmp and this.report_metadata_xmp {
+			while args.src.available() <= 0,
+				post args.src.available() > 0,
+			{
+				yield? base."$short read"
+			}
+			// The +1 is because XMP metadata's encoding includes each block's
+			// leading byte (the block size) as part of the metadata passed to
+			// the caller.
+			this.metadata_chunk_length_value = args.src.peek_u8_as_u64() + 1
+			this.metadata_fourcc_value = 0x584D5020  // "XMP "
+			this.metadata_io_position = args.src.position() ~sat+ this.metadata_chunk_length_value
+			this.call_sequence = 1
+			return base."@metadata reported"
 		}
 
 		break
@@ -501,9 +807,6 @@
 	var flags                    base.u8
 	var gc_duration_centiseconds base.u16
 
-	if this.seen_graphic_control {
-		return "#bad graphic control"
-	}
 	c = args.src.read_u8?()
 	if c <> 4 {
 		return "#bad graphic control"
@@ -539,7 +842,6 @@
 	if c <> 0 {
 		return "#bad graphic control"
 	}
-	this.seen_graphic_control = true
 }
 
 // decode_id_partX reads an Image Descriptor. The Image Separator byte has
@@ -569,7 +871,7 @@
 	// and the bottom right extent of the first frame. See
 	// test/data/artificial/gif-frame-out-of-bounds.gif.make-artificial.txt for
 	// more discussion.
-	if this.call_sequence == 0 {
+	if (this.call_sequence == 0) and (not this.quirk_enabled_image_bounds_are_strict) {
 		this.width = this.width.max(x:this.frame_rect_x1)
 		this.height = this.height.max(x:this.frame_rect_y1)
 	}
@@ -577,10 +879,12 @@
 
 pri func decoder.decode_id_part1?(dst ptr base.pixel_buffer, src base.io_reader) {
 	var flags               base.u8
+	var which_palette       base.u8[..1]
 	var num_palette_entries base.u32[..256]
 	var i                   base.u32
 	var argb                base.u32
 	var dst_palette         slice base.u8
+	var status              base.status
 	var lw                  base.u8
 
 	flags = args.src.read_u8?()
@@ -591,7 +895,7 @@
 	}
 
 	// Read the optional Local Color Table.
-	this.which_palette = 1
+	which_palette = 1
 	if (flags & 0x80) <> 0 {
 		num_palette_entries = (1 as base.u32) << (1 + (flags & 0x07))
 		i = 0
@@ -615,10 +919,12 @@
 			this.palettes[1][(4 * i) + 3] = 0xFF
 			i += 1
 		}
+	} else if this.quirk_enabled_reject_empty_palette and (not this.has_global_palette) {
+		return "#bad palette"
 	} else if this.gc_has_transparent_index {
 		this.palettes[1][:].copy_from_slice!(s:this.palettes[0][:])
 	} else {
-		this.which_palette = 0
+		which_palette = 0
 	}
 
 	// Set the gc_transparent_index palette entry to transparent black.
@@ -636,11 +942,14 @@
 
 	// TODO: a Wuffs (not just C) name for the
 	// WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_BINARY magic pixfmt constant.
-	this.swizzler.prepare!(
+	status = this.swizzler.prepare!(
 		dst_pixfmt:args.dst.pixel_format(),
 		dst_palette:dst_palette,
 		src_pixfmt:0x47040008,
-		src_palette:this.palettes[this.which_palette][:])
+		src_palette:this.palettes[which_palette][:])
+	if not status.is_ok() {
+		return status
+	}
 
 	// Other GIF implementations accept GIF files that aren't completely spec
 	// compliant. For example, the test/data/gifplayer-muybridge.gif file
@@ -661,8 +970,11 @@
 		this.lzw.reset!()
 	}
 
+	// Process the LZW literal width. The spec says that "images which have one
+	// color bit must be indicated as having a code size [i.e. literal width]
+	// of 2", but in practice, some encoders use a literal width of 1 or 0.
 	lw = args.src.read_u8?()
-	if (lw < 2) or (8 < lw) {
+	if lw > 8 {
 		return "#bad literal width"
 	}
 	this.lzw.set_literal_width!(lw:lw as base.u32)
@@ -676,6 +988,7 @@
 	var n_compressed    base.u64
 	var compressed      slice base.u8
 	var r               base.io_reader
+	var mark            base.u64
 	var lzw_status      base.status
 	var copy_status     base.status
 	var uncompressed    slice base.u8
@@ -726,9 +1039,10 @@
 				return "#internal error: inconsistent ri/wi"
 			}
 			io_bind (io:r, data:this.compressed[this.compressed_ri:this.compressed_wi]) {
+				mark = r.mark()
 				lzw_status =? this.lzw.decode_io_writer?(
 					dst:this.util.null_io_writer(), src:r, workbuf:this.util.null_slice_u8())
-				this.compressed_ri ~sat+= r.since_mark().length()
+				this.compressed_ri ~sat+= r.count_since(mark:mark)
 			}
 
 			uncompressed = this.lzw.flush!()
@@ -769,25 +1083,28 @@
 }
 
 pri func decoder.copy_to_image_buffer!(pb ptr base.pixel_buffer, src slice base.u8) base.status {
-	// TODO: don't assume a packed pixel format.
+	// TODO: don't assume an interleaved pixel format.
 	var dst             slice base.u8
 	var src             slice base.u8
 	var n               base.u64
 	var src_ri          base.u64
 	var bytes_per_pixel base.u32[..64]
-	var pixfmt          base.u32
+	var pixfmt_channels base.u32
 	var tab             table base.u8
 	var i               base.u64
 	var j               base.u64
 
 	// TODO: a Wuffs (not just C) name for the WUFFS_BASE__PIXEL_FORMAT__ETC
 	// magic pixfmt constants. Also, support more formats.
-	bytes_per_pixel = 1
-	pixfmt = args.pb.pixel_format()
-	if (pixfmt == 0x45008888) or (pixfmt == 0x55008888) or
-		(pixfmt == 0x46008888) or (pixfmt == 0x56008888) or
-		(pixfmt == 0x47008888) or (pixfmt == 0x57008888) {
+	pixfmt_channels = args.pb.pixel_format() & 0xFFFF
+	if (pixfmt_channels == 0x8888) {
 		bytes_per_pixel = 4
+	} else if (pixfmt_channels == 0x0888) {
+		bytes_per_pixel = 3
+	} else if (pixfmt_channels == 0x0008) {
+		bytes_per_pixel = 1
+	} else {
+		return base."#unsupported option"
 	}
 
 	tab = args.pb.plane(p:0)
@@ -795,6 +1112,9 @@
 		src = args.src[src_ri:]
 
 		if this.dst_y >= this.frame_rect_y1 {
+			if this.quirk_enabled_ignore_too_much_pixel_data {
+				return ok
+			}
 			return base."#too much data"
 		}
 
@@ -810,7 +1130,8 @@
 			} else {
 				dst = dst[i:]
 			}
-			n = this.swizzler.swizzle_packed!(dst:dst, dst_palette:this.dst_palette[:], src:src)
+			n = this.swizzler.swizzle_interleaved!(
+				dst:dst, dst_palette:this.dst_palette[:], src:src)
 
 			src_ri ~sat+= n
 			this.dst_x ~sat+= (n & 0xFFFFFFFF) as base.u32
diff --git a/std/gzip/decode_gzip.wuffs b/std/gzip/decode_gzip.wuffs
index 297b191..49a7cb6 100644
--- a/std/gzip/decode_gzip.wuffs
+++ b/std/gzip/decode_gzip.wuffs
@@ -46,6 +46,7 @@
 	var c                   base.u8
 	var flags               base.u8
 	var xlen                base.u16
+	var mark                base.u64
 	var checksum_got        base.u32
 	var decoded_length_got  base.u32
 	var status              base.status
@@ -111,11 +112,11 @@
 
 	// Decode and checksum the DEFLATE-encoded payload.
 	while true {
-		args.dst.set_mark!()
+		mark = args.dst.mark()
 		status =? this.flate.decode_io_writer?(dst:args.dst, src:args.src, workbuf:args.workbuf)
 		if not this.ignore_checksum {
-			checksum_got = this.checksum.update!(x:args.dst.since_mark())
-			decoded_length_got ~mod+= ((args.dst.since_mark().length() & 0xFFFFFFFF) as base.u32)
+			checksum_got = this.checksum.update!(x:args.dst.since(mark:mark))
+			decoded_length_got ~mod+= ((args.dst.count_since(mark:mark) & 0xFFFFFFFF) as base.u32)
 		}
 		if status.is_ok() {
 			break
diff --git a/std/lzw/decode_lzw.wuffs b/std/lzw/decode_lzw.wuffs
index 7f3698c..5f02e99 100644
--- a/std/lzw/decode_lzw.wuffs
+++ b/std/lzw/decode_lzw.wuffs
@@ -27,11 +27,11 @@
 pub const decoder_workbuf_len_max_incl_worst_case base.u64 = 0
 
 pub struct decoder?(
-	// set_literal_width_arg is the saved argument passed to set_literal_width.
-	// This field is copied to the literal_width field at the start of
-	// decode_io_writer. During that method, calling set_literal_width will
-	// change set_literal_width_arg but not literal_width.
-	set_literal_width_arg base.u32[..8],
+	// set_literal_width_arg is 1 plus the saved argument passed to
+	// set_literal_width. This is assigned to the literal_width field at the
+	// start of decode_io_writer. During that method, calling set_literal_width
+	// will change set_literal_width_arg but not literal_width.
+	set_literal_width_arg base.u32[..9],
 
 	// read_from state that does not change during a decode call.
 	literal_width base.u32[..8],
@@ -69,8 +69,8 @@
 	output array[8192 + 7] base.u8,
 )
 
-pub func decoder.set_literal_width!(lw base.u32[2..8]) {
-	this.set_literal_width_arg = args.lw
+pub func decoder.set_literal_width!(lw base.u32[..8]) {
+	this.set_literal_width_arg = args.lw + 1
 }
 
 pub func decoder.workbuf_len() base.range_ii_u64 {
@@ -82,8 +82,8 @@
 
 	// Initialize read_from state.
 	this.literal_width = 8
-	if this.set_literal_width_arg >= 2 {
-		this.literal_width = this.set_literal_width_arg
+	if this.set_literal_width_arg > 0 {
+		this.literal_width = this.set_literal_width_arg - 1
 	}
 	this.clear_code = (1 as base.u32) << this.literal_width
 	this.end_code = this.clear_code + 1
diff --git a/std/zlib/decode_zlib.wuffs b/std/zlib/decode_zlib.wuffs
index b7b5817..fcf6c65 100644
--- a/std/zlib/decode_zlib.wuffs
+++ b/std/zlib/decode_zlib.wuffs
@@ -49,6 +49,7 @@
 	var checksum_got  base.u32
 	var status        base.status
 	var checksum_want base.u32
+	var mark          base.u64
 
 	x = args.src.read_u16be?()
 	if ((x >> 8) & 0x0F) <> 0x08 {
@@ -66,10 +67,10 @@
 
 	// Decode and checksum the DEFLATE-encoded payload.
 	while true {
-		args.dst.set_mark!()
+		mark = args.dst.mark()
 		status =? this.flate.decode_io_writer?(dst:args.dst, src:args.src, workbuf:args.workbuf)
 		if not this.ignore_checksum {
-			checksum_got = this.checksum.update!(x:args.dst.since_mark())
+			checksum_got = this.checksum.update!(x:args.dst.since(mark:mark))
 		}
 		if status.is_ok() {
 			break
diff --git a/test/c/mimiclib/deflate-gzip-zlib.c b/test/c/mimiclib/deflate-gzip-zlib.c
index 9b9c0c3..4c3a740 100644
--- a/test/c/mimiclib/deflate-gzip-zlib.c
+++ b/test/c/mimiclib/deflate-gzip-zlib.c
@@ -42,7 +42,7 @@
                                       uint64_t wlimit,
                                       uint64_t rlimit,
                                       bool deflate_instead_of_zlib) {
-  if (wlimit || rlimit) {
+  if ((wlimit < UINT64_MAX) || (rlimit < UINT64_MAX)) {
     // Supporting this would probably mean using tinfl_decompress instead of
     // the simpler tinfl_decompress_mem_to_mem function.
     return "unsupported I/O limit";
@@ -99,7 +99,7 @@
     size_t len = src->meta.wi - src->meta.ri;
     if (len > 0x7FFFFFFF) {
       return "src length is too large";
-    } else if ((len > rlimit) && (rlimit > 0)) {
+    } else if (len > rlimit) {
       len = rlimit;
     }
     global_mimiclib_deflate_unused_u32 =
@@ -120,7 +120,7 @@
     size_t len = src->meta.wi - src->meta.ri;
     if (len > 0x7FFFFFFF) {
       return "src length is too large";
-    } else if ((len > rlimit) && (rlimit > 0)) {
+    } else if (len > rlimit) {
       len = rlimit;
     }
     global_mimiclib_deflate_unused_u32 =
@@ -178,14 +178,14 @@
   while (true) {
     z.next_in = src->data.ptr + src->meta.ri;
     z.avail_in = src->meta.wi - src->meta.ri;
-    if ((z.avail_in > rlimit) && (rlimit > 0)) {
+    if (z.avail_in > rlimit) {
       z.avail_in = rlimit;
     }
     uInt initial_avail_in = z.avail_in;
 
     z.next_out = dst->data.ptr + dst->meta.wi;
     z.avail_out = dst->data.len - dst->meta.wi;
-    if ((z.avail_out > wlimit) && (wlimit > 0)) {
+    if (z.avail_out > wlimit) {
       z.avail_out = wlimit;
     }
     uInt initial_avail_out = z.avail_out;
diff --git a/test/c/std/adler32.c b/test/c/std/adler32.c
index 9e509a9..b48a707 100644
--- a/test/c/std/adler32.c
+++ b/test/c/std/adler32.c
@@ -253,7 +253,7 @@
   return do_bench_io_buffers(
       wuffs_bench_adler32,
       WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED, tc_src,
-      &adler32_midsummer_gt, 0, 0, 1500);
+      &adler32_midsummer_gt, UINT64_MAX, UINT64_MAX, 1500);
 }
 
 const char* bench_wuffs_adler32_100k() {
@@ -261,7 +261,7 @@
   return do_bench_io_buffers(
       wuffs_bench_adler32,
       WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED, tc_src,
-      &adler32_pi_gt, 0, 0, 150);
+      &adler32_pi_gt, UINT64_MAX, UINT64_MAX, 150);
 }
 
   // ---------------- Mimic Benches
@@ -271,13 +271,14 @@
 const char* bench_mimic_adler32_10k() {
   CHECK_FOCUS(__func__);
   return do_bench_io_buffers(mimic_bench_adler32, 0, tc_src,
-                             &adler32_midsummer_gt, 0, 0, 1500);
+                             &adler32_midsummer_gt, UINT64_MAX, UINT64_MAX,
+                             1500);
 }
 
 const char* bench_mimic_adler32_100k() {
   CHECK_FOCUS(__func__);
-  return do_bench_io_buffers(mimic_bench_adler32, 0, tc_src, &adler32_pi_gt, 0,
-                             0, 150);
+  return do_bench_io_buffers(mimic_bench_adler32, 0, tc_src, &adler32_pi_gt,
+                             UINT64_MAX, UINT64_MAX, 150);
 }
 
 #endif  // WUFFS_MIMIC
diff --git a/test/c/std/crc32.c b/test/c/std/crc32.c
index b2c395d..33322c0 100644
--- a/test/c/std/crc32.c
+++ b/test/c/std/crc32.c
@@ -288,7 +288,7 @@
   return do_bench_io_buffers(
       wuffs_bench_crc32_ieee,
       WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED, tc_src,
-      &crc32_midsummer_gt, 0, 0, 1500);
+      &crc32_midsummer_gt, UINT64_MAX, UINT64_MAX, 1500);
 }
 
 const char* bench_wuffs_crc32_ieee_100k() {
@@ -296,7 +296,7 @@
   return do_bench_io_buffers(
       wuffs_bench_crc32_ieee,
       WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED, tc_src,
-      &crc32_pi_gt, 0, 0, 150);
+      &crc32_pi_gt, UINT64_MAX, UINT64_MAX, 150);
 }
 
   // ---------------- Mimic Benches
@@ -306,13 +306,13 @@
 const char* bench_mimic_crc32_ieee_10k() {
   CHECK_FOCUS(__func__);
   return do_bench_io_buffers(mimic_bench_crc32_ieee, 0, tc_src,
-                             &crc32_midsummer_gt, 0, 0, 1500);
+                             &crc32_midsummer_gt, UINT64_MAX, UINT64_MAX, 1500);
 }
 
 const char* bench_mimic_crc32_ieee_100k() {
   CHECK_FOCUS(__func__);
-  return do_bench_io_buffers(mimic_bench_crc32_ieee, 0, tc_src, &crc32_pi_gt, 0,
-                             0, 150);
+  return do_bench_io_buffers(mimic_bench_crc32_ieee, 0, tc_src, &crc32_pi_gt,
+                             UINT64_MAX, UINT64_MAX, 150);
 }
 
 #endif  // WUFFS_MIMIC
diff --git a/test/c/std/deflate.c b/test/c/std/deflate.c
index b011709..c7ac8e9 100644
--- a/test/c/std/deflate.c
+++ b/test/c/std/deflate.c
@@ -148,20 +148,19 @@
   }
 
   while (true) {
-    wuffs_base__io_writer dst_writer = wuffs_base__io_buffer__writer(dst);
-    if (wlimit) {
-      set_writer_limit(&dst_writer, wlimit);
-    }
-    wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(src);
-    if (rlimit) {
-      set_reader_limit(&src_reader, rlimit);
-    }
+    wuffs_base__io_buffer limited_dst = make_limited_writer(*dst, wlimit);
+    wuffs_base__io_buffer limited_src = make_limited_reader(*src, rlimit);
 
     status = wuffs_deflate__decoder__decode_io_writer(
-        &dec, dst_writer, src_reader, global_work_slice);
+        &dec, &limited_dst, &limited_src, global_work_slice);
 
-    if ((wlimit && (status == wuffs_base__suspension__short_write)) ||
-        (rlimit && (status == wuffs_base__suspension__short_read))) {
+    dst->meta.wi += limited_dst.meta.wi;
+    src->meta.ri += limited_src.meta.ri;
+
+    if (((wlimit < UINT64_MAX) &&
+         (status == wuffs_base__suspension__short_write)) ||
+        ((rlimit < UINT64_MAX) &&
+         (status == wuffs_base__suspension__short_read))) {
       continue;
     }
     return status;
@@ -170,25 +169,29 @@
 
 const char* test_wuffs_deflate_decode_256_bytes() {
   CHECK_FOCUS(__func__);
-  return do_test_io_buffers(wuffs_deflate_decode, &deflate_256_bytes_gt, 0, 0);
+  return do_test_io_buffers(wuffs_deflate_decode, &deflate_256_bytes_gt,
+                            UINT64_MAX, UINT64_MAX);
 }
 
 const char* test_wuffs_deflate_decode_deflate_backref_crosses_blocks() {
   CHECK_FOCUS(__func__);
   return do_test_io_buffers(wuffs_deflate_decode,
-                            &deflate_deflate_backref_crosses_blocks_gt, 0, 0);
+                            &deflate_deflate_backref_crosses_blocks_gt,
+                            UINT64_MAX, UINT64_MAX);
 }
 
 const char* test_wuffs_deflate_decode_deflate_distance_32768() {
   CHECK_FOCUS(__func__);
   return do_test_io_buffers(wuffs_deflate_decode,
-                            &deflate_deflate_distance_32768_gt, 0, 0);
+                            &deflate_deflate_distance_32768_gt, UINT64_MAX,
+                            UINT64_MAX);
 }
 
 const char* test_wuffs_deflate_decode_deflate_distance_code_31() {
   CHECK_FOCUS(__func__);
-  const char* got = do_test_io_buffers(
-      wuffs_deflate_decode, &deflate_deflate_distance_code_31_gt, 0, 0);
+  const char* got = do_test_io_buffers(wuffs_deflate_decode,
+                                       &deflate_deflate_distance_code_31_gt,
+                                       UINT64_MAX, UINT64_MAX);
   if (got != wuffs_deflate__error__bad_huffman_code) {
     RETURN_FAIL("got \"%s\", want \"%s\"", got,
                 wuffs_deflate__error__bad_huffman_code);
@@ -198,22 +201,26 @@
 
 const char* test_wuffs_deflate_decode_midsummer() {
   CHECK_FOCUS(__func__);
-  return do_test_io_buffers(wuffs_deflate_decode, &deflate_midsummer_gt, 0, 0);
+  return do_test_io_buffers(wuffs_deflate_decode, &deflate_midsummer_gt,
+                            UINT64_MAX, UINT64_MAX);
 }
 
 const char* test_wuffs_deflate_decode_pi_just_one_read() {
   CHECK_FOCUS(__func__);
-  return do_test_io_buffers(wuffs_deflate_decode, &deflate_pi_gt, 0, 0);
+  return do_test_io_buffers(wuffs_deflate_decode, &deflate_pi_gt, UINT64_MAX,
+                            UINT64_MAX);
 }
 
 const char* test_wuffs_deflate_decode_pi_many_big_reads() {
   CHECK_FOCUS(__func__);
-  return do_test_io_buffers(wuffs_deflate_decode, &deflate_pi_gt, 0, 4096);
+  return do_test_io_buffers(wuffs_deflate_decode, &deflate_pi_gt, UINT64_MAX,
+                            4096);
 }
 
 const char* test_wuffs_deflate_decode_pi_many_medium_reads() {
   CHECK_FOCUS(__func__);
-  return do_test_io_buffers(wuffs_deflate_decode, &deflate_pi_gt, 0, 599);
+  return do_test_io_buffers(wuffs_deflate_decode, &deflate_pi_gt, UINT64_MAX,
+                            599);
 }
 
 const char* test_wuffs_deflate_decode_pi_many_small_writes_reads() {
@@ -223,13 +230,14 @@
 
 const char* test_wuffs_deflate_decode_romeo() {
   CHECK_FOCUS(__func__);
-  return do_test_io_buffers(wuffs_deflate_decode, &deflate_romeo_gt, 0, 0);
+  return do_test_io_buffers(wuffs_deflate_decode, &deflate_romeo_gt, UINT64_MAX,
+                            UINT64_MAX);
 }
 
 const char* test_wuffs_deflate_decode_romeo_fixed() {
   CHECK_FOCUS(__func__);
-  return do_test_io_buffers(wuffs_deflate_decode, &deflate_romeo_fixed_gt, 0,
-                            0);
+  return do_test_io_buffers(wuffs_deflate_decode, &deflate_romeo_fixed_gt,
+                            UINT64_MAX, UINT64_MAX);
 }
 
 const char* test_wuffs_deflate_decode_split_src() {
@@ -256,9 +264,6 @@
     return status;
   }
 
-  wuffs_base__io_writer dst_writer = wuffs_base__io_buffer__writer(&got);
-  wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
-
   int i;
   for (i = 1; i < 32; i++) {
     size_t split = gt->src_offset0 + i;
@@ -279,13 +284,13 @@
     src.meta.ri = gt->src_offset0;
     src.meta.wi = split;
     const char* z0 = wuffs_deflate__decoder__decode_io_writer(
-        &dec, dst_writer, src_reader, global_work_slice);
+        &dec, &got, &src, global_work_slice);
 
     src.meta.closed = true;
     src.meta.ri = split;
     src.meta.wi = gt->src_offset1;
     const char* z1 = wuffs_deflate__decoder__decode_io_writer(
-        &dec, dst_writer, src_reader, global_work_slice);
+        &dec, &got, &src, global_work_slice);
 
     if (z0 != wuffs_base__suspension__short_read) {
       RETURN_FAIL("i=%d: z0: got \"%s\", want \"%s\"", i, z0,
@@ -312,22 +317,20 @@
                                           wuffs_base__io_buffer* got,
                                           wuffs_deflate__decoder* dec,
                                           uint32_t starting_history_index,
-                                          uint64_t limit,
+                                          uint64_t wlimit,
                                           const char* want_z) {
   src->meta.ri = gt->src_offset0;
   src->meta.wi = gt->src_offset1;
   got->meta.ri = 0;
   got->meta.wi = 0;
 
-  wuffs_base__io_writer dst_writer = wuffs_base__io_buffer__writer(got);
-  wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(src);
+  wuffs_base__io_buffer limited_got = make_limited_writer(*got, wlimit);
 
   dec->private_impl.f_history_index = starting_history_index;
 
-  set_writer_limit(&dst_writer, limit);
-
   const char* got_z = wuffs_deflate__decoder__decode_io_writer(
-      dec, dst_writer, src_reader, global_work_slice);
+      dec, &limited_got, src, global_work_slice);
+  got->meta.wi += limited_got.meta.wi;
   if (got_z != want_z) {
     RETURN_FAIL("i=%d: starting_history_index=0x%04" PRIX32
                 ": decode status: got \"%s\", want \"%s\"",
@@ -624,25 +627,29 @@
 
 const char* test_mimic_deflate_decode_256_bytes() {
   CHECK_FOCUS(__func__);
-  return do_test_io_buffers(mimic_deflate_decode, &deflate_256_bytes_gt, 0, 0);
+  return do_test_io_buffers(mimic_deflate_decode, &deflate_256_bytes_gt,
+                            UINT64_MAX, UINT64_MAX);
 }
 
 const char* test_mimic_deflate_decode_deflate_backref_crosses_blocks() {
   CHECK_FOCUS(__func__);
   return do_test_io_buffers(mimic_deflate_decode,
-                            &deflate_deflate_backref_crosses_blocks_gt, 0, 0);
+                            &deflate_deflate_backref_crosses_blocks_gt,
+                            UINT64_MAX, UINT64_MAX);
 }
 
 const char* test_mimic_deflate_decode_deflate_distance_32768() {
   CHECK_FOCUS(__func__);
   return do_test_io_buffers(mimic_deflate_decode,
-                            &deflate_deflate_distance_32768_gt, 0, 0);
+                            &deflate_deflate_distance_32768_gt, UINT64_MAX,
+                            UINT64_MAX);
 }
 
 const char* test_mimic_deflate_decode_deflate_distance_code_31() {
   CHECK_FOCUS(__func__);
-  const char* got = do_test_io_buffers(
-      mimic_deflate_decode, &deflate_deflate_distance_code_31_gt, 0, 0);
+  const char* got = do_test_io_buffers(mimic_deflate_decode,
+                                       &deflate_deflate_distance_code_31_gt,
+                                       UINT64_MAX, UINT64_MAX);
   const char* want = "inflate failed (data error)";
   if ((got != want) && ((got == NULL) || (want == NULL) || strcmp(got, want))) {
     RETURN_FAIL("got \"%s\", want \"%s\"", got, want);
@@ -652,28 +659,32 @@
 
 const char* test_mimic_deflate_decode_midsummer() {
   CHECK_FOCUS(__func__);
-  return do_test_io_buffers(mimic_deflate_decode, &deflate_midsummer_gt, 0, 0);
+  return do_test_io_buffers(mimic_deflate_decode, &deflate_midsummer_gt,
+                            UINT64_MAX, UINT64_MAX);
 }
 
 const char* test_mimic_deflate_decode_pi_just_one_read() {
   CHECK_FOCUS(__func__);
-  return do_test_io_buffers(mimic_deflate_decode, &deflate_pi_gt, 0, 0);
+  return do_test_io_buffers(mimic_deflate_decode, &deflate_pi_gt, UINT64_MAX,
+                            UINT64_MAX);
 }
 
 const char* test_mimic_deflate_decode_pi_many_big_reads() {
   CHECK_FOCUS(__func__);
-  return do_test_io_buffers(mimic_deflate_decode, &deflate_pi_gt, 0, 4096);
+  return do_test_io_buffers(mimic_deflate_decode, &deflate_pi_gt, UINT64_MAX,
+                            4096);
 }
 
 const char* test_mimic_deflate_decode_romeo() {
   CHECK_FOCUS(__func__);
-  return do_test_io_buffers(mimic_deflate_decode, &deflate_romeo_gt, 0, 0);
+  return do_test_io_buffers(mimic_deflate_decode, &deflate_romeo_gt, UINT64_MAX,
+                            UINT64_MAX);
 }
 
 const char* test_mimic_deflate_decode_romeo_fixed() {
   CHECK_FOCUS(__func__);
-  return do_test_io_buffers(mimic_deflate_decode, &deflate_romeo_fixed_gt, 0,
-                            0);
+  return do_test_io_buffers(mimic_deflate_decode, &deflate_romeo_fixed_gt,
+                            UINT64_MAX, UINT64_MAX);
 }
 
 #endif  // WUFFS_MIMIC
@@ -684,7 +695,7 @@
   CHECK_FOCUS(__func__);
   return do_bench_io_buffers(wuffs_deflate_decode,
                              WUFFS_INITIALIZE__DEFAULT_OPTIONS, tc_dst,
-                             &deflate_romeo_gt, 0, 0, 2000);
+                             &deflate_romeo_gt, UINT64_MAX, UINT64_MAX, 2000);
 }
 
 const char* bench_wuffs_deflate_decode_1k_part_init() {
@@ -692,14 +703,14 @@
   return do_bench_io_buffers(
       wuffs_deflate_decode,
       WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED, tc_dst,
-      &deflate_romeo_gt, 0, 0, 2000);
+      &deflate_romeo_gt, UINT64_MAX, UINT64_MAX, 2000);
 }
 
 const char* bench_wuffs_deflate_decode_10k_full_init() {
   CHECK_FOCUS(__func__);
-  return do_bench_io_buffers(wuffs_deflate_decode,
-                             WUFFS_INITIALIZE__DEFAULT_OPTIONS, tc_dst,
-                             &deflate_midsummer_gt, 0, 0, 300);
+  return do_bench_io_buffers(
+      wuffs_deflate_decode, WUFFS_INITIALIZE__DEFAULT_OPTIONS, tc_dst,
+      &deflate_midsummer_gt, UINT64_MAX, UINT64_MAX, 300);
 }
 
 const char* bench_wuffs_deflate_decode_10k_part_init() {
@@ -707,7 +718,7 @@
   return do_bench_io_buffers(
       wuffs_deflate_decode,
       WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED, tc_dst,
-      &deflate_midsummer_gt, 0, 0, 300);
+      &deflate_midsummer_gt, UINT64_MAX, UINT64_MAX, 300);
 }
 
 const char* bench_wuffs_deflate_decode_100k_just_one_read() {
@@ -715,7 +726,7 @@
   return do_bench_io_buffers(
       wuffs_deflate_decode,
       WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED, tc_dst,
-      &deflate_pi_gt, 0, 0, 30);
+      &deflate_pi_gt, UINT64_MAX, UINT64_MAX, 30);
 }
 
 const char* bench_wuffs_deflate_decode_100k_many_big_reads() {
@@ -723,7 +734,7 @@
   return do_bench_io_buffers(
       wuffs_deflate_decode,
       WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED, tc_dst,
-      &deflate_pi_gt, 0, 4096, 30);
+      &deflate_pi_gt, UINT64_MAX, 4096, 30);
 }
 
   // ---------------- Mimic Benches
@@ -733,25 +744,26 @@
 const char* bench_mimic_deflate_decode_1k() {
   CHECK_FOCUS(__func__);
   return do_bench_io_buffers(mimic_deflate_decode, 0, tc_dst, &deflate_romeo_gt,
-                             0, 0, 2000);
+                             UINT64_MAX, UINT64_MAX, 2000);
 }
 
 const char* bench_mimic_deflate_decode_10k() {
   CHECK_FOCUS(__func__);
   return do_bench_io_buffers(mimic_deflate_decode, 0, tc_dst,
-                             &deflate_midsummer_gt, 0, 0, 300);
+                             &deflate_midsummer_gt, UINT64_MAX, UINT64_MAX,
+                             300);
 }
 
 const char* bench_mimic_deflate_decode_100k_just_one_read() {
   CHECK_FOCUS(__func__);
-  return do_bench_io_buffers(mimic_deflate_decode, 0, tc_dst, &deflate_pi_gt, 0,
-                             0, 30);
+  return do_bench_io_buffers(mimic_deflate_decode, 0, tc_dst, &deflate_pi_gt,
+                             UINT64_MAX, UINT64_MAX, 30);
 }
 
 const char* bench_mimic_deflate_decode_100k_many_big_reads() {
   CHECK_FOCUS(__func__);
-  return do_bench_io_buffers(mimic_deflate_decode, 0, tc_dst, &deflate_pi_gt, 0,
-                             4096, 30);
+  return do_bench_io_buffers(mimic_deflate_decode, 0, tc_dst, &deflate_pi_gt,
+                             UINT64_MAX, 4096, 30);
 }
 
 #endif  // WUFFS_MIMIC
diff --git a/test/c/std/gif.c b/test/c/std/gif.c
index 6ed3277..ceae7c6 100644
--- a/test/c/std/gif.c
+++ b/test/c/std/gif.c
@@ -73,8 +73,7 @@
 const char* test_basic_bad_receiver() {
   CHECK_FOCUS(__func__);
   wuffs_base__image_config ic = ((wuffs_base__image_config){});
-  wuffs_base__io_reader src = ((wuffs_base__io_reader){});
-  const char* status = wuffs_gif__decoder__decode_image_config(NULL, &ic, src);
+  const char* status = wuffs_gif__decoder__decode_image_config(NULL, &ic, NULL);
   if (status != wuffs_base__error__bad_receiver) {
     RETURN_FAIL("decode_image_config: got \"%s\", want \"%s\"", status,
                 wuffs_base__error__bad_receiver);
@@ -112,8 +111,7 @@
   CHECK_FOCUS(__func__);
   wuffs_gif__decoder dec = ((wuffs_gif__decoder){});
   wuffs_base__image_config ic = ((wuffs_base__image_config){});
-  wuffs_base__io_reader src = ((wuffs_base__io_reader){});
-  const char* status = wuffs_gif__decoder__decode_image_config(&dec, &ic, src);
+  const char* status = wuffs_gif__decoder__decode_image_config(&dec, &ic, NULL);
   if (status != wuffs_base__error__initialize_not_called) {
     RETURN_FAIL("decode_image_config: got \"%s\", want \"%s\"", status,
                 wuffs_base__error__initialize_not_called);
@@ -205,8 +203,7 @@
 
   wuffs_base__image_config ic = ((wuffs_base__image_config){});
   wuffs_base__frame_config fc = ((wuffs_base__frame_config){});
-  wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(src);
-  status = wuffs_gif__decoder__decode_image_config(&dec, &ic, src_reader);
+  status = wuffs_gif__decoder__decode_image_config(&dec, &ic, src);
   if (status) {
     return status;
   }
@@ -224,15 +221,15 @@
   }
 
   while (true) {
-    status = wuffs_gif__decoder__decode_frame_config(&dec, &fc, src_reader);
+    status = wuffs_gif__decoder__decode_frame_config(&dec, &fc, src);
     if (status == wuffs_base__warning__end_of_data) {
       break;
     } else if (status) {
       return status;
     }
 
-    status = wuffs_gif__decoder__decode_frame(&dec, &pb, src_reader,
-                                              global_work_slice, NULL);
+    status = wuffs_gif__decoder__decode_frame(&dec, &pb, src, global_work_slice,
+                                              NULL);
     if (status) {
       return status;
     }
@@ -276,8 +273,7 @@
 
   {
     wuffs_base__image_config ic = ((wuffs_base__image_config){});
-    wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
-    status = wuffs_gif__decoder__decode_image_config(&dec, &ic, src_reader);
+    status = wuffs_gif__decoder__decode_image_config(&dec, &ic, &src);
     if (status) {
       RETURN_FAIL("decode_image_config: got \"%s\"", status);
     }
@@ -324,13 +320,11 @@
   int num_iters = 0;
   while (true) {
     num_iters++;
-    wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
-    if (rlimit) {
-      set_reader_limit(&src_reader, rlimit);
-    }
+    wuffs_base__io_buffer limited_src = make_limited_reader(src, rlimit);
     size_t old_ri = src.meta.ri;
 
-    status = wuffs_gif__decoder__decode_frame_config(&dec, &fc, src_reader);
+    status = wuffs_gif__decoder__decode_frame_config(&dec, &fc, &limited_src);
+    src.meta.ri += limited_src.meta.ri;
 
     if (!status) {
       break;
@@ -350,14 +344,12 @@
 
   while (true) {
     num_iters++;
-    wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
-    if (rlimit) {
-      set_reader_limit(&src_reader, rlimit);
-    }
+    wuffs_base__io_buffer limited_src = make_limited_reader(src, rlimit);
     size_t old_ri = src.meta.ri;
 
     const char* status = wuffs_gif__decoder__decode_frame(
-        &dec, &pb, src_reader, global_work_slice, NULL);
+        &dec, &pb, &limited_src, global_work_slice, NULL);
+    src.meta.ri += limited_src.meta.ri;
 
     if (!status) {
       break;
@@ -381,7 +373,7 @@
     return status;
   }
 
-  if (rlimit) {
+  if (rlimit < UINT64_MAX) {
     if (num_iters <= 2) {
       RETURN_FAIL("num_iters: got %d, want > 2", num_iters);
     }
@@ -432,15 +424,49 @@
     if (ind_want.meta.wi > (expanded_want.data.len / 4)) {
       RETURN_FAIL("indexes are too long to expand into the work buffer");
     }
-    size_t i;
-    for (i = 0; i < ind_want.meta.wi; i++) {
-      uint8_t index = ind_want.data.ptr[i];
-      expanded_want.data.ptr[4 * i + 0] = pal_want_array[4 * index + 0];
-      expanded_want.data.ptr[4 * i + 1] = pal_want_array[4 * index + 1];
-      expanded_want.data.ptr[4 * i + 2] = pal_want_array[4 * index + 2];
-      expanded_want.data.ptr[4 * i + 3] = pal_want_array[4 * index + 3];
+
+    if (dst_pixfmt == WUFFS_BASE__PIXEL_FORMAT__BGR) {
+      size_t i;
+      for (i = 0; i < ind_want.meta.wi; i++) {
+        uint8_t index = ind_want.data.ptr[i];
+        expanded_want.data.ptr[3 * i + 0] = pal_want_array[4 * index + 0];
+        expanded_want.data.ptr[3 * i + 1] = pal_want_array[4 * index + 1];
+        expanded_want.data.ptr[3 * i + 2] = pal_want_array[4 * index + 2];
+      }
+      expanded_want.meta.wi = 3 * ind_want.meta.wi;
+    } else if (dst_pixfmt == WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL) {
+      size_t i;
+      for (i = 0; i < ind_want.meta.wi; i++) {
+        uint8_t index = ind_want.data.ptr[i];
+        expanded_want.data.ptr[4 * i + 0] = pal_want_array[4 * index + 0];
+        expanded_want.data.ptr[4 * i + 1] = pal_want_array[4 * index + 1];
+        expanded_want.data.ptr[4 * i + 2] = pal_want_array[4 * index + 2];
+        expanded_want.data.ptr[4 * i + 3] = pal_want_array[4 * index + 3];
+      }
+      expanded_want.meta.wi = 4 * ind_want.meta.wi;
+    } else if (dst_pixfmt == WUFFS_BASE__PIXEL_FORMAT__RGB) {
+      size_t i;
+      for (i = 0; i < ind_want.meta.wi; i++) {
+        uint8_t index = ind_want.data.ptr[i];
+        expanded_want.data.ptr[3 * i + 0] = pal_want_array[4 * index + 2];
+        expanded_want.data.ptr[3 * i + 1] = pal_want_array[4 * index + 1];
+        expanded_want.data.ptr[3 * i + 2] = pal_want_array[4 * index + 0];
+      }
+      expanded_want.meta.wi = 3 * ind_want.meta.wi;
+    } else if (dst_pixfmt == WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL) {
+      size_t i;
+      for (i = 0; i < ind_want.meta.wi; i++) {
+        uint8_t index = ind_want.data.ptr[i];
+        expanded_want.data.ptr[4 * i + 0] = pal_want_array[4 * index + 2];
+        expanded_want.data.ptr[4 * i + 1] = pal_want_array[4 * index + 1];
+        expanded_want.data.ptr[4 * i + 2] = pal_want_array[4 * index + 0];
+        expanded_want.data.ptr[4 * i + 3] = pal_want_array[4 * index + 3];
+      }
+      expanded_want.meta.wi = 4 * ind_want.meta.wi;
+    } else {
+      return "unsupported pixel format";
     }
-    expanded_want.meta.wi = 4 * ind_want.meta.wi;
+
     status = check_io_buffers_equal("pixels ", &got, &expanded_want);
     if (status) {
       return status;
@@ -451,8 +477,7 @@
     if (src.meta.ri == src.meta.wi) {
       RETURN_FAIL("decode_frame returned \"ok\" but src was exhausted");
     }
-    wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
-    status = wuffs_gif__decoder__decode_frame(&dec, &pb, src_reader,
+    status = wuffs_gif__decoder__decode_frame(&dec, &pb, &src,
                                               global_work_slice, NULL);
     if (status != wuffs_base__warning__end_of_data) {
       RETURN_FAIL("decode_frame: got \"%s\", want \"%s\"", status,
@@ -468,6 +493,7 @@
 }
 
 const char* do_test_wuffs_gif_decode_expecting(wuffs_base__io_buffer src,
+                                               uint32_t quirk,
                                                const char* want_status,
                                                bool want_dirty_rect_is_empty) {
   wuffs_gif__decoder dec;
@@ -477,11 +503,13 @@
   if (status) {
     RETURN_FAIL("initialize: \"%s\"", status);
   }
+  if (quirk) {
+    wuffs_gif__decoder__set_quirk_enabled(&dec, quirk, true);
+  }
 
   wuffs_base__image_config ic = ((wuffs_base__image_config){});
-  wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
 
-  status = wuffs_gif__decoder__decode_image_config(&dec, &ic, src_reader);
+  status = wuffs_gif__decoder__decode_image_config(&dec, &ic, &src);
   if (status) {
     RETURN_FAIL("decode_image_config: got \"%s\"", status);
   }
@@ -493,8 +521,8 @@
     RETURN_FAIL("set_from_slice: \"%s\"", status);
   }
 
-  status = wuffs_gif__decoder__decode_frame(&dec, &pb, src_reader,
-                                            global_work_slice, NULL);
+  status = wuffs_gif__decoder__decode_frame(&dec, &pb, &src, global_work_slice,
+                                            NULL);
   if (status != want_status) {
     RETURN_FAIL("decode_frame: got \"%s\", want \"%s\"", status, want_status);
   }
@@ -530,10 +558,9 @@
   }
 
   {
-    wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
-    set_reader_limit(&src_reader, 700);
-
-    status = wuffs_gif__decoder__decode_frame_config(&dec, NULL, src_reader);
+    wuffs_base__io_buffer limited_src = make_limited_reader(src, 700);
+    status = wuffs_gif__decoder__decode_frame_config(&dec, NULL, &limited_src);
+    src.meta.ri += limited_src.meta.ri;
     if (status != wuffs_base__suspension__short_read) {
       RETURN_FAIL("decode_frame_config: got \"%s\", want \"%s\"", status,
                   wuffs_base__suspension__short_read);
@@ -545,9 +572,7 @@
   // wuffs_base__error__interleaved_coroutine_calls.
 
   {
-    wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
-
-    status = wuffs_gif__decoder__decode_image_config(&dec, NULL, src_reader);
+    status = wuffs_gif__decoder__decode_image_config(&dec, NULL, &src);
     if (status != wuffs_base__error__interleaved_coroutine_calls) {
       RETURN_FAIL("decode_image_config: got \"%s\", want \"%s\"", status,
                   wuffs_base__error__interleaved_coroutine_calls);
@@ -577,14 +602,12 @@
     RETURN_FAIL("initialize: \"%s\"", status);
   }
 
-  wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
-
-  status = wuffs_gif__decoder__decode_image_config(&dec, NULL, src_reader);
+  status = wuffs_gif__decoder__decode_image_config(&dec, NULL, &src);
   if (status) {
     RETURN_FAIL("decode_image_config: got \"%s\"", status);
   }
 
-  status = wuffs_gif__decoder__decode_image_config(&dec, NULL, src_reader);
+  status = wuffs_gif__decoder__decode_image_config(&dec, NULL, &src);
   if (status != wuffs_base__error__bad_call_sequence) {
     RETURN_FAIL("decode_image_config: got \"%s\", want \"%s\"", status,
                 wuffs_base__error__bad_call_sequence);
@@ -615,9 +638,8 @@
   }
 
   wuffs_base__image_config ic = ((wuffs_base__image_config){});
-  wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
 
-  status = wuffs_gif__decoder__decode_image_config(&dec, &ic, src_reader);
+  status = wuffs_gif__decoder__decode_image_config(&dec, &ic, &src);
   if (status) {
     RETURN_FAIL("decode_image_config: got \"%s\"", status);
   }
@@ -638,7 +660,7 @@
   uint32_t i;
   for (i = 0; i < want_num_frames; i++) {
     wuffs_base__frame_config fc = ((wuffs_base__frame_config){});
-    status = wuffs_gif__decoder__decode_frame_config(&dec, &fc, src_reader);
+    status = wuffs_gif__decoder__decode_frame_config(&dec, &fc, &src);
     if (status) {
       RETURN_FAIL("decode_frame_config #%" PRIu32 ": got \"%s\"", i, status);
     }
@@ -656,7 +678,7 @@
       }
     }
 
-    status = wuffs_gif__decoder__decode_frame(&dec, &pb, src_reader,
+    status = wuffs_gif__decoder__decode_frame(&dec, &pb, &src,
                                               global_work_slice, NULL);
     if (status) {
       RETURN_FAIL("decode_frame #%" PRIu32 ": got \"%s\"", i, status);
@@ -673,7 +695,7 @@
   // There should be no more frames, no matter how many times we call
   // decode_frame.
   for (i = 0; i < 3; i++) {
-    status = wuffs_gif__decoder__decode_frame(&dec, &pb, src_reader,
+    status = wuffs_gif__decoder__decode_frame(&dec, &pb, &src,
                                               global_work_slice, NULL);
     if (status != wuffs_base__warning__end_of_data) {
       RETURN_FAIL("decode_frame: got \"%s\", want \"%s\"", status,
@@ -720,6 +742,212 @@
       want_frame_config_bounds);
 }
 
+const char* test_wuffs_gif_decode_delay_num_frames_decoded() {
+  CHECK_FOCUS(__func__);
+  wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
+      .data = global_src_slice,
+  });
+  const char* status = read_file(&src, "test/data/animated-red-blue.gif");
+  if (status) {
+    return status;
+  }
+  if (src.meta.wi < 1) {
+    return "src file is too short";
+  }
+
+  // A GIF image should end with the 0x3B Trailer byte.
+  if (src.data.ptr[src.meta.wi - 1] != 0x3B) {
+    RETURN_FAIL("final byte: got 0x%02X, want 0x%02X",
+                src.data.ptr[src.meta.wi - 1], 0x3B);
+  }
+  // Replace that final byte with something invalid: neither 0x21 (Extension
+  // Introducer), 0x2C (Image Separator) or 0x3B (Trailer).
+  src.data.ptr[src.meta.wi - 1] = 0x99;
+
+  int q;
+  for (q = 0; q < 2; q++) {
+    src.meta.ri = 0;
+
+    wuffs_gif__decoder dec;
+    status = wuffs_gif__decoder__initialize(
+        &dec, sizeof dec, WUFFS_VERSION,
+        WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED);
+    if (status) {
+      RETURN_FAIL("q=%d: initialize: \"%s\"", q, status);
+    }
+    wuffs_gif__decoder__set_quirk_enabled(
+        &dec, wuffs_gif__quirk_delay_num_decoded_frames, q);
+
+    while (true) {
+      status = wuffs_gif__decoder__decode_frame_config(&dec, NULL, &src);
+      if (status) {
+        break;
+      }
+    }
+
+    uint64_t got = wuffs_gif__decoder__num_decoded_frames(&dec);
+    uint64_t want = q ? 3 : 4;
+    if (got != want) {
+      RETURN_FAIL("q=%d: num_decoded_frames: got %" PRIu64 ", want %" PRIu64, q,
+                  got, want);
+    }
+  }
+  return NULL;
+}
+
+const char* test_wuffs_gif_decode_empty_palette() {
+  CHECK_FOCUS(__func__);
+  wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
+      .data = global_src_slice,
+  });
+  const char* status =
+      read_file(&src, "test/data/artificial/gif-empty-palette.gif");
+  if (status) {
+    return status;
+  }
+  int q;
+  for (q = 0; q < 2; q++) {
+    src.meta.ri = 0;
+
+    wuffs_gif__decoder dec;
+    status = wuffs_gif__decoder__initialize(
+        &dec, sizeof dec, WUFFS_VERSION,
+        WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED);
+    if (status) {
+      RETURN_FAIL("q=%d: initialize: \"%s\"", q, status);
+    }
+    wuffs_gif__decoder__set_quirk_enabled(
+        &dec, wuffs_gif__quirk_reject_empty_palette, q);
+
+    wuffs_base__image_config ic = ((wuffs_base__image_config){});
+    status = wuffs_gif__decoder__decode_image_config(&dec, &ic, &src);
+    if (status) {
+      RETURN_FAIL("q=%d: decode_image_config: \"%s\"", q, status);
+    }
+
+    wuffs_base__pixel_buffer pb = ((wuffs_base__pixel_buffer){});
+    status = wuffs_base__pixel_buffer__set_from_slice(&pb, &ic.pixcfg,
+                                                      global_pixel_slice);
+    if (status) {
+      RETURN_FAIL("q=%d: set_from_slice: \"%s\"", q, status);
+    }
+
+    int i;
+    for (i = 0; i < 2; i++) {
+      status = wuffs_gif__decoder__decode_frame(&dec, &pb, &src,
+                                                global_work_slice, NULL);
+      if ((q == 1) && (i == 1)) {
+        if (status != wuffs_gif__error__bad_palette) {
+          RETURN_FAIL("q=%d: i=%d: decode_frame: got \"%s\", want \"%s\"", q, i,
+                      status, wuffs_gif__error__bad_palette);
+        }
+        break;
+      }
+      if (status) {
+        RETURN_FAIL("q=%d: i=%d: decode_frame: \"%s\"", q, i, status);
+      }
+
+      wuffs_base__slice_u8 pal = wuffs_base__pixel_buffer__palette(&pb);
+      if (pal.len < 4) {
+        RETURN_FAIL("q=%d: i=%d: palette length: got %d, want >= 4", q, i,
+                    (int)(pal.len));
+      }
+      uint8_t* p = pal.ptr;
+      int blue = i ? 0x00 : 0xFF;
+      if ((p[0] != blue) || (p[1] != 0x00) || (p[2] != 0x00) ||
+          (p[3] != 0xFF)) {
+        RETURN_FAIL(
+            "q=%d: i=%d: palette[0] BGRA: got (0x%02X, 0x%02X, 0x%02X, "
+            "0x%02X), want (0x%02X, 0x00, 0x00, 0xFF)",
+            q, i, (int)(p[0]), (int)(p[1]), (int)(p[2]), (int)(p[3]), blue);
+      }
+    }
+  }
+  return NULL;
+}
+
+const char* test_wuffs_gif_decode_background_color() {
+  CHECK_FOCUS(__func__);
+  wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
+      .data = global_src_slice,
+  });
+  const char* status =
+      read_file(&src, "test/data/artificial/gif-background-color.gif");
+  if (status) {
+    return status;
+  }
+  int q;
+  for (q = 0; q < 2; q++) {
+    src.meta.ri = 0;
+
+    wuffs_gif__decoder dec;
+    status = wuffs_gif__decoder__initialize(
+        &dec, sizeof dec, WUFFS_VERSION,
+        WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED);
+    if (status) {
+      RETURN_FAIL("q=%d: initialize: \"%s\"", q, status);
+    }
+    wuffs_gif__decoder__set_quirk_enabled(
+        &dec, wuffs_gif__quirk_honor_background_color, q);
+
+    wuffs_base__frame_config fc = ((wuffs_base__frame_config){});
+    status = wuffs_gif__decoder__decode_frame_config(&dec, &fc, &src);
+    if (status) {
+      RETURN_FAIL("q=%d: decode_frame_config: \"%s\"", q, status);
+    }
+
+    wuffs_base__color_u32_argb_premul got =
+        wuffs_base__frame_config__background_color(&fc);
+    wuffs_base__color_u32_argb_premul want = q ? 0xFF80C3C3 : 0x00000000;
+
+    if (got != want) {
+      RETURN_FAIL("q=%d: got 0x%08" PRIX32 ", want 0x%08" PRIX32, q, got, want);
+    }
+  }
+  return NULL;
+}
+
+const char* test_wuffs_gif_decode_first_frame_is_opaque() {
+  CHECK_FOCUS(__func__);
+  wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
+      .data = global_src_slice,
+  });
+  const char* status =
+      read_file(&src, "test/data/artificial/gif-frame-out-of-bounds.gif");
+  if (status) {
+    return status;
+  }
+  int q;
+  for (q = 0; q < 2; q++) {
+    src.meta.ri = 0;
+
+    wuffs_gif__decoder dec;
+    status = wuffs_gif__decoder__initialize(
+        &dec, sizeof dec, WUFFS_VERSION,
+        WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED);
+    if (status) {
+      RETURN_FAIL("q=%d: initialize: \"%s\"", q, status);
+    }
+    wuffs_gif__decoder__set_quirk_enabled(
+        &dec, wuffs_gif__quirk_honor_background_color, q);
+
+    wuffs_base__image_config ic = ((wuffs_base__image_config){});
+    status = wuffs_gif__decoder__decode_image_config(&dec, &ic, &src);
+    if (status) {
+      RETURN_FAIL("q=%d: decode_image_config: \"%s\"", q, status);
+    }
+
+    bool got = wuffs_base__image_config__first_frame_is_opaque(&ic);
+    bool want = q;
+
+    if (got != want) {
+      RETURN_FAIL("q=%d: got %s, want %s", q, got ? "true" : "false",
+                  want ? "true" : "false");
+    }
+  }
+  return NULL;
+}
+
 const char* test_wuffs_gif_decode_frame_out_of_bounds() {
   CHECK_FOCUS(__func__);
   wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
@@ -731,144 +959,242 @@
     return status;
   }
 
-  wuffs_gif__decoder dec;
-  status = wuffs_gif__decoder__initialize(
-      &dec, sizeof dec, WUFFS_VERSION,
-      WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED);
-  if (status) {
-    RETURN_FAIL("initialize: \"%s\"", status);
-  }
-  wuffs_base__image_config ic = ((wuffs_base__image_config){});
-  wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
-  status = wuffs_gif__decoder__decode_image_config(&dec, &ic, src_reader);
-  if (status) {
-    RETURN_FAIL("decode_image_config: \"%s\"", status);
-  }
+  int q;
+  for (q = 0; q < 2; q++) {
+    src.meta.ri = 0;
 
-  // The nominal width and height for the overall image is 2×2, but its first
-  // frame extends those bounds to 4×2. See
-  // test/data/artificial/gif-frame-out-of-bounds.gif.make-artificial.txt for
-  // more discussion.
+    wuffs_gif__decoder dec;
+    status = wuffs_gif__decoder__initialize(
+        &dec, sizeof dec, WUFFS_VERSION,
+        WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED);
+    if (status) {
+      RETURN_FAIL("q=%d: initialize: \"%s\"", q, status);
+    }
+    wuffs_gif__decoder__set_quirk_enabled(
+        &dec, wuffs_gif__quirk_image_bounds_are_strict, q);
 
-  const uint32_t width = 4;
-  const uint32_t height = 2;
-
-  if (wuffs_base__pixel_config__width(&ic.pixcfg) != width) {
-    RETURN_FAIL("width: got %" PRIu32 ", want %" PRIu32,
-                wuffs_base__pixel_config__width(&ic.pixcfg), width);
-  }
-  if (wuffs_base__pixel_config__height(&ic.pixcfg) != height) {
-    RETURN_FAIL("height: got %" PRIu32 ", want %" PRIu32,
-                wuffs_base__pixel_config__height(&ic.pixcfg), height);
-  }
-
-  wuffs_base__pixel_buffer pb = ((wuffs_base__pixel_buffer){});
-  status = wuffs_base__pixel_buffer__set_from_slice(&pb, &ic.pixcfg,
-                                                    global_pixel_slice);
-  if (status) {
-    RETURN_FAIL("set_from_slice: \"%s\"", status);
-  }
-
-  // See test/data/artificial/gif-frame-out-of-bounds.gif.make-artificial.txt
-  // for the want_frame_config_bounds and want_pixel_indexes source.
-
-  wuffs_base__rect_ie_u32 want_frame_config_bounds[4] = {
-      make_rect_ie_u32(1, 0, 4, 1),
-      make_rect_ie_u32(0, 1, 2, 2),
-      make_rect_ie_u32(0, 2, 1, 2),
-      make_rect_ie_u32(2, 0, 4, 2),
-  };
-
-  const char* want_pixel_indexes[4] = {
-      ".123....",
-      "....89..",
-      "........",
-      "..45..89",
-  };
-
-  uint32_t i;
-  for (i = 0; true; i++) {
-    wuffs_base__frame_config fc = ((wuffs_base__frame_config){});
-    {
-      status = wuffs_gif__decoder__decode_frame_config(&dec, &fc, src_reader);
-      if (i == WUFFS_TESTLIB_ARRAY_SIZE(want_frame_config_bounds)) {
-        if (status != wuffs_base__warning__end_of_data) {
-          RETURN_FAIL("decode_frame_config #%" PRIu32 ": got \"%s\"", i,
-                      status);
-        }
-        break;
-      }
-      if (status) {
-        RETURN_FAIL("decode_frame_config #%" PRIu32 ": got \"%s\"", i, status);
-      }
-
-      wuffs_base__rect_ie_u32 got = wuffs_base__frame_config__bounds(&fc);
-      wuffs_base__rect_ie_u32 want = want_frame_config_bounds[i];
-      if (!wuffs_base__rect_ie_u32__equals(&got, want)) {
-        RETURN_FAIL("decode_frame_config #%" PRIu32 ": bounds: got (%" PRIu32
-                    ", %" PRIu32 ")-(%" PRIu32 ", %" PRIu32 "), want (%" PRIu32
-                    ", %" PRIu32 ")-(%" PRIu32 ", %" PRIu32 ")",
-                    i, got.min_incl_x, got.min_incl_y, got.max_excl_x,
-                    got.max_excl_y, want.min_incl_x, want.min_incl_y,
-                    want.max_excl_x, want.max_excl_y);
-      }
+    wuffs_base__image_config ic = ((wuffs_base__image_config){});
+    status = wuffs_gif__decoder__decode_image_config(&dec, &ic, &src);
+    if (status) {
+      RETURN_FAIL("q=%d: decode_image_config: \"%s\"", q, status);
     }
 
-    {
-      wuffs_base__table_u8 p = wuffs_base__pixel_buffer__plane(&pb, 0);
-      uint32_t y, x;
-      for (y = 0; y < height; y++) {
-        for (x = 0; x < width; x++) {
-          p.ptr[(y * p.stride) + x] = 0;
+    // The nominal width and height for the overall image is 2×2, but its first
+    // frame extends those bounds to 4×2. See
+    // test/data/artificial/gif-frame-out-of-bounds.gif.make-artificial.txt for
+    // more discussion.
+
+    const uint32_t width = q ? 2 : 4;
+    const uint32_t height = 2;
+
+    if (wuffs_base__pixel_config__width(&ic.pixcfg) != width) {
+      RETURN_FAIL("q=%d: width: got %" PRIu32 ", want %" PRIu32, q,
+                  wuffs_base__pixel_config__width(&ic.pixcfg), width);
+    }
+    if (wuffs_base__pixel_config__height(&ic.pixcfg) != height) {
+      RETURN_FAIL("q=%d: height: got %" PRIu32 ", want %" PRIu32, q,
+                  wuffs_base__pixel_config__height(&ic.pixcfg), height);
+    }
+
+    wuffs_base__pixel_buffer pb = ((wuffs_base__pixel_buffer){});
+    status = wuffs_base__pixel_buffer__set_from_slice(&pb, &ic.pixcfg,
+                                                      global_pixel_slice);
+    if (status) {
+      RETURN_FAIL("q=%d: set_from_slice: \"%s\"", q, status);
+    }
+
+    // See test/data/artificial/gif-frame-out-of-bounds.gif.make-artificial.txt
+    // for the want_frame_config_bounds and want_pixel_indexes source.
+
+    wuffs_base__rect_ie_u32 want_frame_config_bounds[4] = {
+        q ? make_rect_ie_u32(1, 0, 2, 1) : make_rect_ie_u32(1, 0, 4, 1),
+        q ? make_rect_ie_u32(0, 1, 2, 2) : make_rect_ie_u32(0, 1, 2, 2),
+        q ? make_rect_ie_u32(0, 2, 1, 2) : make_rect_ie_u32(0, 2, 1, 2),
+        q ? make_rect_ie_u32(2, 0, 2, 2) : make_rect_ie_u32(2, 0, 4, 2),
+    };
+
+    const char* want_pixel_indexes[4] = {
+        q ? ".1.." : ".123....",
+        q ? "..89" : "....89..",
+        q ? "...." : "........",
+        q ? "...." : "..45..89",
+    };
+
+    uint32_t i;
+    for (i = 0; true; i++) {
+      wuffs_base__frame_config fc = ((wuffs_base__frame_config){});
+      {
+        status = wuffs_gif__decoder__decode_frame_config(&dec, &fc, &src);
+        if (i == WUFFS_TESTLIB_ARRAY_SIZE(want_frame_config_bounds)) {
+          if (status != wuffs_base__warning__end_of_data) {
+            RETURN_FAIL("q=%d: decode_frame_config #%" PRIu32 ": got \"%s\"", q,
+                        i, status);
+          }
+          break;
+        }
+        if (status) {
+          RETURN_FAIL("q=%d: decode_frame_config #%" PRIu32 ": got \"%s\"", q,
+                      i, status);
+        }
+
+        wuffs_base__rect_ie_u32 got = wuffs_base__frame_config__bounds(&fc);
+        wuffs_base__rect_ie_u32 want = want_frame_config_bounds[i];
+        if (!wuffs_base__rect_ie_u32__equals(&got, want)) {
+          RETURN_FAIL("q=%d: decode_frame_config #%" PRIu32
+                      ": bounds: got (%" PRIu32 ", %" PRIu32 ")-(%" PRIu32
+                      ", %" PRIu32 "), want (%" PRIu32 ", %" PRIu32
+                      ")-(%" PRIu32 ", %" PRIu32 ")",
+                      q, i, got.min_incl_x, got.min_incl_y, got.max_excl_x,
+                      got.max_excl_y, want.min_incl_x, want.min_incl_y,
+                      want.max_excl_x, want.max_excl_y);
         }
       }
 
-      status = wuffs_gif__decoder__decode_frame(&dec, &pb, src_reader,
-                                                global_work_slice, NULL);
-      if (status) {
-        RETURN_FAIL("decode_frame #%" PRIu32 ": got \"%s\"", i, status);
-      }
-
-      wuffs_base__rect_ie_u32 frame_rect =
-          wuffs_base__frame_config__bounds(&fc);
-      wuffs_base__rect_ie_u32 dirty_rect =
-          wuffs_gif__decoder__frame_dirty_rect(&dec);
-      if (!wuffs_base__rect_ie_u32__contains_rect(&frame_rect, dirty_rect)) {
-        RETURN_FAIL("internal error: frame_rect does not contain dirty_rect");
-      }
-
-      char got[(width * height) + 1];
-      for (y = 0; y < height; y++) {
-        for (x = 0; x < width; x++) {
-          uint8_t index = 0x0F & p.ptr[(y * p.stride) + x];
-          got[(y * width) + x] = index ? ('0' + index) : '.';
+      {
+        wuffs_base__table_u8 p = wuffs_base__pixel_buffer__plane(&pb, 0);
+        uint32_t y, x;
+        for (y = 0; y < height; y++) {
+          for (x = 0; x < width; x++) {
+            p.ptr[(y * p.stride) + x] = 0;
+          }
         }
-      }
-      got[width * height] = 0;
 
-      const char* want = want_pixel_indexes[i];
-      if (memcmp(got, want, width * height)) {
-        RETURN_FAIL("decode_frame #%" PRIu32 ": got \"%s\", want \"%s\"", i,
-                    got, want);
+        status = wuffs_gif__decoder__decode_frame(&dec, &pb, &src,
+                                                  global_work_slice, NULL);
+        if (status) {
+          RETURN_FAIL("q=%d: decode_frame #%" PRIu32 ": got \"%s\"", q, i,
+                      status);
+        }
+
+        wuffs_base__rect_ie_u32 frame_rect =
+            wuffs_base__frame_config__bounds(&fc);
+        wuffs_base__rect_ie_u32 dirty_rect =
+            wuffs_gif__decoder__frame_dirty_rect(&dec);
+        if (!wuffs_base__rect_ie_u32__contains_rect(&frame_rect, dirty_rect)) {
+          RETURN_FAIL(
+              "q=%d: internal error: frame_rect does not contain dirty_rect",
+              q);
+        }
+
+        char got[(width * height) + 1];
+        for (y = 0; y < height; y++) {
+          for (x = 0; x < width; x++) {
+            uint8_t index = 0x0F & p.ptr[(y * p.stride) + x];
+            got[(y * width) + x] = index ? ('0' + index) : '.';
+          }
+        }
+        got[width * height] = 0;
+
+        const char* want = want_pixel_indexes[i];
+        if (memcmp(got, want, width * height)) {
+          RETURN_FAIL("q=%d: decode_frame #%" PRIu32
+                      ": got \"%s\", want \"%s\"",
+                      q, i, got, want);
+        }
       }
     }
   }
   return NULL;
 }
 
-const char* test_wuffs_gif_decode_bgra_nonpremul() {
+const char* test_wuffs_gif_decode_zero_width_frame() {
+  CHECK_FOCUS(__func__);
+  wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
+      .data = global_src_slice,
+  });
+  const char* status =
+      read_file(&src, "test/data/artificial/gif-zero-width-frame.gif");
+  if (status) {
+    return status;
+  }
+  int q;
+  for (q = 0; q < 3; q++) {
+    src.meta.ri = 0;
+
+    wuffs_gif__decoder dec;
+    status = wuffs_gif__decoder__initialize(
+        &dec, sizeof dec, WUFFS_VERSION,
+        WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED);
+    if (status) {
+      RETURN_FAIL("q=%d: initialize: \"%s\"", q, status);
+    }
+
+    const char* want = NULL;
+    switch (q) {
+      case 0:
+        want = wuffs_base__error__too_much_data;
+        break;
+      case 1:
+        want = NULL;
+        wuffs_gif__decoder__set_quirk_enabled(
+            &dec, wuffs_gif__quirk_ignore_too_much_pixel_data, true);
+        break;
+      case 2:
+        want = wuffs_gif__error__bad_frame_size;
+        wuffs_gif__decoder__set_quirk_enabled(
+            &dec, wuffs_gif__quirk_reject_empty_frame, true);
+        break;
+    }
+
+    wuffs_base__image_config ic = ((wuffs_base__image_config){});
+    status = wuffs_gif__decoder__decode_image_config(&dec, &ic, &src);
+    if (status) {
+      RETURN_FAIL("q=%d: decode_image_config: \"%s\"", q, status);
+    }
+
+    wuffs_base__pixel_buffer pb = ((wuffs_base__pixel_buffer){});
+    status = wuffs_base__pixel_buffer__set_from_slice(&pb, &ic.pixcfg,
+                                                      global_pixel_slice);
+    if (status) {
+      RETURN_FAIL("q=%d: set_from_slice: \"%s\"", q, status);
+    }
+
+    const char* got = wuffs_gif__decoder__decode_frame(&dec, &pb, &src,
+                                                       global_work_slice, NULL);
+    if (got != want) {
+      RETURN_FAIL("q=%d: decode_frame: got \"%s\", want \"%s\"", q, got, want);
+    }
+  }
+  return NULL;
+}
+
+const char* test_wuffs_gif_decode_pixfmt_bgr() {
   CHECK_FOCUS(__func__);
   return do_test_wuffs_gif_decode("test/data/bricks-dither.gif",
                                   "test/data/bricks-dither.palette",
-                                  "test/data/bricks-dither.indexes", 0,
+                                  "test/data/bricks-dither.indexes", UINT64_MAX,
+                                  WUFFS_BASE__PIXEL_FORMAT__BGR);
+}
+
+const char* test_wuffs_gif_decode_pixfmt_bgra_nonpremul() {
+  CHECK_FOCUS(__func__);
+  return do_test_wuffs_gif_decode("test/data/bricks-dither.gif",
+                                  "test/data/bricks-dither.palette",
+                                  "test/data/bricks-dither.indexes", UINT64_MAX,
                                   WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL);
 }
 
+const char* test_wuffs_gif_decode_pixfmt_rgb() {
+  CHECK_FOCUS(__func__);
+  return do_test_wuffs_gif_decode("test/data/bricks-dither.gif",
+                                  "test/data/bricks-dither.palette",
+                                  "test/data/bricks-dither.indexes", UINT64_MAX,
+                                  WUFFS_BASE__PIXEL_FORMAT__RGB);
+}
+
+const char* test_wuffs_gif_decode_pixfmt_rgba_nonpremul() {
+  CHECK_FOCUS(__func__);
+  return do_test_wuffs_gif_decode("test/data/bricks-dither.gif",
+                                  "test/data/bricks-dither.palette",
+                                  "test/data/bricks-dither.indexes", UINT64_MAX,
+                                  WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL);
+}
+
 const char* test_wuffs_gif_decode_input_is_a_gif_just_one_read() {
   CHECK_FOCUS(__func__);
   return do_test_wuffs_gif_decode(
       "test/data/bricks-dither.gif", "test/data/bricks-dither.palette",
-      "test/data/bricks-dither.indexes", 0,
+      "test/data/bricks-dither.indexes", UINT64_MAX,
       WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_BINARY);
 }
 
@@ -920,9 +1246,8 @@
     RETURN_FAIL("initialize: \"%s\"", status);
   }
   wuffs_base__image_config ic = ((wuffs_base__image_config){});
-  wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
 
-  status = wuffs_gif__decoder__decode_image_config(&dec, &ic, src_reader);
+  status = wuffs_gif__decoder__decode_image_config(&dec, &ic, &src);
   if (status != wuffs_gif__error__bad_header) {
     RETURN_FAIL("decode_image_config: got \"%s\", want \"%s\"", status,
                 wuffs_gif__error__bad_header);
@@ -930,6 +1255,163 @@
   return NULL;
 }
 
+const char* test_wuffs_gif_decode_metadata() {
+  CHECK_FOCUS(__func__);
+
+  wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
+      .data = global_src_slice,
+  });
+
+  const char* status = read_file(&src, "test/data/artificial/gif-metadata.gif");
+  if (status) {
+    return status;
+  }
+
+  int iccp;
+  for (iccp = 0; iccp < 2; iccp++) {
+    int xmp;
+    for (xmp = 0; xmp < 2; xmp++) {
+      bool seen_iccp = false;
+      bool seen_xmp = false;
+
+      wuffs_gif__decoder dec;
+      status = wuffs_gif__decoder__initialize(
+          &dec, sizeof dec, WUFFS_VERSION,
+          WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED);
+      if (status) {
+        RETURN_FAIL("initialize: \"%s\"", status);
+      }
+
+      if (iccp) {
+        wuffs_gif__decoder__set_report_metadata(&dec, WUFFS_BASE__FOURCC__ICCP,
+                                                true);
+      }
+      if (xmp) {
+        wuffs_gif__decoder__set_report_metadata(&dec, WUFFS_BASE__FOURCC__XMP,
+                                                true);
+      }
+
+      wuffs_base__image_config ic = ((wuffs_base__image_config){});
+      src.meta.ri = 0;
+
+      while (true) {
+        status = wuffs_gif__decoder__decode_image_config(&dec, &ic, &src);
+        if (!status) {
+          break;
+        } else if (status != wuffs_base__warning__metadata_reported) {
+          RETURN_FAIL(
+              "decode_image_config (iccp=%d, xmp=%d): got \"%s\", want \"%s\"",
+              iccp, xmp, status, wuffs_base__warning__metadata_reported);
+        }
+
+        const char* want = "";
+        char got_buffer[100];
+        int got_length = 0;
+        uint32_t got_fourcc = wuffs_gif__decoder__metadata_fourcc(&dec);
+
+        switch (wuffs_gif__decoder__metadata_fourcc(&dec)) {
+          case WUFFS_BASE__FOURCC__ICCP:
+            want = "\x16\x26\x36\x46\x56\x76\x86\x96";
+            seen_iccp = true;
+            break;
+          case WUFFS_BASE__FOURCC__XMP:
+            want = "\x05\x17\x27\x37\x47\x57\x03\x77\x87\x97";
+            seen_xmp = true;
+            break;
+          default:
+            RETURN_FAIL(
+                "metadata_fourcc (iccp=%d, xmp=%d): unexpected FourCC "
+                "0x%08" PRIX32,
+                iccp, xmp, got_fourcc);
+        }
+
+        while (true) {
+          uint64_t n = wuffs_gif__decoder__metadata_chunk_length(&dec);
+          if ((n > 100) || (n + got_length > 100)) {
+            RETURN_FAIL(
+                "metadata_chunk_length (iccp=%d, xmp=%d): too much "
+                "metadata (vs buffer size)",
+                iccp, xmp);
+          }
+          if (n > wuffs_base__io_buffer__reader_available(&src)) {
+            RETURN_FAIL(
+                "metadata_chunk_length (iccp=%d, xmp=%d): too much "
+                "metadata (vs available)",
+                iccp, xmp);
+          }
+          memcpy(got_buffer + got_length, src.data.ptr + src.meta.ri, n);
+          got_length += n;
+          src.meta.ri += n;
+
+          status = wuffs_gif__decoder__ack_metadata_chunk(&dec, &src);
+          if (!status) {
+            break;
+          } else if (status != wuffs_base__warning__metadata_reported) {
+            RETURN_FAIL(
+                "ack_metadata_chunk (iccp=%d, xmp=%d): got \"%s\", want \"%s\"",
+                iccp, xmp, status, wuffs_base__warning__metadata_reported);
+          }
+        }
+
+        int want_length = strlen(want);
+        if ((got_length != want_length) ||
+            strncmp(got_buffer, want, want_length)) {
+          RETURN_FAIL("metadata (iccp=%d, xmp=%d): fourcc=0x%08" PRIX32
+                      ": values differed",
+                      iccp, xmp, got_fourcc);
+        }
+      }
+
+      if (iccp != seen_iccp) {
+        RETURN_FAIL("seen_iccp (iccp=%d, xmp=%d): got %d, want %d", iccp, xmp,
+                    seen_iccp, iccp);
+      }
+
+      if (xmp != seen_xmp) {
+        RETURN_FAIL("seen_xmp (iccp=%d, xmp=%d): got %d, want %d", iccp, xmp,
+                    seen_xmp, xmp);
+      }
+
+      {
+        uint64_t got = wuffs_base__image_config__first_frame_io_position(&ic);
+        uint64_t want = 25;
+        if (got != want) {
+          RETURN_FAIL("first_frame_io_position: got %" PRIu64 ", want %" PRIu64,
+                      got, want);
+        }
+      }
+
+      {
+        uint32_t got = wuffs_gif__decoder__num_animation_loops(&dec);
+        uint32_t want = 2001;
+        if (got != want) {
+          RETURN_FAIL("num_animation_loops: got %" PRIu32 ", want %" PRIu32,
+                      got, want);
+        }
+      }
+
+      {
+        wuffs_base__frame_config fc = ((wuffs_base__frame_config){});
+        status = wuffs_gif__decoder__decode_frame_config(&dec, &fc, &src);
+        if (status) {
+          RETURN_FAIL("decode_frame_config (iccp=%d, xmp=%d): %s", iccp, xmp,
+                      status);
+        }
+        uint32_t got = wuffs_base__frame_config__width(&fc);
+        uint32_t want = 1;
+        if (got != want) {
+          RETURN_FAIL(
+              "decode_frame_config (iccp=%d, xmp=%d): width: got %" PRIu32
+              ", want %" PRIu32,
+              iccp, xmp, got, want);
+        }
+      }
+    }
+  }
+
+  return NULL;
+}
+
 const char* test_wuffs_gif_decode_missing_two_src_bytes() {
   CHECK_FOCUS(__func__);
 
@@ -949,7 +1431,40 @@
   src.meta.wi -= 2;
 
   return do_test_wuffs_gif_decode_expecting(
-      src, wuffs_base__suspension__short_read, false);
+      src, 0, wuffs_base__suspension__short_read, false);
+}
+
+const char* test_wuffs_gif_decode_multiple_graphic_controls() {
+  CHECK_FOCUS(__func__);
+  wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
+      .data = global_src_slice,
+  });
+  const char* status =
+      read_file(&src, "test/data/artificial/gif-multiple-graphic-controls.gif");
+  if (status) {
+    return status;
+  }
+
+  wuffs_gif__decoder dec;
+  status = wuffs_gif__decoder__initialize(
+      &dec, sizeof dec, WUFFS_VERSION,
+      WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED);
+  if (status) {
+    RETURN_FAIL("initialize: \"%s\"", status);
+  }
+  wuffs_base__frame_config fc = ((wuffs_base__frame_config){});
+  status = wuffs_gif__decoder__decode_frame_config(&dec, &fc, &src);
+  if (status) {
+    RETURN_FAIL("decode_frame_config: \"%s\"", status);
+  }
+
+  int got = wuffs_base__frame_config__duration(&fc) /
+            WUFFS_BASE__FLICKS_PER_MILLISECOND;
+  int want = 300;
+  if (got != want) {
+    RETURN_FAIL("duration: got %d, want %d", got, want);
+  }
+  return NULL;
 }
 
 const char* test_wuffs_gif_decode_multiple_loop_counts() {
@@ -971,8 +1486,7 @@
     RETURN_FAIL("initialize: \"%s\"", status);
   }
   wuffs_base__image_config ic = ((wuffs_base__image_config){});
-  wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
-  status = wuffs_gif__decoder__decode_image_config(&dec, &ic, src_reader);
+  status = wuffs_gif__decoder__decode_image_config(&dec, &ic, &src);
   if (status) {
     RETURN_FAIL("decode_image_config: \"%s\"", status);
   }
@@ -987,7 +1501,7 @@
   uint32_t i;
   for (i = 0; true; i++) {
     {
-      status = wuffs_gif__decoder__decode_frame_config(&dec, NULL, src_reader);
+      status = wuffs_gif__decoder__decode_frame_config(&dec, NULL, &src);
       if (i == WUFFS_TESTLIB_ARRAY_SIZE(want_num_animation_loops)) {
         if (status != wuffs_base__warning__end_of_data) {
           RETURN_FAIL("decode_frame_config #%" PRIu32 ": got \"%s\"", i,
@@ -1033,7 +1547,7 @@
   }
 
   return do_test_wuffs_gif_decode_expecting(
-      src, wuffs_base__error__not_enough_data, true);
+      src, 0, wuffs_base__error__not_enough_data, true);
 }
 
 const char* test_wuffs_gif_decode_pixel_data_not_enough() {
@@ -1050,10 +1564,10 @@
   }
 
   return do_test_wuffs_gif_decode_expecting(
-      src, wuffs_base__error__not_enough_data, false);
+      src, 0, wuffs_base__error__not_enough_data, false);
 }
 
-const char* test_wuffs_gif_decode_pixel_data_too_much() {
+const char* test_wuffs_gif_decode_pixel_data_too_much_sans_quirk() {
   CHECK_FOCUS(__func__);
 
   wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
@@ -1067,7 +1581,24 @@
   }
 
   return do_test_wuffs_gif_decode_expecting(
-      src, wuffs_base__error__too_much_data, false);
+      src, 0, wuffs_base__error__too_much_data, false);
+}
+
+const char* test_wuffs_gif_decode_pixel_data_too_much_with_quirk() {
+  CHECK_FOCUS(__func__);
+
+  wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
+      .data = global_src_slice,
+  });
+
+  const char* status =
+      read_file(&src, "test/data/artificial/gif-pixel-data-too-much.gif");
+  if (status) {
+    return status;
+  }
+
+  return do_test_wuffs_gif_decode_expecting(
+      src, wuffs_gif__quirk_ignore_too_much_pixel_data, NULL, false);
 }
 
 const char* test_wuffs_gif_frame_dirty_rect() {
@@ -1091,8 +1622,7 @@
   }
 
   wuffs_base__image_config ic = ((wuffs_base__image_config){});
-  status = wuffs_gif__decoder__decode_image_config(
-      &dec, &ic, wuffs_base__io_buffer__reader(&src));
+  status = wuffs_gif__decoder__decode_image_config(&dec, &ic, &src);
   if (status) {
     RETURN_FAIL("decode_image_config: \"%s\"", status);
   }
@@ -1113,10 +1643,11 @@
   int i = 0;
 
   while (true) {
-    wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
-    set_reader_limit(&src_reader, 1);
-    status = wuffs_gif__decoder__decode_frame(&dec, &pb, src_reader,
+    wuffs_base__io_buffer limited_src = make_limited_reader(src, 1);
+
+    status = wuffs_gif__decoder__decode_frame(&dec, &pb, &limited_src,
                                               global_work_slice, NULL);
+    src.meta.ri += limited_src.meta.ri;
 
     wuffs_base__rect_ie_u32 r = wuffs_gif__decoder__frame_dirty_rect(&dec);
     if ((i < WUFFS_TESTLIB_ARRAY_SIZE(wants)) && (wants[i] == r.max_excl_y)) {
@@ -1154,12 +1685,11 @@
   if (status) {
     RETURN_FAIL("initialize: \"%s\"", status);
   }
-  wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
 
   wuffs_base__pixel_buffer pb = ((wuffs_base__pixel_buffer){});
   if (!frame_config) {
     wuffs_base__image_config ic = ((wuffs_base__image_config){});
-    status = wuffs_gif__decoder__decode_image_config(&dec, &ic, src_reader);
+    status = wuffs_gif__decoder__decode_image_config(&dec, &ic, &src);
     if (status) {
       RETURN_FAIL("decode_image_config: \"%s\"", status);
     }
@@ -1189,9 +1719,9 @@
 
     const char* status = NULL;
     if (frame_config) {
-      status = wuffs_gif__decoder__decode_frame_config(&dec, NULL, src_reader);
+      status = wuffs_gif__decoder__decode_frame_config(&dec, NULL, &src);
     } else {
-      status = wuffs_gif__decoder__decode_frame(&dec, &pb, src_reader,
+      status = wuffs_gif__decoder__decode_frame(&dec, &pb, &src,
                                                 global_work_slice, NULL);
     }
 
@@ -1237,8 +1767,6 @@
     RETURN_FAIL("initialize: \"%s\"", status);
   }
 
-  wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
-
   if (chunked) {
     if (src.meta.wi < 50) {
       RETURN_FAIL("src is too short");
@@ -1248,7 +1776,7 @@
     src.meta.wi = 30;
     src.meta.closed = false;
 
-    status = wuffs_gif__decoder__decode_image_config(&dec, NULL, src_reader);
+    status = wuffs_gif__decoder__decode_image_config(&dec, NULL, &src);
     if (status != wuffs_base__suspension__short_read) {
       RETURN_FAIL("decode_image_config (chunked): \"%s\"", status);
     }
@@ -1265,7 +1793,7 @@
     }
   }
 
-  status = wuffs_gif__decoder__decode_image_config(&dec, NULL, src_reader);
+  status = wuffs_gif__decoder__decode_image_config(&dec, NULL, &src);
   if (status) {
     RETURN_FAIL("decode_image_config: \"%s\"", status);
   }
@@ -1276,7 +1804,7 @@
   int i;
   for (i = 0; i < 4; i++) {
     fcs[i] = ((wuffs_base__frame_config){});
-    status = wuffs_gif__decoder__decode_frame_config(&dec, &fcs[i], src_reader);
+    status = wuffs_gif__decoder__decode_frame_config(&dec, &fcs[i], &src);
     if (status) {
       RETURN_FAIL("decode_frame_config #%d: \"%s\"", i, status);
     }
@@ -1321,7 +1849,7 @@
     }
   }
 
-  status = wuffs_gif__decoder__decode_frame_config(&dec, NULL, src_reader);
+  status = wuffs_gif__decoder__decode_frame_config(&dec, NULL, &src);
   if (status != wuffs_base__warning__end_of_data) {
     RETURN_FAIL("decode_frame_config EOD: \"%s\"", status);
   }
@@ -1345,7 +1873,7 @@
     int j;
     for (j = i; j < 4; j++) {
       wuffs_base__frame_config fc = ((wuffs_base__frame_config){});
-      status = wuffs_gif__decoder__decode_frame_config(&dec, &fc, src_reader);
+      status = wuffs_gif__decoder__decode_frame_config(&dec, &fc, &src);
       if (status) {
         RETURN_FAIL("decode_frame_config #%d, #%d: \"%s\"", i, j, status);
       }
@@ -1365,7 +1893,7 @@
       }
     }
 
-    status = wuffs_gif__decoder__decode_frame_config(&dec, NULL, src_reader);
+    status = wuffs_gif__decoder__decode_frame_config(&dec, NULL, &src);
     if (status != wuffs_base__warning__end_of_data) {
       RETURN_FAIL("decode_frame_config EOD #%d: \"%s\"", i, status);
     }
@@ -1704,18 +2232,29 @@
     test_wuffs_gif_decode_animated_big,                      //
     test_wuffs_gif_decode_animated_medium,                   //
     test_wuffs_gif_decode_animated_small,                    //
-    test_wuffs_gif_decode_bgra_nonpremul,                    //
+    test_wuffs_gif_decode_background_color,                  //
+    test_wuffs_gif_decode_delay_num_frames_decoded,          //
+    test_wuffs_gif_decode_empty_palette,                     //
+    test_wuffs_gif_decode_first_frame_is_opaque,             //
     test_wuffs_gif_decode_frame_out_of_bounds,               //
     test_wuffs_gif_decode_input_is_a_gif_just_one_read,      //
     test_wuffs_gif_decode_input_is_a_gif_many_big_reads,     //
     test_wuffs_gif_decode_input_is_a_gif_many_medium_reads,  //
     test_wuffs_gif_decode_input_is_a_gif_many_small_reads,   //
     test_wuffs_gif_decode_input_is_a_png,                    //
+    test_wuffs_gif_decode_metadata,                          //
     test_wuffs_gif_decode_missing_two_src_bytes,             //
+    test_wuffs_gif_decode_multiple_graphic_controls,         //
     test_wuffs_gif_decode_multiple_loop_counts,              //
     test_wuffs_gif_decode_pixel_data_none,                   //
     test_wuffs_gif_decode_pixel_data_not_enough,             //
-    test_wuffs_gif_decode_pixel_data_too_much,               //
+    test_wuffs_gif_decode_pixel_data_too_much_sans_quirk,    //
+    test_wuffs_gif_decode_pixel_data_too_much_with_quirk,    //
+    test_wuffs_gif_decode_pixfmt_bgr,                        //
+    test_wuffs_gif_decode_pixfmt_bgra_nonpremul,             //
+    test_wuffs_gif_decode_pixfmt_rgb,                        //
+    test_wuffs_gif_decode_pixfmt_rgba_nonpremul,             //
+    test_wuffs_gif_decode_zero_width_frame,                  //
     test_wuffs_gif_frame_dirty_rect,                         //
     test_wuffs_gif_num_decoded_frame_configs,                //
     test_wuffs_gif_num_decoded_frames,                       //
diff --git a/test/c/std/gzip.c b/test/c/std/gzip.c
index 830d605..89e14c8 100644
--- a/test/c/std/gzip.c
+++ b/test/c/std/gzip.c
@@ -96,20 +96,19 @@
   }
 
   while (true) {
-    wuffs_base__io_writer dst_writer = wuffs_base__io_buffer__writer(dst);
-    if (wlimit) {
-      set_writer_limit(&dst_writer, wlimit);
-    }
-    wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(src);
-    if (rlimit) {
-      set_reader_limit(&src_reader, rlimit);
-    }
+    wuffs_base__io_buffer limited_dst = make_limited_writer(*dst, wlimit);
+    wuffs_base__io_buffer limited_src = make_limited_reader(*src, rlimit);
 
-    status = wuffs_gzip__decoder__decode_io_writer(&dec, dst_writer, src_reader,
-                                                   global_work_slice);
+    status = wuffs_gzip__decoder__decode_io_writer(
+        &dec, &limited_dst, &limited_src, global_work_slice);
 
-    if ((wlimit && (status == wuffs_base__suspension__short_write)) ||
-        (rlimit && (status == wuffs_base__suspension__short_read))) {
+    dst->meta.wi += limited_dst.meta.wi;
+    src->meta.ri += limited_src.meta.ri;
+
+    if (((wlimit < UINT64_MAX) &&
+         (status == wuffs_base__suspension__short_write)) ||
+        ((rlimit < UINT64_MAX) &&
+         (status == wuffs_base__suspension__short_read))) {
       continue;
     }
     return status;
@@ -138,7 +137,7 @@
     src.data.ptr[src.meta.wi - 1 - (bad_checksum & 7)] ^= 1;
   }
 
-  int end_limit;
+  int end_limit;  // The rlimit, relative to the end of the data.
   for (end_limit = 0; end_limit < 10; end_limit++) {
     wuffs_gzip__decoder dec;
     status = wuffs_gzip__decoder__initialize(
@@ -149,14 +148,13 @@
     }
     wuffs_gzip__decoder__set_ignore_checksum(&dec, ignore_checksum);
     got.meta.wi = 0;
-    wuffs_base__io_writer got_writer = wuffs_base__io_buffer__writer(&got);
     src.meta.ri = 0;
 
     // Decode the src data in 1 or 2 chunks, depending on whether end_limit is
     // or isn't zero.
     int i;
     for (i = 0; i < 2; i++) {
-      wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
+      uint64_t rlimit = UINT64_MAX;
       const char* want_z = NULL;
       if (i == 0) {
         if (end_limit == 0) {
@@ -165,7 +163,7 @@
         if (src.meta.wi < end_limit) {
           RETURN_FAIL("end_limit=%d: not enough source data", end_limit);
         }
-        set_reader_limit(&src_reader, src.meta.wi - (uint64_t)(end_limit));
+        rlimit = src.meta.wi - (uint64_t)(end_limit);
         want_z = wuffs_base__suspension__short_read;
       } else {
         want_z = (bad_checksum && !ignore_checksum)
@@ -173,8 +171,10 @@
                      : NULL;
       }
 
+      wuffs_base__io_buffer limited_src = make_limited_reader(src, rlimit);
       const char* got_z = wuffs_gzip__decoder__decode_io_writer(
-          &dec, got_writer, src_reader, global_work_slice);
+          &dec, &got, &limited_src, global_work_slice);
+      src.meta.ri += limited_src.meta.ri;
       if (got_z != want_z) {
         RETURN_FAIL("end_limit=%d: got \"%s\", want \"%s\"", end_limit, got_z,
                     want_z);
@@ -206,12 +206,14 @@
 
 const char* test_wuffs_gzip_decode_midsummer() {
   CHECK_FOCUS(__func__);
-  return do_test_io_buffers(wuffs_gzip_decode, &gzip_midsummer_gt, 0, 0);
+  return do_test_io_buffers(wuffs_gzip_decode, &gzip_midsummer_gt, UINT64_MAX,
+                            UINT64_MAX);
 }
 
 const char* test_wuffs_gzip_decode_pi() {
   CHECK_FOCUS(__func__);
-  return do_test_io_buffers(wuffs_gzip_decode, &gzip_pi_gt, 0, 0);
+  return do_test_io_buffers(wuffs_gzip_decode, &gzip_pi_gt, UINT64_MAX,
+                            UINT64_MAX);
 }
 
   // ---------------- Mimic Tests
@@ -220,12 +222,14 @@
 
 const char* test_mimic_gzip_decode_midsummer() {
   CHECK_FOCUS(__func__);
-  return do_test_io_buffers(mimic_gzip_decode, &gzip_midsummer_gt, 0, 0);
+  return do_test_io_buffers(mimic_gzip_decode, &gzip_midsummer_gt, UINT64_MAX,
+                            UINT64_MAX);
 }
 
 const char* test_mimic_gzip_decode_pi() {
   CHECK_FOCUS(__func__);
-  return do_test_io_buffers(mimic_gzip_decode, &gzip_pi_gt, 0, 0);
+  return do_test_io_buffers(mimic_gzip_decode, &gzip_pi_gt, UINT64_MAX,
+                            UINT64_MAX);
 }
 
 #endif  // WUFFS_MIMIC
@@ -236,14 +240,14 @@
   CHECK_FOCUS(__func__);
   return do_bench_io_buffers(
       wuffs_gzip_decode, WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED,
-      tc_dst, &gzip_midsummer_gt, 0, 0, 300);
+      tc_dst, &gzip_midsummer_gt, UINT64_MAX, UINT64_MAX, 300);
 }
 
 const char* bench_wuffs_gzip_decode_100k() {
   CHECK_FOCUS(__func__);
   return do_bench_io_buffers(
       wuffs_gzip_decode, WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED,
-      tc_dst, &gzip_pi_gt, 0, 0, 30);
+      tc_dst, &gzip_pi_gt, UINT64_MAX, UINT64_MAX, 30);
 }
 
   // ---------------- Mimic Benches
@@ -253,13 +257,13 @@
 const char* bench_mimic_gzip_decode_10k() {
   CHECK_FOCUS(__func__);
   return do_bench_io_buffers(mimic_gzip_decode, 0, tc_dst, &gzip_midsummer_gt,
-                             0, 0, 300);
+                             UINT64_MAX, UINT64_MAX, 300);
 }
 
 const char* bench_mimic_gzip_decode_100k() {
   CHECK_FOCUS(__func__);
-  return do_bench_io_buffers(mimic_gzip_decode, 0, tc_dst, &gzip_pi_gt, 0, 0,
-                             30);
+  return do_bench_io_buffers(mimic_gzip_decode, 0, tc_dst, &gzip_pi_gt,
+                             UINT64_MAX, UINT64_MAX, 30);
 }
 
 #endif  // WUFFS_MIMIC
diff --git a/test/c/std/lzw.c b/test/c/std/lzw.c
index 6b800a7..f156e8e 100644
--- a/test/c/std/lzw.c
+++ b/test/c/std/lzw.c
@@ -118,19 +118,15 @@
   int num_iters = 0;
   while (true) {
     num_iters++;
-    wuffs_base__io_writer got_writer = wuffs_base__io_buffer__writer(&got);
-    if (wlimit) {
-      set_writer_limit(&got_writer, wlimit);
-    }
-    wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
-    if (rlimit) {
-      set_reader_limit(&src_reader, rlimit);
-    }
+    wuffs_base__io_buffer limited_got = make_limited_writer(got, wlimit);
+    wuffs_base__io_buffer limited_src = make_limited_reader(src, rlimit);
     size_t old_wi = got.meta.wi;
     size_t old_ri = src.meta.ri;
 
-    status = wuffs_lzw__decoder__decode_io_writer(&dec, got_writer, src_reader,
-                                                  global_work_slice);
+    status = wuffs_lzw__decoder__decode_io_writer(
+        &dec, &limited_got, &limited_src, global_work_slice);
+    got.meta.wi += limited_got.meta.wi;
+    src.meta.ri += limited_src.meta.ri;
     if (!status) {
       if (src.meta.ri != src.meta.wi) {
         RETURN_FAIL("decode returned \"ok\" but src was not exhausted");
@@ -155,7 +151,7 @@
     }
   }
 
-  if (wlimit || rlimit) {
+  if ((wlimit < UINT64_MAX) || (rlimit < UINT64_MAX)) {
     if (num_iters <= 1) {
       RETURN_FAIL("num_iters: got %d, want > 1", num_iters);
     }
@@ -172,21 +168,21 @@
   CHECK_FOCUS(__func__);
   return do_test_wuffs_lzw_decode("test/data/bricks-dither.indexes.giflzw",
                                   14923, "test/data/bricks-dither.indexes",
-                                  19200, 0, 0);
+                                  19200, UINT64_MAX, UINT64_MAX);
 }
 
 const char* test_wuffs_lzw_decode_bricks_nodither() {
   CHECK_FOCUS(__func__);
   return do_test_wuffs_lzw_decode("test/data/bricks-nodither.indexes.giflzw",
                                   13382, "test/data/bricks-nodither.indexes",
-                                  19200, 0, 0);
+                                  19200, UINT64_MAX, UINT64_MAX);
 }
 
 const char* test_wuffs_lzw_decode_many_big_reads() {
   CHECK_FOCUS(__func__);
   return do_test_wuffs_lzw_decode("test/data/bricks-gray.indexes.giflzw", 14731,
-                                  "test/data/bricks-gray.indexes", 19200, 0,
-                                  4096);
+                                  "test/data/bricks-gray.indexes", 19200,
+                                  UINT64_MAX, 4096);
 }
 
 const char* test_wuffs_lzw_decode_many_small_writes_reads() {
@@ -199,7 +195,8 @@
 const char* test_wuffs_lzw_decode_pi() {
   CHECK_FOCUS(__func__);
   return do_test_wuffs_lzw_decode("test/data/pi.txt.giflzw", 50550,
-                                  "test/data/pi.txt", 100003, 0, 0);
+                                  "test/data/pi.txt", 100003, UINT64_MAX,
+                                  UINT64_MAX);
 }
 
 const char* test_wuffs_lzw_decode_output_bad() {
@@ -234,9 +231,8 @@
   }
   wuffs_lzw__decoder__set_literal_width(&dec, 7);
 
-  status = wuffs_lzw__decoder__decode_io_writer(
-      &dec, wuffs_base__io_buffer__writer(&got),
-      wuffs_base__io_buffer__reader(&src), global_work_slice);
+  status =
+      wuffs_lzw__decoder__decode_io_writer(&dec, &got, &src, global_work_slice);
   if (status != wuffs_lzw__error__bad_code) {
     RETURN_FAIL("decode: \"%s\"", status);
   }
@@ -279,9 +275,8 @@
   }
   wuffs_lzw__decoder__set_literal_width(&dec, 8);
 
-  status = wuffs_lzw__decoder__decode_io_writer(
-      &dec, wuffs_base__io_buffer__writer(&got),
-      wuffs_base__io_buffer__reader(&src), global_work_slice);
+  status =
+      wuffs_lzw__decoder__decode_io_writer(&dec, &got, &src, global_work_slice);
   if (status) {
     RETURN_FAIL("decode: \"%s\"", status);
   }
@@ -295,6 +290,93 @@
   return NULL;
 }
 
+const char* do_test_wuffs_lzw_decode_width(uint32_t width,
+                                           wuffs_base__io_buffer src,
+                                           wuffs_base__io_buffer want) {
+  wuffs_lzw__decoder dec;
+  const char* status = wuffs_lzw__decoder__initialize(
+      &dec, sizeof dec, WUFFS_VERSION,
+      WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED);
+  if (status) {
+    RETURN_FAIL("initialize: \"%s\"", status);
+  }
+  wuffs_lzw__decoder__set_literal_width(&dec, width);
+
+  wuffs_base__io_buffer got = ((wuffs_base__io_buffer){
+      .data = global_got_slice,
+  });
+  status =
+      wuffs_lzw__decoder__decode_io_writer(&dec, &got, &src, global_work_slice);
+  if (status) {
+    RETURN_FAIL("decode: \"%s\"", status);
+  }
+
+  return check_io_buffers_equal("", &got, &want);
+}
+
+// A zero literal width isn't very practical: the output bytes can only be 0x00
+// and it isn't possible to encode the empty string, as the End code requires
+// two bits but the first non-Clear code after a Clear code has only one bit,
+// so it must be the literal 0x00. Nonetheless, the giflib C library accepts a
+// zero literal width (it only rejects literal widths above 8), so we do too.
+const char* test_wuffs_lzw_decode_width_0() {
+  CHECK_FOCUS(__func__);
+
+  // 0b...._...._...._...1  0x001 Clear code.
+  // 0b...._...._...._..0.  0x000 Literal "0".
+  // 0b...._...._...._11..  0x011 Back-ref "00"
+  // 0b...._...._.100_....  0x100 Back-ref "000".
+  // 0b...._..00_0..._....  0x000 Literal "0".
+  // 0b...0_10.._...._....  0x010 End code.
+  wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
+      .data = global_src_slice,
+  });
+  src.meta.wi = 2;
+  src.data.ptr[0] = 0x4D;
+  src.data.ptr[1] = 0x08;
+
+  wuffs_base__io_buffer want = ((wuffs_base__io_buffer){
+      .data = global_want_slice,
+  });
+  want.meta.wi = 7;
+  want.data.ptr[0] = 0x00;
+  want.data.ptr[1] = 0x00;
+  want.data.ptr[2] = 0x00;
+  want.data.ptr[3] = 0x00;
+  want.data.ptr[4] = 0x00;
+  want.data.ptr[5] = 0x00;
+  want.data.ptr[6] = 0x00;
+
+  return do_test_wuffs_lzw_decode_width(0, src, want);
+}
+
+const char* test_wuffs_lzw_decode_width_1() {
+  CHECK_FOCUS(__func__);
+
+  // 0b...._...._...._..10  0x010 Clear code.
+  // 0b...._...._...._00..  0x000 Literal "0".
+  // 0b...._...._.001_....  0x001 Literal "1".
+  // 0b...._..10_0..._....  0x100 Back-ref "01".
+  // 0b...0_11.._...._....  0x011 End code.
+  wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
+      .data = global_src_slice,
+  });
+  src.meta.wi = 2;
+  src.data.ptr[0] = 0x12;
+  src.data.ptr[1] = 0x0E;
+
+  wuffs_base__io_buffer want = ((wuffs_base__io_buffer){
+      .data = global_want_slice,
+  });
+  want.meta.wi = 4;
+  want.data.ptr[0] = 0x00;
+  want.data.ptr[1] = 0x01;
+  want.data.ptr[2] = 0x00;
+  want.data.ptr[3] = 0x01;
+
+  return do_test_wuffs_lzw_decode_width(1, src, want);
+}
+
 // ---------------- LZW Benches
 
 const char* do_bench_wuffs_lzw_decode(const char* filename,
@@ -305,8 +387,6 @@
   wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
       .data = global_src_slice,
   });
-  wuffs_base__io_writer got_writer = wuffs_base__io_buffer__writer(&got);
-  wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
 
   const char* status = read_file(&src, filename);
   if (status) {
@@ -335,7 +415,7 @@
     if (status) {
       RETURN_FAIL("initialize: \"%s\"", status);
     }
-    status = wuffs_lzw__decoder__decode_io_writer(&dec, got_writer, src_reader,
+    status = wuffs_lzw__decoder__decode_io_writer(&dec, &got, &src,
                                                   global_work_slice);
     if (status) {
       RETURN_FAIL("decode: \"%s\"", status);
@@ -368,6 +448,8 @@
     test_wuffs_lzw_decode_output_bad,               //
     test_wuffs_lzw_decode_output_empty,             //
     test_wuffs_lzw_decode_pi,                       //
+    test_wuffs_lzw_decode_width_0,                  //
+    test_wuffs_lzw_decode_width_1,                  //
 
     NULL,
 };
diff --git a/test/c/std/zlib.c b/test/c/std/zlib.c
index 726d4a4..93fa7b2 100644
--- a/test/c/std/zlib.c
+++ b/test/c/std/zlib.c
@@ -96,20 +96,19 @@
   }
 
   while (true) {
-    wuffs_base__io_writer dst_writer = wuffs_base__io_buffer__writer(dst);
-    if (wlimit) {
-      set_writer_limit(&dst_writer, wlimit);
-    }
-    wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(src);
-    if (rlimit) {
-      set_reader_limit(&src_reader, rlimit);
-    }
+    wuffs_base__io_buffer limited_dst = make_limited_writer(*dst, wlimit);
+    wuffs_base__io_buffer limited_src = make_limited_reader(*src, rlimit);
 
-    status = wuffs_zlib__decoder__decode_io_writer(&dec, dst_writer, src_reader,
-                                                   global_work_slice);
+    status = wuffs_zlib__decoder__decode_io_writer(
+        &dec, &limited_dst, &limited_src, global_work_slice);
 
-    if ((wlimit && (status == wuffs_base__suspension__short_write)) ||
-        (rlimit && (status == wuffs_base__suspension__short_read))) {
+    dst->meta.wi += limited_dst.meta.wi;
+    src->meta.ri += limited_src.meta.ri;
+
+    if (((wlimit < UINT64_MAX) &&
+         (status == wuffs_base__suspension__short_write)) ||
+        ((rlimit < UINT64_MAX) &&
+         (status == wuffs_base__suspension__short_read))) {
       continue;
     }
     return status;
@@ -137,7 +136,7 @@
     src.data.ptr[src.meta.wi - 1 - (bad_checksum & 3)] ^= 1;
   }
 
-  int end_limit;
+  int end_limit;  // The rlimit, relative to the end of the data.
   for (end_limit = 0; end_limit < 10; end_limit++) {
     wuffs_zlib__decoder dec;
     status = wuffs_zlib__decoder__initialize(
@@ -148,14 +147,13 @@
     }
     wuffs_zlib__decoder__set_ignore_checksum(&dec, ignore_checksum);
     got.meta.wi = 0;
-    wuffs_base__io_writer got_writer = wuffs_base__io_buffer__writer(&got);
     src.meta.ri = 0;
 
     // Decode the src data in 1 or 2 chunks, depending on whether end_limit is
     // or isn't zero.
     int i;
     for (i = 0; i < 2; i++) {
-      wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
+      uint64_t rlimit = UINT64_MAX;
       const char* want_z = NULL;
       if (i == 0) {
         if (end_limit == 0) {
@@ -164,7 +162,7 @@
         if (src.meta.wi < end_limit) {
           RETURN_FAIL("end_limit=%d: not enough source data", end_limit);
         }
-        set_reader_limit(&src_reader, src.meta.wi - (uint64_t)(end_limit));
+        rlimit = src.meta.wi - (uint64_t)(end_limit);
         want_z = wuffs_base__suspension__short_read;
       } else {
         want_z = (bad_checksum && !ignore_checksum)
@@ -172,8 +170,11 @@
                      : NULL;
       }
 
+      wuffs_base__io_buffer limited_src = make_limited_reader(src, rlimit);
+
       const char* got_z = wuffs_zlib__decoder__decode_io_writer(
-          &dec, got_writer, src_reader, global_work_slice);
+          &dec, &got, &limited_src, global_work_slice);
+      src.meta.ri += limited_src.meta.ri;
       if (got_z != want_z) {
         RETURN_FAIL("end_limit=%d: got \"%s\", want \"%s\"", end_limit, got_z,
                     want_z);
@@ -205,12 +206,14 @@
 
 const char* test_wuffs_zlib_decode_midsummer() {
   CHECK_FOCUS(__func__);
-  return do_test_io_buffers(wuffs_zlib_decode, &zlib_midsummer_gt, 0, 0);
+  return do_test_io_buffers(wuffs_zlib_decode, &zlib_midsummer_gt, UINT64_MAX,
+                            UINT64_MAX);
 }
 
 const char* test_wuffs_zlib_decode_pi() {
   CHECK_FOCUS(__func__);
-  return do_test_io_buffers(wuffs_zlib_decode, &zlib_pi_gt, 0, 0);
+  return do_test_io_buffers(wuffs_zlib_decode, &zlib_pi_gt, UINT64_MAX,
+                            UINT64_MAX);
 }
 
   // ---------------- Mimic Tests
@@ -219,12 +222,14 @@
 
 const char* test_mimic_zlib_decode_midsummer() {
   CHECK_FOCUS(__func__);
-  return do_test_io_buffers(mimic_zlib_decode, &zlib_midsummer_gt, 0, 0);
+  return do_test_io_buffers(mimic_zlib_decode, &zlib_midsummer_gt, UINT64_MAX,
+                            UINT64_MAX);
 }
 
 const char* test_mimic_zlib_decode_pi() {
   CHECK_FOCUS(__func__);
-  return do_test_io_buffers(mimic_zlib_decode, &zlib_pi_gt, 0, 0);
+  return do_test_io_buffers(mimic_zlib_decode, &zlib_pi_gt, UINT64_MAX,
+                            UINT64_MAX);
 }
 
 #endif  // WUFFS_MIMIC
@@ -235,14 +240,14 @@
   CHECK_FOCUS(__func__);
   return do_bench_io_buffers(
       wuffs_zlib_decode, WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED,
-      tc_dst, &zlib_midsummer_gt, 0, 0, 300);
+      tc_dst, &zlib_midsummer_gt, UINT64_MAX, UINT64_MAX, 300);
 }
 
 const char* bench_wuffs_zlib_decode_100k() {
   CHECK_FOCUS(__func__);
   return do_bench_io_buffers(
       wuffs_zlib_decode, WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED,
-      tc_dst, &zlib_pi_gt, 0, 0, 30);
+      tc_dst, &zlib_pi_gt, UINT64_MAX, UINT64_MAX, 30);
 }
 
   // ---------------- Mimic Benches
@@ -252,13 +257,13 @@
 const char* bench_mimic_zlib_decode_10k() {
   CHECK_FOCUS(__func__);
   return do_bench_io_buffers(mimic_zlib_decode, 0, tc_dst, &zlib_midsummer_gt,
-                             0, 0, 300);
+                             UINT64_MAX, UINT64_MAX, 300);
 }
 
 const char* bench_mimic_zlib_decode_100k() {
   CHECK_FOCUS(__func__);
-  return do_bench_io_buffers(mimic_zlib_decode, 0, tc_dst, &zlib_pi_gt, 0, 0,
-                             30);
+  return do_bench_io_buffers(mimic_zlib_decode, 0, tc_dst, &zlib_pi_gt,
+                             UINT64_MAX, UINT64_MAX, 30);
 }
 
 #endif  // WUFFS_MIMIC
diff --git a/test/c/testlib/testlib.c b/test/c/testlib/testlib.c
index a6b17d3..30a8389 100644
--- a/test/c/testlib/testlib.c
+++ b/test/c/testlib/testlib.c
@@ -366,32 +366,40 @@
   return ret;
 }
 
-void set_reader_limit(wuffs_base__io_reader* o, uint64_t limit) {
-  if (o && o->private_impl.buf) {
-    uint8_t* p = o->private_impl.buf->data.ptr + o->private_impl.buf->meta.ri;
-    uint8_t* q = o->private_impl.buf->data.ptr + o->private_impl.buf->meta.wi;
-    if (!o->private_impl.mark) {
-      o->private_impl.mark = p;
-      o->private_impl.limit = q;
-    }
-    if ((o->private_impl.limit - p) > limit) {
-      o->private_impl.limit = p + limit;
-    }
+wuffs_base__io_buffer make_limited_reader(wuffs_base__io_buffer b,
+                                          uint64_t limit) {
+  uint64_t n = b.meta.wi - b.meta.ri;
+  bool closed = b.meta.closed;
+  if (n > limit) {
+    n = limit;
+    closed = false;
   }
+
+  wuffs_base__io_buffer ret;
+  ret.data.ptr = b.data.ptr + b.meta.ri;
+  ret.data.len = n;
+  ret.meta.wi = n;
+  ret.meta.ri = 0;
+  ret.meta.pos = wuffs_base__u64__sat_add(b.meta.pos, b.meta.ri);
+  ret.meta.closed = closed;
+  return ret;
 }
 
-void set_writer_limit(wuffs_base__io_writer* o, uint64_t limit) {
-  if (o && o->private_impl.buf) {
-    uint8_t* p = o->private_impl.buf->data.ptr + o->private_impl.buf->meta.wi;
-    uint8_t* q = o->private_impl.buf->data.ptr + o->private_impl.buf->data.len;
-    if (!o->private_impl.mark) {
-      o->private_impl.mark = p;
-      o->private_impl.limit = q;
-    }
-    if ((o->private_impl.limit - p) > limit) {
-      o->private_impl.limit = p + limit;
-    }
+wuffs_base__io_buffer make_limited_writer(wuffs_base__io_buffer b,
+                                          uint64_t limit) {
+  uint64_t n = b.data.len - b.meta.wi;
+  if (n > limit) {
+    n = limit;
   }
+
+  wuffs_base__io_buffer ret;
+  ret.data.ptr = b.data.ptr + b.meta.wi;
+  ret.data.len = n;
+  ret.meta.wi = 0;
+  ret.meta.ri = 0;
+  ret.meta.pos = wuffs_base__u64__sat_add(b.meta.pos, b.meta.wi);
+  ret.meta.closed = b.meta.closed;
+  return ret;
 }
 
 // TODO: we shouldn't need to pass the rect. Instead, pass a subset pixbuf.
diff --git a/test/data/artificial/deflate-degenerate-huffman-unused.deflate b/test/data/artificial/deflate-degenerate-huffman-unused.deflate
new file mode 100644
index 0000000..4751683
--- /dev/null
+++ b/test/data/artificial/deflate-degenerate-huffman-unused.deflate
Binary files differ
diff --git a/test/data/artificial/deflate-degenerate-huffman-unused.deflate.commentary.txt b/test/data/artificial/deflate-degenerate-huffman-unused.deflate.commentary.txt
new file mode 100644
index 0000000..52698b3
--- /dev/null
+++ b/test/data/artificial/deflate-degenerate-huffman-unused.deflate.commentary.txt
@@ -0,0 +1,66 @@
+Running deflate-degenerate-huffman-unused.deflate through script/print-bits.go
+and adding commentary:
+
+    offset  xoffset ASCII   hex     binary
+    000000  0x0000  .       0x05    0b_...._.101  Dynamic Huffman block, final
+    000000  0x0000  .       0x04    0b_0000_0...  NumLCodes: 257
+    000001  0x0001  .       0xC0    0b_...0_0000  NumDCodes: 1
+    000001  0x0001  .       0xC0    0b_110._....  NumCLCodeLengths: 18
+    000002  0x0002  !       0x21    0b_...._...1
+
+Decode the H-CL Huffman table (NumCLCodeLengths = 18). Recall the peculiar
+code_order: 16, 17, 18, 0, 8, ..., 2, 14, 1, 15:
+
+    000002  0x0002  !       0x21    0b_0010_000.  CLCodeLengths: 18 x 3 bits
+    000003  0x0003  .       0x01    0b_0000_0001    CLCLs[ 1] is 2
+    000004  0x0004  .       0x00    0b_0000_0000    CLCLs[ 2] is 2
+    000005  0x0005  .       0x00    0b_0000_0000    CLCLs[17] is 2
+    000006  0x0006  .       0x00    0b_0000_0000    CLCLs[18] is 2
+    000007  0x0007  .       0x80    0b_1000_0000
+    000008  0x0008  .       0xA0    0b_.010_0000
+
+The H-CL Huffman table is:
+"00" -> CodeLength=1
+"01" -> CodeLength=2
+"10" -> CodeLength=17 which means a block of ( 3 + 3_extra_bits) zeroes
+"11" -> CodeLength=18 which means a block of (11 + 7_extra_bits) zeroes
+
+Decode the H-L Huffman table (NumLCodes = 257):
+
+    000008  0x0008  .       0xA0    0b_1..._....  "11" is CL=18: 11+7extra
+    000009  0x0009  .       0xB7    0b_...._...1
+    000009  0x0009  .       0xB7    0b_1011_011.  7extra=91: 102 zeroes
+    000010  0x000A  V       0x56    0b_...._..10  "01" is CL= 2 (102='f')
+    000010  0x000A  V       0x56    0b_...._01..  "10" is CL=17:  3+3extra
+    000010  0x000A  V       0x56    0b_.101_....  3extra=5: 8 zeroes
+    000010  0x000A  V       0x56    0b_0..._....  "00" is CL= 1 (111='o')
+    000011  0x000B  .       0xFE    0b_...._...0
+    000011  0x000B  .       0xFE    0b_...._.11.  "11" is CL=18: 11+7extra
+    000011  0x000B  .       0xFE    0b_1111_1...  7extra=127: 138 zeroes
+    000012  0x000C  7       0x37    0b_...._..11
+    000012  0x000C  7       0x37    0b_...._01..  "10" is CL=17:  3+3extra
+    000012  0x000C  7       0x37    0b_.011_....  3extra=3: 6 zeroes
+    000012  0x000C  7       0x37    0b_0..._....  "01" is CL=2 (256=EOB)
+    000013  0x000D  .       0x89    0b_...._...1
+
+The H-L Huffman table is:
+"0"  -> 'o'
+"10" -> 'f'
+"11" -> EOB
+
+Decode the H-D Huffman table (NumDCodes = 1):
+
+    000013  0x000D  .       0x89    0b_...._.00.  "00" is CL= 1 (DCode 0)
+
+The H-D Huffman table is:
+"0"  -> 0
+This table is incomplete: there is no entry starting with a "1" bit. It is
+therefore degenerate, but not used below.
+
+Apply H-L and H-D.
+
+    000013  0x000D  .       0x89    0b_...0_1...  lcode:  102  literal 'f'
+    000013  0x000D  .       0x89    0b_..0._....  lcode:  111  literal 'o'
+    000013  0x000D  .       0x89    0b_.0.._....  lcode:  111  literal 'o'
+    000013  0x000D  .       0x89    0b_1..._....  lcode:  256  end of block
+    000014  0x000E  .       0x01    0b_...._...1
diff --git a/test/data/artificial/deflate-degenerate-huffman-unused.deflate.make-artificial.txt b/test/data/artificial/deflate-degenerate-huffman-unused.deflate.make-artificial.txt
new file mode 100644
index 0000000..ba90dd8
--- /dev/null
+++ b/test/data/artificial/deflate-degenerate-huffman-unused.deflate.make-artificial.txt
@@ -0,0 +1,31 @@
+# Feed this file to script/make-artificial.go
+
+make deflate
+
+blockDynamicHuffman (final) {
+	numLCodes 257
+	numDCodes 1
+	numCLCodeLengths 18
+
+	huffman CodeLength {
+		1  00
+		2  01
+		17 10
+		18 11
+	}
+
+	huffman Length/Literal {
+		# 102='f', 111='o', 256=EOB
+		111 0
+		102 10
+		256 11
+	}
+
+	huffman Distance {
+		# Incomplete. There is no key/value pair whose value starts with "1".
+		0 0
+	}
+
+	literal "foo"
+	endOfBlock
+}
diff --git a/test/data/artificial/deflate-distance-32768.deflate.commentary.txt b/test/data/artificial/deflate-distance-32768.deflate.commentary.txt
index 84a30b2..fb5d94e 100644
--- a/test/data/artificial/deflate-distance-32768.deflate.commentary.txt
+++ b/test/data/artificial/deflate-distance-32768.deflate.commentary.txt
@@ -18,7 +18,7 @@
     000019  0x0013  .       0xE9    0b_...._...1
     000019  0x0013  .       0xE9    0b_..10_100.  dcode:    5  base:          7
     000019  0x0013  .       0xE9    0b_.1.._....  extra:    1  distance:      8
-	etc
+    etc
     000236  0x00EC  .       0xE9    0b_1..._....  lcode:  284  base:        227
     000237  0x00ED  .       0x91    0b_.001_0001
     000237  0x00ED  .       0x91    0b_1..._....  extra:   17  length:      244
diff --git a/test/data/artificial/gif-background-color.gif b/test/data/artificial/gif-background-color.gif
new file mode 100644
index 0000000..d25e59e
--- /dev/null
+++ b/test/data/artificial/gif-background-color.gif
Binary files differ
diff --git a/test/data/artificial/gif-background-color.gif.make-artificial.txt b/test/data/artificial/gif-background-color.gif.make-artificial.txt
new file mode 100644
index 0000000..10c4c38
--- /dev/null
+++ b/test/data/artificial/gif-background-color.gif.make-artificial.txt
@@ -0,0 +1,34 @@
+# Feed this file to script/make-artificial.go
+
+make gif
+
+header
+
+image {
+	imageWidthHeight 4 1
+	backgroundColorIndex 3
+	palette {
+		0x80 0x00 0x00
+		0x80 0x00 0xC1
+		0x80 0xC2 0x00
+		0x80 0xC3 0xC3
+	}
+}
+
+loopCount 1
+
+graphicControl animationDisposalRestoreBackground 100ms
+
+frame {
+	frameLeftTopWidthHeight 0 0 2 1
+}
+lzw 2 0x00 0x01
+
+graphicControl animationDisposalRestoreBackground 200ms
+
+frame {
+	frameLeftTopWidthHeight 2 0 1 1
+}
+lzw 2 0x02
+
+trailer
diff --git a/test/data/artificial/gif-empty-palette.gif b/test/data/artificial/gif-empty-palette.gif
new file mode 100644
index 0000000..b26bd84
--- /dev/null
+++ b/test/data/artificial/gif-empty-palette.gif
Binary files differ
diff --git a/test/data/artificial/gif-empty-palette.gif.make-artificial.txt b/test/data/artificial/gif-empty-palette.gif.make-artificial.txt
new file mode 100644
index 0000000..bc114a6
--- /dev/null
+++ b/test/data/artificial/gif-empty-palette.gif.make-artificial.txt
@@ -0,0 +1,33 @@
+# Feed this file to script/make-artificial.go
+
+# This GIF image has two frames. There is no global palette, and only the first
+# frame has a local palette.
+#
+# Wuffs implicitly gives the second frame an all-opaque-black palette by
+# default. Wuffs can be configured to reject it instead, by enabling a 'quirk'.
+
+make gif
+
+header
+
+image {
+	imageWidthHeight 1 1
+}
+
+frame {
+	frameLeftTopWidthHeight 0 0 1 1
+	palette {
+		0x00 0x00 0xFF
+		0x11 0x00 0xFF
+		0x22 0x00 0xFF
+		0x33 0x00 0xFF
+	}
+}
+lzw 2 0x00
+
+frame {
+	frameLeftTopWidthHeight 0 0 1 1
+}
+lzw 2 0x00
+
+trailer
diff --git a/test/data/artificial/gif-frame-out-of-bounds.gif.make-artificial.txt b/test/data/artificial/gif-frame-out-of-bounds.gif.make-artificial.txt
index 20ce402..da7111c 100644
--- a/test/data/artificial/gif-frame-out-of-bounds.gif.make-artificial.txt
+++ b/test/data/artificial/gif-frame-out-of-bounds.gif.make-artificial.txt
@@ -47,7 +47,8 @@
 #  - giflib enforces no particular policy. It provides image and frame bounds
 #    as encoded in the file and leaves it up to the caller how to respond.
 #
-# Wuffs chooses policy 4, the same as Chromium.
+# Wuffs chooses policy 4 by default, the same as Chromium. Wuffs can be
+# configured to choose policy 1, by enabling a 'quirk'.
 
 make gif
 
diff --git a/test/data/artificial/gif-metadata.gif b/test/data/artificial/gif-metadata.gif
new file mode 100644
index 0000000..8d01384
--- /dev/null
+++ b/test/data/artificial/gif-metadata.gif
Binary files differ
diff --git a/test/data/artificial/gif-metadata.gif.make-artificial.txt b/test/data/artificial/gif-metadata.gif.make-artificial.txt
new file mode 100644
index 0000000..197f857
--- /dev/null
+++ b/test/data/artificial/gif-metadata.gif.make-artificial.txt
@@ -0,0 +1,54 @@
+# Feed this file to script/make-artificial.go
+
+make gif
+
+header
+
+image {
+	imageWidthHeight 2 2
+	palette {
+		0x00 0x00 0xFF
+		0x11 0x00 0xFF
+		0x22 0x00 0xFF
+		0x33 0x00 0xFF
+	}
+}
+
+# ICCP metadata.
+#
+# Extension (Application Extension), 11 bytes for AI and AAC.
+bytes 0x21 0xFF 0x0B
+# Application Identifier "ICCRGBG1".
+bytes 0x49 0x43 0x43 0x52 0x47 0x42 0x47 0x31
+# Application Authentication Code "012".
+bytes 0x30 0x31 0x32
+# A block of arbitrary data.
+bytes 0x05 0x16 0x26 0x36 0x46 0x56
+# Another block of arbitrary data.
+bytes 0x03 0x76 0x86 0x96
+# Block Terminator.
+bytes 0x00
+
+# XMP metadata.
+#
+# Extension (Application Extension), 11 bytes for AI and AAC.
+bytes 0x21 0xFF 0x0B
+# Application Identifier "XMP Data".
+bytes 0x58 0x4D 0x50 0x20 0x44 0x61 0x74 0x61
+# Application Authentication Code "XMP".
+bytes 0x58 0x4D 0x50
+# A block of arbitrary data.
+bytes 0x05 0x17 0x27 0x37 0x47 0x57
+# Another block of arbitrary data.
+bytes 0x03 0x77 0x87 0x97
+# Block Terminator.
+bytes 0x00
+
+loopCount 2000
+
+frame {
+	frameLeftTopWidthHeight 1 0 1 1
+}
+lzw 4 0x01
+
+trailer
diff --git a/test/data/artificial/gif-multiple-graphic-controls.gif b/test/data/artificial/gif-multiple-graphic-controls.gif
new file mode 100644
index 0000000..ca49790
--- /dev/null
+++ b/test/data/artificial/gif-multiple-graphic-controls.gif
Binary files differ
diff --git a/test/data/artificial/gif-multiple-graphic-controls.gif.make-artificial.txt b/test/data/artificial/gif-multiple-graphic-controls.gif.make-artificial.txt
new file mode 100644
index 0000000..cd36eed
--- /dev/null
+++ b/test/data/artificial/gif-multiple-graphic-controls.gif.make-artificial.txt
@@ -0,0 +1,31 @@
+# Feed this file to script/make-artificial.go
+
+# This GIF image contains multiple "Graphic Control Extensions" for a frame.
+#
+# The GIF89a specification says that "at most one Graphic Control Extension may
+# precede a graphic rendering block", but in practice, some encoders emit more
+# than one, and decoders ignore all but the last one.
+
+make gif
+
+header
+
+image {
+	imageWidthHeight 1 1
+	palette {
+		0x00 0x00 0xFF
+		0x11 0x00 0xFF
+		0x22 0x00 0xFF
+		0x33 0x00 0xFF
+	}
+}
+
+graphicControl animationDisposalNone 200ms
+graphicControl animationDisposalNone 300ms
+
+frame {
+	frameLeftTopWidthHeight 0 0 1 1
+}
+lzw 2 0x00
+
+trailer
diff --git a/test/data/artificial/gif-multiple-loop-counts.gif.make-artificial.txt b/test/data/artificial/gif-multiple-loop-counts.gif.make-artificial.txt
index 4ed6cf2..75b9647 100644
--- a/test/data/artificial/gif-multiple-loop-counts.gif.make-artificial.txt
+++ b/test/data/artificial/gif-multiple-loop-counts.gif.make-artificial.txt
@@ -1,5 +1,30 @@
 # Feed this file to script/make-artificial.go
 
+# This GIF image contains multiple "loop count" entries.
+#
+# The GIF89a specification doesn't discuss loop counts: it is an extension to
+# the format. Neither the official spec nor unofficial documents (e.g.
+# http://www.vurdalakov.net/misc/gif/netscape-looping-application-extension)
+# state how to interpret having more than one of them: whether to accept the
+# first, accept the last, reject the overall animated image as invalid, etc.
+# Different spec-compliant decoders can choose different policies.
+#
+# Wuffs reports the most recent loop count seen, which may change over the
+# course of stepping through the frames. Programs that use Wuffs may enforce
+# their own policy (e.g. accept first, accept last, etc.) on top of that.
+#
+# Note also that the loop count as written in the GIF file format has "excludes"
+# semantics: it is the number of times to repeat the animation excluding the
+# initial play-through (and 0 means to loop forever). An "excludes" loop count
+# of 3 means to play each animation frame 4 times. An absent loop count means to
+# play each animation frame exactly once.
+#
+# Wuffs' API provides "includes" semantics, across all of its supported
+# animation file formats. An "includes" loop count of 3 means to play each
+# animation frame 3 times. There is no absent option. Wuffs' numbers and GIF's
+# numbers will generally differ by 1 (other than the absent case, and both
+# using 0 to mean forever).
+
 make gif
 
 header
diff --git a/test/data/artificial/gif-zero-width-frame.gif b/test/data/artificial/gif-zero-width-frame.gif
new file mode 100644
index 0000000..b88b4b4
--- /dev/null
+++ b/test/data/artificial/gif-zero-width-frame.gif
Binary files differ
diff --git a/test/data/artificial/gif-zero-width-frame.gif.make-artificial.txt b/test/data/artificial/gif-zero-width-frame.gif.make-artificial.txt
new file mode 100644
index 0000000..a7d16b5
--- /dev/null
+++ b/test/data/artificial/gif-zero-width-frame.gif.make-artificial.txt
@@ -0,0 +1,23 @@
+# Feed this file to script/make-artificial.go
+
+make gif
+
+header
+
+image {
+	imageWidthHeight 2 2
+	palette {
+		0x00 0x00 0xFF
+		0x11 0x00 0xFF
+		0x22 0x00 0xFF
+		0x33 0x00 0xFF
+	}
+}
+
+frame {
+	frameLeftTopWidthHeight 0 0 0 0
+}
+# The frame is 0 pixels, but we supply 1.
+lzw 2 0x00
+
+trailer