Add std/png filter_and_swizzle
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index a198a72..8481c4a 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -8359,6 +8359,8 @@
   struct {
     wuffs_crc32__ieee_hasher f_crc;
     wuffs_zlib__decoder f_zlib;
+    uint8_t f_dst_palette[1024];
+    uint8_t f_src_palette[1024];
 
     struct {
       uint32_t v_dst_pixfmt;
@@ -29798,6 +29800,7 @@
 const char wuffs_png__error__bad_chunk[] = "#png: bad chunk";
 const char wuffs_png__error__bad_header[] = "#png: bad header";
 const char wuffs_png__error__unsupported_png_file[] = "#png: unsupported PNG file";
+const char wuffs_png__error__internal_error_inconsistent_workbuf_length[] = "#png: internal error: inconsistent workbuf length";
 const char wuffs_png__error__internal_error_zlib_decoder_did_not_exhaust_its_input[] = "#png: internal error: zlib decoder did not exhaust its input";
 
 // ---------------- Private Consts
@@ -29806,6 +29809,12 @@
 
 // ---------------- Private Function Prototypes
 
+static wuffs_base__status
+wuffs_png__decoder__filter_and_swizzle(
+    wuffs_png__decoder* self,
+    wuffs_base__pixel_buffer* a_dst,
+    wuffs_base__slice_u8 a_workbuf);
+
 // ---------------- VTables
 
 const wuffs_base__image_decoder__func_ptrs
@@ -30497,6 +30506,7 @@
   uint8_t* io2_v_w WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
   uint64_t v_w_mark = 0;
   uint64_t v_r_mark = 0;
+  wuffs_base__status v_swizzler_status = wuffs_base__make_status(NULL);
   wuffs_base__status v_zlib_status = wuffs_base__make_status(NULL);
 
   const uint8_t* iop_a_src = NULL;
@@ -30686,6 +30696,33 @@
       status = wuffs_base__make_status(wuffs_base__error__not_enough_data);
       goto exit;
     }
+    v_swizzler_status = wuffs_base__pixel_swizzler__prepare(&self->private_impl.f_swizzler,
+        wuffs_base__pixel_buffer__pixel_format(a_dst),
+        wuffs_base__pixel_buffer__palette_or_else(a_dst, wuffs_base__make_slice_u8(self->private_data.f_dst_palette, 1024)),
+        wuffs_base__utility__make_pixel_format(self->private_impl.f_src_pixfmt),
+        wuffs_base__make_slice_u8(self->private_data.f_src_palette, 1024),
+        a_blend);
+    if ( ! wuffs_base__status__is_ok(&v_swizzler_status)) {
+      status = v_swizzler_status;
+      if (wuffs_base__status__is_error(&status)) {
+        goto exit;
+      } else if (wuffs_base__status__is_suspension(&status)) {
+        status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension);
+        goto exit;
+      }
+      goto ok;
+    }
+    v_swizzler_status = wuffs_png__decoder__filter_and_swizzle(self, a_dst, a_workbuf);
+    if ( ! wuffs_base__status__is_ok(&v_swizzler_status)) {
+      status = v_swizzler_status;
+      if (wuffs_base__status__is_error(&status)) {
+        goto exit;
+      } else if (wuffs_base__status__is_suspension(&status)) {
+        status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension);
+        goto exit;
+      }
+      goto ok;
+    }
     self->private_impl.f_call_sequence = 255;
 
     goto ok;
@@ -30711,6 +30748,59 @@
   return status;
 }
 
+// -------- func png.decoder.filter_and_swizzle
+
+static wuffs_base__status
+wuffs_png__decoder__filter_and_swizzle(
+    wuffs_png__decoder* self,
+    wuffs_base__pixel_buffer* a_dst,
+    wuffs_base__slice_u8 a_workbuf) {
+  wuffs_base__pixel_format v_dst_pixfmt = {0};
+  uint32_t v_dst_bits_per_pixel = 0;
+  uint64_t v_dst_bytes_per_pixel = 0;
+  uint64_t v_dst_bytes_per_row = 0;
+  wuffs_base__slice_u8 v_dst_palette = {0};
+  wuffs_base__table_u8 v_tab = {0};
+  uint32_t v_y = 0;
+  wuffs_base__slice_u8 v_dst = {0};
+  uint8_t v_filter = 0;
+  wuffs_base__slice_u8 v_curr_row = {0};
+  wuffs_base__slice_u8 v_prev_row = {0};
+
+  v_dst_pixfmt = wuffs_base__pixel_buffer__pixel_format(a_dst);
+  v_dst_bits_per_pixel = wuffs_base__pixel_format__bits_per_pixel(&v_dst_pixfmt);
+  if ((v_dst_bits_per_pixel & 7) != 0) {
+    return wuffs_base__make_status(wuffs_base__error__unsupported_option);
+  }
+  v_dst_bytes_per_pixel = ((uint64_t)((v_dst_bits_per_pixel / 8)));
+  v_dst_bytes_per_row = (((uint64_t)(self->private_impl.f_width)) * v_dst_bytes_per_pixel);
+  v_dst_palette = wuffs_base__pixel_buffer__palette_or_else(a_dst, wuffs_base__make_slice_u8(self->private_data.f_dst_palette, 1024));
+  v_tab = wuffs_base__pixel_buffer__plane(a_dst, 0);
+  while (v_y < self->private_impl.f_height) {
+    v_dst = wuffs_base__table_u8__row(v_tab, v_y);
+    if (v_dst_bytes_per_row < ((uint64_t)(v_dst.len))) {
+      v_dst = wuffs_base__slice_u8__subslice_j(v_dst, v_dst_bytes_per_row);
+    }
+    if (1 > ((uint64_t)(a_workbuf.len))) {
+      return wuffs_base__make_status(wuffs_png__error__internal_error_inconsistent_workbuf_length);
+    }
+    v_filter = a_workbuf.ptr[0];
+    a_workbuf = wuffs_base__slice_u8__subslice_i(a_workbuf, 1);
+    if (self->private_impl.f_bytes_per_row > ((uint64_t)(a_workbuf.len))) {
+      return wuffs_base__make_status(wuffs_png__error__internal_error_inconsistent_workbuf_length);
+    }
+    v_curr_row = wuffs_base__slice_u8__subslice_j(a_workbuf, self->private_impl.f_bytes_per_row);
+    a_workbuf = wuffs_base__slice_u8__subslice_i(a_workbuf, self->private_impl.f_bytes_per_row);
+    if (v_filter == 0) {
+    } else if (((uint64_t)(v_prev_row.len)) == 0) {
+    }
+    wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, v_dst, v_dst_palette, v_curr_row);
+    v_prev_row = v_curr_row;
+    v_y += 1;
+  }
+  return wuffs_base__make_status(NULL);
+}
+
 // -------- func png.decoder.frame_dirty_rect
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__rect_ie_u32
diff --git a/std/png/decode_png.wuffs b/std/png/decode_png.wuffs
index 89b6ab5..720a1c3 100644
--- a/std/png/decode_png.wuffs
+++ b/std/png/decode_png.wuffs
@@ -19,6 +19,7 @@
 pub status "#bad header"
 pub status "#unsupported PNG file"
 
+pri status "#internal error: inconsistent workbuf length"
 pri status "#internal error: zlib decoder did not exhaust its input"
 
 pub const DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE : base.u64 = 0
@@ -76,6 +77,9 @@
 )(
 	crc  : crc32.ieee_hasher,
 	zlib : zlib.decoder,
+
+	dst_palette : array[4 * 256] base.u8,
+	src_palette : array[4 * 256] base.u8,
 )
 
 pub func decoder.set_quirk_enabled!(quirk: base.u32, enabled: base.bool) {
@@ -232,10 +236,11 @@
 }
 
 pub func decoder.decode_frame?(dst: ptr base.pixel_buffer, src: base.io_reader, blend: base.pixel_blend, workbuf: slice base.u8, opts: nptr base.decode_frame_options) {
-	var w           : base.io_writer
-	var w_mark      : base.u64
-	var r_mark      : base.u64
-	var zlib_status : base.status
+	var w               : base.io_writer
+	var w_mark          : base.u64
+	var r_mark          : base.u64
+	var swizzler_status : base.status
+	var zlib_status     : base.status
 
 	if this.call_sequence < 4 {
 		this.decode_frame_config?(dst: nullptr, src: args.src)
@@ -287,10 +292,85 @@
 	if this.workbuf_wi <> this.workbuf_length {
 		return base."#not enough data"
 	}
+	swizzler_status = this.swizzler.prepare!(
+		dst_pixfmt: args.dst.pixel_format(),
+		dst_palette: args.dst.palette_or_else(fallback: this.dst_palette[..]),
+		src_pixfmt: this.util.make_pixel_format(repr: this.src_pixfmt),
+		src_palette: this.src_palette[..],
+		blend: args.blend)
+	if not swizzler_status.is_ok() {
+		return swizzler_status
+	}
+	swizzler_status = this.filter_and_swizzle!(dst: args.dst, workbuf: args.workbuf)
+	if not swizzler_status.is_ok() {
+		return swizzler_status
+	}
 
 	this.call_sequence = 0xFF
 }
 
+pri func decoder.filter_and_swizzle!(dst: ptr base.pixel_buffer, workbuf: slice base.u8) base.status {
+	var dst_pixfmt          : base.pixel_format
+	var dst_bits_per_pixel  : base.u32[..= 256]
+	var dst_bytes_per_pixel : base.u64[..= 32]
+	var dst_bytes_per_row   : base.u64
+	var dst_palette         : slice base.u8
+	var tab                 : table base.u8
+
+	var y        : base.u32
+	var dst      : slice base.u8
+	var filter   : base.u8
+	var curr_row : slice base.u8
+	var prev_row : slice base.u8
+
+	// TODO: the dst_pixfmt variable shouldn't be necessary. We should be able
+	// to chain the two calls: "args.dst.pixel_format().bits_per_pixel()".
+	dst_pixfmt = args.dst.pixel_format()
+	dst_bits_per_pixel = dst_pixfmt.bits_per_pixel()
+	if (dst_bits_per_pixel & 7) <> 0 {
+		return base."#unsupported option"
+	}
+	dst_bytes_per_pixel = (dst_bits_per_pixel / 8) as base.u64
+	dst_bytes_per_row = (this.width as base.u64) * dst_bytes_per_pixel
+	dst_palette = args.dst.palette_or_else(fallback: this.dst_palette[..])
+	tab = args.dst.plane(p: 0)
+
+	while y < this.height {
+		assert y < 0xFFFF_FFFF via "a < b: a < c; c <= b"(c: this.height)
+		dst = tab.row(y: y)
+		if dst_bytes_per_row < dst.length() {
+			dst = dst[.. dst_bytes_per_row]
+		}
+
+		if 1 > args.workbuf.length() {
+			return "#internal error: inconsistent workbuf length"
+		}
+		filter = args.workbuf[0]
+		args.workbuf = args.workbuf[1 ..]
+		if this.bytes_per_row > args.workbuf.length() {
+			return "#internal error: inconsistent workbuf length"
+		}
+		curr_row = args.workbuf[.. this.bytes_per_row]
+		args.workbuf = args.workbuf[this.bytes_per_row ..]
+
+		if filter == 0 {
+			// No-op.
+		} else if prev_row.length() == 0 {
+			// TODO.
+		}
+
+		this.swizzler.swizzle_interleaved_from_slice!(
+			dst: dst,
+			dst_palette: dst_palette,
+			src: curr_row)
+
+		prev_row = curr_row
+		y += 1
+	} endwhile
+
+	return ok
+}
+
 pub func decoder.frame_dirty_rect() base.rect_ie_u32 {
 	return this.util.make_rect_ie_u32(
 		min_incl_x: 0,
diff --git a/test/c/std/png.c b/test/c/std/png.c
index e46beb7..9165588 100644
--- a/test/c/std/png.c
+++ b/test/c/std/png.c
@@ -83,7 +83,7 @@
                    WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED));
   return do_test__wuffs_base__image_decoder(
       wuffs_png__decoder__upcast_as__wuffs_base__image_decoder(&dec),
-      "test/data/bricks-gray.png", 0, SIZE_MAX, 160, 120, 0);  // TODO.
+      "test/data/bricks-gray.png", 0, SIZE_MAX, 160, 120, 0xFF060606);
 }
 
 const char*  //