Revert "Add Animated PNG demuxer (#31098)"

This reverts commit b3e9642c6b37943db65ea768e819662b45feb09f.
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index d2468df..3f9b72a 100644
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -1789,8 +1789,6 @@
 ORIGIN: ../../../flutter/lib/ui/painting/image_filter.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/lib/ui/painting/image_generator.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/lib/ui/painting/image_generator.h + ../../../flutter/LICENSE
-ORIGIN: ../../../flutter/lib/ui/painting/image_generator_apng.cc + ../../../flutter/LICENSE
-ORIGIN: ../../../flutter/lib/ui/painting/image_generator_apng.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/lib/ui/painting/image_generator_registry.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/lib/ui/painting/image_generator_registry.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/lib/ui/painting/image_shader.cc + ../../../flutter/LICENSE
@@ -4268,8 +4266,6 @@
 FILE: ../../../flutter/lib/ui/painting/image_filter.h
 FILE: ../../../flutter/lib/ui/painting/image_generator.cc
 FILE: ../../../flutter/lib/ui/painting/image_generator.h
-FILE: ../../../flutter/lib/ui/painting/image_generator_apng.cc
-FILE: ../../../flutter/lib/ui/painting/image_generator_apng.h
 FILE: ../../../flutter/lib/ui/painting/image_generator_registry.cc
 FILE: ../../../flutter/lib/ui/painting/image_generator_registry.h
 FILE: ../../../flutter/lib/ui/painting/image_shader.cc
diff --git a/fml/endianness.h b/fml/endianness.h
index eff90f9..269b461 100644
--- a/fml/endianness.h
+++ b/fml/endianness.h
@@ -26,17 +26,9 @@
 
 namespace fml {
 
-template <typename T>
-struct IsByteSwappable
-    : public std::
-          integral_constant<bool, std::is_integral_v<T> || std::is_enum_v<T>> {
-};
-template <typename T>
-constexpr bool IsByteSwappableV = IsByteSwappable<T>::value;
-
 /// @brief  Flips the endianness of the given value.
 ///         The given value must be an integral type of size 1, 2, 4, or 8.
-template <typename T, class = std::enable_if_t<IsByteSwappableV<T>>>
+template <typename T, class = std::enable_if_t<std::is_integral_v<T>>>
 constexpr T ByteSwap(T n) {
   if constexpr (sizeof(T) == 1) {
     return n;
@@ -55,7 +47,7 @@
 ///         current architecture. This is effectively a cross platform
 ///         ntohl/ntohs (as network byte order is always Big Endian).
 ///         The given value must be an integral type of size 1, 2, 4, or 8.
-template <typename T, class = std::enable_if_t<IsByteSwappableV<T>>>
+template <typename T, class = std::enable_if_t<std::is_integral_v<T>>>
 constexpr T BigEndianToArch(T n) {
 #if FML_ARCH_CPU_LITTLE_ENDIAN
   return ByteSwap<T>(n);
@@ -67,7 +59,7 @@
 /// @brief  Convert a known little endian value to match the endianness of the
 ///         current architecture.
 ///         The given value must be an integral type of size 1, 2, 4, or 8.
-template <typename T, class = std::enable_if_t<IsByteSwappableV<T>>>
+template <typename T, class = std::enable_if_t<std::is_integral_v<T>>>
 constexpr T LittleEndianToArch(T n) {
 #if !FML_ARCH_CPU_LITTLE_ENDIAN
   return ByteSwap<T>(n);
diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn
index b0c3ce3..02a6fab 100644
--- a/lib/ui/BUILD.gn
+++ b/lib/ui/BUILD.gn
@@ -59,8 +59,6 @@
     "painting/image_filter.h",
     "painting/image_generator.cc",
     "painting/image_generator.h",
-    "painting/image_generator_apng.cc",
-    "painting/image_generator_apng.h",
     "painting/image_generator_registry.cc",
     "painting/image_generator_registry.h",
     "painting/image_shader.cc",
@@ -163,7 +161,6 @@
     "//third_party/dart/runtime/bin:dart_io_api",
     "//third_party/rapidjson",
     "//third_party/skia",
-    "//third_party/zlib:zlib",
   ]
 
   if (impeller_supports_rendering) {
diff --git a/lib/ui/painting/image_decoder_unittests.cc b/lib/ui/painting/image_decoder_unittests.cc
index a9b09c5..2bf4a8b 100644
--- a/lib/ui/painting/image_decoder_unittests.cc
+++ b/lib/ui/painting/image_decoder_unittests.cc
@@ -162,7 +162,7 @@
 
   unsigned int GetPlayCount() const { return 1; }
 
-  const ImageGenerator::FrameInfo GetFrameInfo(unsigned int frame_index) {
+  const ImageGenerator::FrameInfo GetFrameInfo(unsigned int frame_index) const {
     return {std::nullopt, 0, SkCodecAnimation::DisposalMethod::kKeep};
   }
 
diff --git a/lib/ui/painting/image_generator.cc b/lib/ui/painting/image_generator.cc
index 3db7625..e5648fc 100644
--- a/lib/ui/painting/image_generator.cc
+++ b/lib/ui/painting/image_generator.cc
@@ -51,7 +51,7 @@
 }
 
 const ImageGenerator::FrameInfo BuiltinSkiaImageGenerator::GetFrameInfo(
-    unsigned int frame_index) {
+    unsigned int frame_index) const {
   return {.required_frame = std::nullopt,
           .duration = 0,
           .disposal_method = SkCodecAnimation::DisposalMethod::kKeep};
@@ -105,7 +105,7 @@
 }
 
 const ImageGenerator::FrameInfo BuiltinSkiaCodecImageGenerator::GetFrameInfo(
-    unsigned int frame_index) {
+    unsigned int frame_index) const {
   SkCodec::FrameInfo info = {};
   codec_generator_->getFrameInfo(frame_index, &info);
   return {
diff --git a/lib/ui/painting/image_generator.h b/lib/ui/painting/image_generator.h
index a5515c8..4792f3b 100644
--- a/lib/ui/painting/image_generator.h
+++ b/lib/ui/painting/image_generator.h
@@ -37,15 +37,11 @@
     /// blended with.
     std::optional<unsigned int> required_frame;
 
-    /// Number of milliseconds to show this frame. 0 means only show it for one
-    /// frame.
+    /// Number of milliseconds to show this frame.
     unsigned int duration;
 
     /// How this frame should be modified before decoding the next one.
     SkCodecAnimation::DisposalMethod disposal_method;
-
-    /// How this frame should be blended with the previous frame.
-    SkCodecAnimation::Blend blend_mode;
   };
 
   virtual ~ImageGenerator();
@@ -84,7 +80,7 @@
   /// @return     Information about the given frame. If the image is
   ///             single-frame, a default result is returned.
   /// @see        `GetFrameCount`
-  virtual const FrameInfo GetFrameInfo(unsigned int frame_index) = 0;
+  virtual const FrameInfo GetFrameInfo(unsigned int frame_index) const = 0;
 
   /// @brief      Given a scale value, find the closest image size that can be
   ///             used for efficiently decoding the image. If subpixel image
@@ -156,7 +152,7 @@
 
   // |ImageGenerator|
   const ImageGenerator::FrameInfo GetFrameInfo(
-      unsigned int frame_index) override;
+      unsigned int frame_index) const override;
 
   // |ImageGenerator|
   SkISize GetScaledDimensions(float desired_scale) override;
@@ -196,7 +192,7 @@
 
   // |ImageGenerator|
   const ImageGenerator::FrameInfo GetFrameInfo(
-      unsigned int frame_index) override;
+      unsigned int frame_index) const override;
 
   // |ImageGenerator|
   SkISize GetScaledDimensions(float desired_scale) override;
diff --git a/lib/ui/painting/image_generator_apng.cc b/lib/ui/painting/image_generator_apng.cc
deleted file mode 100644
index 943d775..0000000
--- a/lib/ui/painting/image_generator_apng.cc
+++ /dev/null
@@ -1,616 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "image_generator_apng.h"
-#include <cstddef>
-#include <cstring>
-
-#include "flutter/fml/logging.h"
-#include "third_party/libpng/png.h"
-#include "third_party/skia/include/codec/SkCodecAnimation.h"
-#include "third_party/skia/include/core/SkAlphaType.h"
-#include "third_party/skia/include/core/SkColorType.h"
-#include "third_party/skia/include/core/SkStream.h"
-#include "third_party/skia/src/codec/SkPngCodec.h"
-#include "third_party/zlib/zlib.h"  // For crc32
-
-namespace flutter {
-
-APNGImageGenerator::~APNGImageGenerator() = default;
-
-APNGImageGenerator::APNGImageGenerator(sk_sp<SkData>& data,
-                                       SkImageInfo& image_info,
-                                       APNGImage&& default_image,
-                                       unsigned int frame_count,
-                                       unsigned int play_count,
-                                       const void* next_chunk_p,
-                                       const std::vector<uint8_t>& header)
-    : data_(data),
-      image_info_(image_info),
-      frame_count_(frame_count),
-      play_count_(play_count),
-      first_frame_index_(default_image.frame_info.has_value() ? 0 : 1),
-      next_chunk_p_(next_chunk_p),
-      header_(header) {
-  images_.push_back(std::move(default_image));
-}
-
-const SkImageInfo& APNGImageGenerator::GetInfo() {
-  return image_info_;
-}
-
-unsigned int APNGImageGenerator::GetFrameCount() const {
-  return frame_count_;
-}
-
-unsigned int APNGImageGenerator::GetPlayCount() const {
-  return frame_count_ > 1 ? play_count_ : 1;
-}
-
-const ImageGenerator::FrameInfo APNGImageGenerator::GetFrameInfo(
-    unsigned int frame_index) {
-  unsigned int image_index = first_frame_index_ + frame_index;
-  if (!DemuxToImageIndex(image_index)) {
-    return {};
-  }
-
-  return images_[image_index].frame_info.value();
-}
-
-SkISize APNGImageGenerator::GetScaledDimensions(float desired_scale) {
-  return image_info_.dimensions();
-}
-
-bool APNGImageGenerator::GetPixels(const SkImageInfo& info,
-                                   void* pixels,
-                                   size_t row_bytes,
-                                   unsigned int frame_index,
-                                   std::optional<unsigned int> prior_frame) {
-  FML_DCHECK(images_.size() > 0);
-  unsigned int image_index = first_frame_index_ + frame_index;
-
-  //----------------------------------------------------------------------------
-  /// 1. Demux the frame from the APNG stream.
-  ///
-
-  if (!DemuxToImageIndex(image_index)) {
-    FML_DLOG(ERROR) << "Couldn't demux image at index " << image_index
-                    << " (frame index: " << frame_index
-                    << ") from APNG stream.";
-    return RenderDefaultImage(info, pixels, row_bytes);
-  }
-
-  //----------------------------------------------------------------------------
-  /// 2. Decode the frame.
-  ///
-
-  APNGImage& frame = images_[image_index];
-  auto frame_info = frame.codec->getInfo();
-  auto frame_row_bytes = frame_info.bytesPerPixel() * frame_info.width();
-
-  if (frame.pixels.empty()) {
-    frame.pixels.resize(frame_row_bytes * frame_info.height());
-    SkCodec::Result result = frame.codec->getPixels(
-        frame.codec->getInfo(), frame.pixels.data(), frame_row_bytes);
-    if (result != SkCodec::kSuccess) {
-      FML_DLOG(ERROR) << "Failed to decode image at index " << image_index
-                      << " (frame index: " << frame_index
-                      << ") of APNG. SkCodec::Result: " << result;
-      return RenderDefaultImage(info, pixels, row_bytes);
-    }
-  }
-
-  //----------------------------------------------------------------------------
-  /// 3. Composite the frame onto the canvas.
-  ///
-
-  if (info.colorType() != kN32_SkColorType) {
-    FML_DLOG(ERROR) << "Failed to composite image at index " << image_index
-                    << " (frame index: " << frame_index
-                    << ") of APNG due to the destination surface having an "
-                       "unsupported color type.";
-    return false;
-  }
-  if (frame_info.colorType() != kN32_SkColorType) {
-    FML_DLOG(ERROR)
-        << "Failed to composite image at index " << image_index
-        << " (frame index: " << frame_index
-        << ") of APNG due to the frame having an unsupported color type.";
-    return false;
-  }
-
-  // Regardless of the byte order (RGBA vs BGRA), the blending operations are
-  // the same.
-  struct Pixel {
-    uint8_t channel[4];
-
-    uint8_t GetAlpha() { return channel[3]; }
-
-    void Premultiply() {
-      for (int i = 0; i < 3; i++) {
-        channel[i] = channel[i] * GetAlpha() / 0xFF;
-      }
-    }
-
-    void Unpremultiply() {
-      if (GetAlpha() == 0) {
-        channel[0] = channel[1] = channel[2] = 0;
-        return;
-      }
-      for (int i = 0; i < 3; i++) {
-        channel[i] = channel[i] * 0xFF / GetAlpha();
-      }
-    }
-  };
-
-  FML_DCHECK(frame_info.bytesPerPixel() == sizeof(Pixel));
-
-  for (int y = 0; y < frame_info.height(); y++) {
-    auto src_row = frame.pixels.data() + y * frame_row_bytes;
-    auto dst_row = static_cast<uint8_t*>(pixels) +
-                   (y + frame.y_offset) * row_bytes +
-                   frame.x_offset * frame_info.bytesPerPixel();
-
-    switch (frame.frame_info->blend_mode) {
-      case SkCodecAnimation::Blend::kSrcOver: {
-        for (int x = 0; x < frame_info.width(); x++) {
-          auto x_offset_bytes = x * frame_info.bytesPerPixel();
-
-          Pixel src = *reinterpret_cast<Pixel*>(src_row + x_offset_bytes);
-          Pixel* dst_p = reinterpret_cast<Pixel*>(dst_row + x_offset_bytes);
-          Pixel dst = *dst_p;
-
-          // Ensure both colors are premultiplied for the blending operation.
-          if (info.alphaType() == kUnpremul_SkAlphaType) {
-            dst.Premultiply();
-          }
-          if (frame_info.alphaType() == kUnpremul_SkAlphaType) {
-            src.Premultiply();
-          }
-
-          for (int i = 0; i < 4; i++) {
-            dst.channel[i] = src.channel[i] +
-                             dst.channel[i] * (0xFF - src.GetAlpha()) / 0xFF;
-          }
-
-          // The final color is premultiplied. Unpremultiply to match the
-          // backdrop surface if necessary.
-          if (info.alphaType() == kUnpremul_SkAlphaType) {
-            dst.Unpremultiply();
-          }
-
-          *dst_p = dst;
-        }
-        break;
-      }
-      case SkCodecAnimation::Blend::kSrc:
-        memcpy(dst_row, src_row, frame_row_bytes);
-        break;
-    }
-  }
-
-  return true;
-}
-
-std::unique_ptr<ImageGenerator> APNGImageGenerator::MakeFromData(
-    sk_sp<SkData> data) {
-  // Ensure the buffer is large enough to at least contain the PNG signature
-  // and a chunk header.
-  if (data->size() < sizeof(kPngSignature) + sizeof(ChunkHeader)) {
-    return nullptr;
-  }
-  // Validate the full PNG signature.
-  const uint8_t* data_p = static_cast<const uint8_t*>(data.get()->data());
-  if (png_sig_cmp(static_cast<png_const_bytep>(data_p), 0,
-                  sizeof(kPngSignature))) {
-    return nullptr;
-  }
-
-  // Validate the header chunk.
-  const ChunkHeader* chunk = reinterpret_cast<const ChunkHeader*>(data_p + 8);
-  if (!IsValidChunkHeader(data_p, data->size(), chunk) ||
-      chunk->get_data_length() != sizeof(ImageHeaderChunkData) ||
-      chunk->get_type() != kImageHeaderChunkType) {
-    return nullptr;
-  }
-
-  // Walk the chunks to find the "animation control" chunk. If an "image data"
-  // chunk is found first, this PNG is not animated.
-  while (true) {
-    chunk = GetNextChunk(data_p, data->size(), chunk);
-
-    if (chunk == nullptr) {
-      return nullptr;
-    }
-    if (chunk->get_type() == kImageDataChunkType) {
-      return nullptr;
-    }
-    if (chunk->get_type() == kAnimationControlChunkType) {
-      break;
-    }
-  }
-
-  const AnimationControlChunkData* animation_data =
-      CastChunkData<AnimationControlChunkData>(chunk);
-
-  // Extract the header signature and chunks to prepend when demuxing images.
-  std::optional<std::vector<uint8_t>> header;
-  const void* first_chunk_p;
-  std::tie(header, first_chunk_p) = ExtractHeader(data_p, data->size());
-  if (!header.has_value()) {
-    return nullptr;
-  }
-
-  // Demux the first image in the APNG chunk stream in order to interpret
-  // extent and blending info immediately.
-  std::optional<APNGImage> default_image;
-  const void* next_chunk_p;
-  std::tie(default_image, next_chunk_p) =
-      DemuxNextImage(data_p, data->size(), header.value(), first_chunk_p);
-  if (!default_image.has_value()) {
-    return nullptr;
-  }
-
-  unsigned int play_count = animation_data->get_num_plays();
-  if (play_count == 0) {
-    play_count = kInfinitePlayCount;
-  }
-
-  SkImageInfo image_info = default_image.value().codec->getInfo();
-  return std::unique_ptr<APNGImageGenerator>(
-      new APNGImageGenerator(data, image_info, std::move(default_image.value()),
-                             animation_data->get_num_frames(), play_count,
-                             next_chunk_p, header.value()));
-}
-
-bool APNGImageGenerator::IsValidChunkHeader(const void* buffer,
-                                            size_t size,
-                                            const ChunkHeader* chunk) {
-  // Ensure the chunk doesn't start before the beginning of the buffer.
-  if (reinterpret_cast<const uint8_t*>(chunk) <
-      static_cast<const uint8_t*>(buffer)) {
-    return false;
-  }
-
-  // Ensure the buffer is large enough to contain at least the chunk header.
-  if (reinterpret_cast<const uint8_t*>(chunk) + sizeof(ChunkHeader) >
-      static_cast<const uint8_t*>(buffer) + size) {
-    return false;
-  }
-
-  // Ensure the buffer is large enough to contain the chunk's given data size
-  // and CRC.
-  const uint8_t* chunk_end =
-      reinterpret_cast<const uint8_t*>(chunk) + GetChunkSize(chunk);
-  if (chunk_end > static_cast<const uint8_t*>(buffer) + size) {
-    return false;
-  }
-
-  // Ensure the 4-byte type only contains ISO 646 letters.
-  uint32_t type = chunk->get_type();
-  for (int i = 0; i < 4; i++) {
-    uint8_t c = type >> i * 8 & 0xFF;
-    if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
-const APNGImageGenerator::ChunkHeader* APNGImageGenerator::GetNextChunk(
-    const void* buffer,
-    size_t size,
-    const ChunkHeader* current_chunk) {
-  FML_DCHECK((uint8_t*)current_chunk + sizeof(ChunkHeader) <=
-             (uint8_t*)buffer + size);
-
-  const ChunkHeader* next_chunk = reinterpret_cast<const ChunkHeader*>(
-      reinterpret_cast<const uint8_t*>(current_chunk) +
-      GetChunkSize(current_chunk));
-  if (!IsValidChunkHeader(buffer, size, next_chunk)) {
-    return nullptr;
-  }
-
-  return next_chunk;
-}
-
-std::pair<std::optional<std::vector<uint8_t>>, const void*>
-APNGImageGenerator::ExtractHeader(const void* buffer_p, size_t buffer_size) {
-  std::vector<uint8_t> result(sizeof(kPngSignature));
-  memcpy(result.data(), kPngSignature, sizeof(kPngSignature));
-
-  const ChunkHeader* chunk = reinterpret_cast<const ChunkHeader*>(
-      static_cast<const uint8_t*>(buffer_p) + sizeof(kPngSignature));
-  // Validate the first chunk to ensure it's safe to read.
-  if (!IsValidChunkHeader(buffer_p, buffer_size, chunk)) {
-    return std::make_pair(std::nullopt, nullptr);
-  }
-
-  // Walk the chunks and copy in the non-APNG chunks until we come across a
-  // frame or image chunk.
-  do {
-    if (chunk->get_type() != kAnimationControlChunkType) {
-      size_t chunk_size = GetChunkSize(chunk);
-      result.resize(result.size() + chunk_size);
-      memcpy(result.data() + result.size() - chunk_size, chunk, chunk_size);
-    }
-
-    chunk = GetNextChunk(buffer_p, buffer_size, chunk);
-  } while (chunk != nullptr && chunk->get_type() != kFrameControlChunkType &&
-           chunk->get_type() != kImageDataChunkType &&
-           chunk->get_type() != kFrameDataChunkType);
-
-  // nullptr means the end of the buffer was reached, which means there's no
-  // frame or image data, so just return nothing because the PNG isn't even
-  // valid.
-  if (chunk == nullptr) {
-    return std::make_pair(std::nullopt, nullptr);
-  }
-
-  return std::make_pair(result, chunk);
-}
-
-std::pair<std::optional<APNGImageGenerator::APNGImage>, const void*>
-APNGImageGenerator::DemuxNextImage(const void* buffer_p,
-                                   size_t buffer_size,
-                                   const std::vector<uint8_t>& header,
-                                   const void* chunk_p) {
-  const ChunkHeader* chunk = reinterpret_cast<const ChunkHeader*>(chunk_p);
-  // Validate the given chunk to ensure it's safe to read.
-  if (!IsValidChunkHeader(buffer_p, buffer_size, chunk)) {
-    return std::make_pair(std::nullopt, nullptr);
-  }
-
-  // Expect frame data to begin at fdAT or IDAT
-  if (chunk->get_type() != kFrameControlChunkType &&
-      chunk->get_type() != kImageDataChunkType) {
-    return std::make_pair(std::nullopt, nullptr);
-  }
-
-  APNGImage result;
-  const FrameControlChunkData* control_data = nullptr;
-
-  // The presence of an fcTL chunk is optional for the first (default) image
-  // of a PNG. Both cases are handled in APNGImage.
-  if (chunk->get_type() == kFrameControlChunkType) {
-    control_data = CastChunkData<FrameControlChunkData>(chunk);
-
-    ImageGenerator::FrameInfo frame_info;
-    switch (control_data->get_blend_op()) {
-      case 0:  // APNG_BLEND_OP_SOURCE
-        frame_info.blend_mode = SkCodecAnimation::Blend::kSrc;
-        break;
-      case 1:  // APNG_BLEND_OP_OVER
-        frame_info.blend_mode = SkCodecAnimation::Blend::kSrcOver;
-        break;
-      default:
-        return std::make_pair(std::nullopt, nullptr);
-    }
-    switch (control_data->get_dispose_op()) {
-      case 0:  // APNG_DISPOSE_OP_NONE
-        frame_info.disposal_method = SkCodecAnimation::DisposalMethod::kKeep;
-        break;
-      case 1:  // APNG_DISPOSE_OP_BACKGROUND
-        frame_info.disposal_method =
-            SkCodecAnimation::DisposalMethod::kRestoreBGColor;
-        break;
-      case 2:  // APNG_DISPOSE_OP_PREVIOUS
-        frame_info.disposal_method =
-            SkCodecAnimation::DisposalMethod::kRestorePrevious;
-        break;
-      default:
-        return std::make_pair(std::nullopt, nullptr);
-    }
-    uint16_t denominator = control_data->get_delay_den() == 0
-                               ? 100
-                               : control_data->get_delay_den();
-    frame_info.duration =
-        static_cast<int>(control_data->get_delay_num() * 1000.f / denominator);
-
-    result.frame_info = frame_info;
-    result.x_offset = control_data->get_x_offset();
-    result.y_offset = control_data->get_y_offset();
-  }
-
-  std::vector<const ChunkHeader*> image_chunks;
-  size_t chunk_space = 0;
-
-  // Walk the chunks until the next frame, end chunk, or an invalid chunk is
-  // reached, recording the chunks to copy along with their required space.
-  // TODO(bdero): Validate that IDAT/fdAT chunks are contiguous.
-  // TODO(bdero): Validate the acTL/fcTL/fdAT sequence number ordering.
-  do {
-    if (chunk->get_type() != kFrameControlChunkType) {
-      image_chunks.push_back(chunk);
-      chunk_space += GetChunkSize(chunk);
-
-      // fdAT chunks are converted into IDAT chunks when demuxed. The only
-      // difference between these chunk types is that fdAT has a 4 byte
-      // sequence number prepended to its data, so subtract that space from
-      // the buffer.
-      if (chunk->get_type() == kFrameDataChunkType) {
-        chunk_space -= 4;
-      }
-    }
-
-    chunk = GetNextChunk(buffer_p, buffer_size, chunk);
-  } while (chunk != nullptr && chunk->get_type() != kFrameControlChunkType &&
-           chunk->get_type() != kImageTrailerChunkType);
-
-  const uint8_t end_chunk[] = {0,   0,   0,    0,    'I',  'E',
-                               'N', 'D', 0xAE, 0x42, 0x60, 0x82};
-
-  // Form a buffer for the new encoded PNG and copy the chunks in.
-  sk_sp<SkData> new_png_buffer = SkData::MakeUninitialized(
-      header.size() + chunk_space + sizeof(end_chunk));
-
-  {
-    uint8_t* write_cursor =
-        static_cast<uint8_t*>(new_png_buffer->writable_data());
-
-    // Copy the signature/header chunks
-    memcpy(write_cursor, header.data(), header.size());
-    // If this is a frame, override the width/height in the IHDR chunk.
-    if (control_data) {
-      ChunkHeader* ihdr_header =
-          reinterpret_cast<ChunkHeader*>(write_cursor + sizeof(kPngSignature));
-      ImageHeaderChunkData* ihdr_data = const_cast<ImageHeaderChunkData*>(
-          CastChunkData<ImageHeaderChunkData>(ihdr_header));
-      ihdr_data->set_width(control_data->get_width());
-      ihdr_data->set_height(control_data->get_height());
-      ihdr_header->UpdateChunkCrc32();
-    }
-    write_cursor += header.size();
-
-    // Copy the image data/ancillary chunks.
-    for (const ChunkHeader* c : image_chunks) {
-      if (c->get_type() == kFrameDataChunkType) {
-        // Write a new IDAT chunk header.
-        ChunkHeader* write_header =
-            reinterpret_cast<ChunkHeader*>(write_cursor);
-        write_header->set_data_length(c->get_data_length() - 4);
-        write_header->set_type(kImageDataChunkType);
-        write_cursor += sizeof(ChunkHeader);
-
-        // Copy all of the data except for the 4 byte sequence number at the
-        // beginning of the fdAT data.
-        memcpy(write_cursor,
-               reinterpret_cast<const uint8_t*>(c) + sizeof(ChunkHeader) + 4,
-               write_header->get_data_length());
-        write_cursor += write_header->get_data_length();
-
-        // Recompute the chunk CRC.
-        write_header->UpdateChunkCrc32();
-        write_cursor += 4;
-      } else {
-        size_t chunk_size = GetChunkSize(c);
-        memcpy(write_cursor, c, chunk_size);
-        write_cursor += chunk_size;
-      }
-    }
-
-    // Copy the trailer chunk.
-    memcpy(write_cursor, &end_chunk, sizeof(end_chunk));
-  }
-
-  SkCodec::Result header_parse_result;
-  result.codec = SkPngCodec::MakeFromStream(
-      SkMemoryStream::Make(new_png_buffer), &header_parse_result);
-  if (header_parse_result != SkCodec::Result::kSuccess) {
-    FML_DLOG(ERROR)
-        << "Failed to parse image header during APNG demux. SkCodec::Result: "
-        << header_parse_result;
-    return std::make_pair(std::nullopt, nullptr);
-  }
-
-  if (chunk->get_type() == kImageTrailerChunkType) {
-    chunk = nullptr;
-  }
-
-  return std::make_pair(std::optional<APNGImage>{std::move(result)}, chunk);
-}
-
-bool APNGImageGenerator::DemuxNextImageInternal() {
-  if (next_chunk_p_ == nullptr) {
-    return false;
-  }
-
-  std::optional<APNGImage> image;
-  const void* data_p = const_cast<void*>(data_.get()->data());
-  std::tie(image, next_chunk_p_) =
-      DemuxNextImage(data_p, data_->size(), header_, next_chunk_p_);
-  if (!image.has_value()) {
-    return false;
-  }
-
-  if (images_.back().frame_info->disposal_method ==
-      SkCodecAnimation::DisposalMethod::kRestorePrevious) {
-    FML_DLOG(INFO)
-        << "DisposalMethod::kRestorePrevious is not supported by the "
-           "MultiFrameCodec. Falling back to DisposalMethod::kRestoreBGColor "
-           " behavior instead.";
-  }
-
-  if (images_.size() > first_frame_index_ &&
-      images_.back().frame_info->disposal_method ==
-          SkCodecAnimation::DisposalMethod::kKeep) {
-    // Mark the required frame as the previous frame in all cases.
-    image->frame_info->required_frame = images_.size() - 1;
-  }
-
-  // Calling SkCodec::getInfo at least once prior to decoding is mandatory.
-  SkImageInfo info = image.value().codec->getInfo();
-  FML_DCHECK(info.colorInfo() == image_info_.colorInfo());
-
-  images_.push_back(std::move(image.value()));
-
-  auto default_info = images_[0].codec->getInfo();
-  if (info.colorType() != default_info.colorType()) {
-    return false;
-  }
-  return true;
-}
-
-bool APNGImageGenerator::DemuxToImageIndex(unsigned int image_index) {
-  // If the requested image doesn't exist yet, demux more frames from the APNG
-  // stream.
-  if (image_index >= images_.size()) {
-    while (DemuxNextImageInternal() && image_index >= images_.size()) {
-    }
-
-    if (image_index >= images_.size()) {
-      // The chunk stream was exhausted before the image was found.
-      return false;
-    }
-  }
-
-  return true;
-}
-
-void APNGImageGenerator::ChunkHeader::UpdateChunkCrc32() {
-  uint32_t* crc_p =
-      reinterpret_cast<uint32_t*>(reinterpret_cast<uint8_t*>(this) +
-                                  sizeof(ChunkHeader) + get_data_length());
-  *crc_p = fml::BigEndianToArch(ComputeChunkCrc32());
-}
-
-uint32_t APNGImageGenerator::ChunkHeader::ComputeChunkCrc32() {
-  // Exclude the length field at the beginning of the chunk header.
-  size_t length = sizeof(ChunkHeader) - 4 + get_data_length();
-  uint8_t* chunk_data_p = reinterpret_cast<uint8_t*>(this) + 4;
-  uint32_t crc = 0;
-
-  // zlib's crc32 can only take 16 bits at a time for the length, but PNG
-  // supports a 32 bit chunk length, so looping is necessary here.
-  // Note that crc32 is always called at least once, even if the chunk has an
-  // empty data section.
-  do {
-    uint16_t length16 = length;
-    if (length16 == 0 && length > 0) {
-      length16 = std::numeric_limits<uint16_t>::max();
-    }
-
-    crc = crc32(crc, chunk_data_p, length16);
-    length -= length16;
-    chunk_data_p += length16;
-  } while (length > 0);
-
-  return crc;
-}
-
-bool APNGImageGenerator::RenderDefaultImage(const SkImageInfo& info,
-                                            void* pixels,
-                                            size_t row_bytes) {
-  SkCodec::Result result = images_[0].codec->getPixels(info, pixels, row_bytes);
-  if (result != SkCodec::kSuccess) {
-    FML_DLOG(ERROR) << "Failed to decode the APNG's default/fallback image. "
-                       "SkCodec::Result: "
-                    << result;
-    return false;
-  }
-  return true;
-}
-
-}  // namespace flutter
diff --git a/lib/ui/painting/image_generator_apng.h b/lib/ui/painting/image_generator_apng.h
deleted file mode 100644
index a18ef58..0000000
--- a/lib/ui/painting/image_generator_apng.h
+++ /dev/null
@@ -1,216 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "image_generator.h"
-
-#include "flutter/fml/endianness.h"
-#include "flutter/fml/logging.h"
-
-#define PNG_FIELD(T, name)                \
- private:                                 \
-  T name;                                 \
-                                          \
- public:                                  \
-  T get_##name() const {                  \
-    return fml::BigEndianToArch<T>(name); \
-  }                                       \
-  void set_##name(T n) {                  \
-    name = fml::BigEndianToArch<T>(n);    \
-  }
-
-namespace flutter {
-
-class APNGImageGenerator : public ImageGenerator {
- public:
-  ~APNGImageGenerator();
-
-  // |ImageGenerator|
-  const SkImageInfo& GetInfo() override;
-
-  // |ImageGenerator|
-  unsigned int GetFrameCount() const override;
-
-  // |ImageGenerator|
-  unsigned int GetPlayCount() const override;
-
-  // |ImageGenerator|
-  const ImageGenerator::FrameInfo GetFrameInfo(
-      unsigned int frame_index) override;
-
-  // |ImageGenerator|
-  SkISize GetScaledDimensions(float desired_scale) override;
-
-  // |ImageGenerator|
-  bool GetPixels(const SkImageInfo& info,
-                 void* pixels,
-                 size_t row_bytes,
-                 unsigned int frame_index,
-                 std::optional<unsigned int> prior_frame) override;
-
-  static std::unique_ptr<ImageGenerator> MakeFromData(sk_sp<SkData> data);
-
- private:
-  static constexpr uint8_t kPngSignature[8] = {137, 80, 78, 71, 13, 10, 26, 10};
-  static constexpr size_t kChunkCrcSize = 4;
-
-  enum ChunkType {
-    kImageHeaderChunkType = 'IHDR',
-    kAnimationControlChunkType = 'acTL',
-    kImageDataChunkType = 'IDAT',
-    kFrameControlChunkType = 'fcTL',
-    kFrameDataChunkType = 'fdAT',
-    kImageTrailerChunkType = 'IEND',
-  };
-
-  class __attribute__((packed, aligned(1))) ChunkHeader {
-    PNG_FIELD(uint32_t, data_length)
-    PNG_FIELD(ChunkType, type)
-
-   public:
-    void UpdateChunkCrc32();
-
-   private:
-    uint32_t ComputeChunkCrc32();
-  };
-
-  class __attribute__((packed, aligned(1))) ImageHeaderChunkData {
-    PNG_FIELD(uint32_t, width)
-    PNG_FIELD(uint32_t, height)
-    PNG_FIELD(uint8_t, bit_depth)
-    PNG_FIELD(uint8_t, color_type)
-    PNG_FIELD(uint8_t, compression_method)
-    PNG_FIELD(uint8_t, filter_method)
-    PNG_FIELD(uint8_t, interlace_method)
-  };
-
-  class __attribute__((packed, aligned(1))) AnimationControlChunkData {
-    PNG_FIELD(uint32_t, num_frames)
-    PNG_FIELD(uint32_t, num_plays)
-  };
-
-  class __attribute__((packed, aligned(1))) FrameControlChunkData {
-    PNG_FIELD(uint32_t, sequence_number)
-    PNG_FIELD(uint32_t, width)
-    PNG_FIELD(uint32_t, height)
-    PNG_FIELD(uint32_t, x_offset)
-    PNG_FIELD(uint32_t, y_offset)
-    PNG_FIELD(uint16_t, delay_num)
-    PNG_FIELD(uint16_t, delay_den)
-    PNG_FIELD(uint8_t, dispose_op)
-    PNG_FIELD(uint8_t, blend_op)
-  };
-
-  /// @brief  The first PNG frame is always the "default" PNG frame. Absence of
-  ///         `frame_info` is only possible on the "default" PNG frame.
-  ///         Each frame goes through two decoding stages:
-  ///         1. Demuxing stage: An individual PNG codec is created for a frame
-  ///            while walking through the APNG chunk stream -- this is placed
-  ///            in the `codec` field.
-  ///         2. Decoding stage: When a frame is requested for the first time,
-  ///            the decoded image is requested from the `SkCodec` and then
-  ///            (depending on the `frame_info`) composited with a previous
-  ///            frame. The final "canvas" frame is placed in the
-  ///            `composited_image` field. At this point, the `codec` is freed
-  ///            and the `composited_image` is handed to the caller for drawing.
-  struct APNGImage {
-    std::unique_ptr<SkCodec> codec;
-
-    // The rendered frame pixels.
-    std::vector<uint8_t> pixels;
-
-    // Absence of frame info is possible on the "default" image.
-    std::optional<ImageGenerator::FrameInfo> frame_info;
-
-    // X offset of this image when composited. Only applicable to frames.
-    unsigned int x_offset;
-
-    // X offset of this image when composited. Only applicable to frames.
-    unsigned int y_offset;
-  };
-
-  APNGImageGenerator(sk_sp<SkData>& data,
-                     SkImageInfo& image_info,
-                     APNGImage&& default_image,
-                     unsigned int frame_count,
-                     unsigned int play_count,
-                     const void* next_chunk_p,
-                     const std::vector<uint8_t>& header);
-
-  static bool IsValidChunkHeader(const void* buffer,
-                                 size_t size,
-                                 const ChunkHeader* chunk);
-
-  static const ChunkHeader* GetNextChunk(const void* buffer,
-                                         size_t size,
-                                         const ChunkHeader* current_chunk);
-
-  /// @brief  This is a utility template for casting a png buffer pointer to a
-  ///         chunk header. Its primary purpose is to statically insert runtime
-  ///         debug checks that detect invalid decoding behavior.
-  template <typename T>
-  static constexpr const T* CastChunkData(const ChunkHeader* chunk) {
-    if constexpr (std::is_same_v<T, ImageHeaderChunkData>) {
-      FML_DCHECK(chunk->get_type() == kImageHeaderChunkType);
-    } else if constexpr (std::is_same_v<T, AnimationControlChunkData>) {
-      FML_DCHECK(chunk->get_type() == kAnimationControlChunkType);
-    } else if constexpr (std::is_same_v<T, FrameControlChunkData>) {
-      FML_DCHECK(chunk->get_type() == kFrameControlChunkType);
-    } else {
-      static_assert(!sizeof(T), "Invalid chunk struct");
-    }
-
-    return reinterpret_cast<const T*>(reinterpret_cast<const uint8_t*>(chunk) +
-                                      sizeof(ChunkHeader));
-  }
-
-  static constexpr size_t GetChunkSize(const ChunkHeader* chunk) {
-    return sizeof(ChunkHeader) + chunk->get_data_length() + kChunkCrcSize;
-  }
-
-  static constexpr bool IsChunkCopySafe(const ChunkHeader* chunk) {
-    // The safe-to-copy bit is the 5th bit of the chunk name's 4th byte. This is
-    // the same as checking that the 4th byte is lowercase.
-    return (chunk->get_type() & 0x20) != 0;
-  }
-
-  /// @brief  Extract a header that's safe to use for both the "default" image
-  ///         and individual PNG frames. Strip the animation control chunk.
-  static std::pair<std::optional<std::vector<uint8_t>>, const void*>
-  ExtractHeader(const void* buffer_p, size_t buffer_size);
-
-  /// @brief  Takes a chunk pointer to a chunk and demuxes/interprets the next
-  ///         image in the APNG sequence. It also provides the next `chunk_p`
-  ///         to use.
-  /// @see    `APNGImage`
-  static std::pair<std::optional<APNGImage>, const void*> DemuxNextImage(
-      const void* buffer_p,
-      size_t buffer_size,
-      const std::vector<uint8_t>& header,
-      const void* chunk_p);
-
-  bool DemuxNextImageInternal();
-
-  bool DemuxToImageIndex(unsigned int image_index);
-
-  bool RenderDefaultImage(const SkImageInfo& info,
-                          void* pixels,
-                          size_t row_bytes);
-
-  FML_DISALLOW_COPY_ASSIGN_AND_MOVE(APNGImageGenerator);
-  sk_sp<SkData> data_;
-  SkImageInfo image_info_;
-  unsigned int frame_count_;
-  unsigned int play_count_;
-
-  // The first image is always the default image, which may or may not be a
-  // frame. All subsequent images are guaranteed to have frame data.
-  std::vector<APNGImage> images_;
-
-  unsigned int first_frame_index_;
-
-  const void* next_chunk_p_;
-  std::vector<uint8_t> header_;
-};
-
-}  // namespace flutter
diff --git a/lib/ui/painting/image_generator_registry.cc b/lib/ui/painting/image_generator_registry.cc
index ca5d83a..4460182 100644
--- a/lib/ui/painting/image_generator_registry.cc
+++ b/lib/ui/painting/image_generator_registry.cc
@@ -14,19 +14,11 @@
 #include "third_party/skia/include/ports/SkImageGeneratorWIC.h"
 #endif
 
-#include "image_generator_apng.h"
-
 namespace flutter {
 
 ImageGeneratorRegistry::ImageGeneratorRegistry() : weak_factory_(this) {
   AddFactory(
       [](sk_sp<SkData> buffer) {
-        return APNGImageGenerator::MakeFromData(std::move(buffer));
-      },
-      0);
-
-  AddFactory(
-      [](sk_sp<SkData> buffer) {
         return BuiltinSkiaCodecImageGenerator::MakeFromData(std::move(buffer));
       },
       0);
diff --git a/lib/ui/painting/image_generator_registry_unittests.cc b/lib/ui/painting/image_generator_registry_unittests.cc
index 54eb453..3051e42 100644
--- a/lib/ui/painting/image_generator_registry_unittests.cc
+++ b/lib/ui/painting/image_generator_registry_unittests.cc
@@ -62,7 +62,7 @@
 
   unsigned int GetPlayCount() const { return 1; }
 
-  const ImageGenerator::FrameInfo GetFrameInfo(unsigned int frame_index) {
+  const ImageGenerator::FrameInfo GetFrameInfo(unsigned int frame_index) const {
     return {std::nullopt, 0, SkCodecAnimation::DisposalMethod::kKeep};
   }
 
diff --git a/lib/ui/painting/multi_frame_codec.cc b/lib/ui/painting/multi_frame_codec.cc
index a380656..76af13b 100644
--- a/lib/ui/painting/multi_frame_codec.cc
+++ b/lib/ui/painting/multi_frame_codec.cc
@@ -107,30 +107,24 @@
   std::optional<unsigned int> prior_frame_index = std::nullopt;
 
   if (requiredFrameIndex != SkCodec::kNoFrame) {
-    // We currently assume that frames can only ever depend on the immediately
-    // previous frame, if any. This means that
-    // `DisposalMethod::kRestorePrevious` is not supported.
     if (lastRequiredFrame_ == nullptr) {
-      FML_DLOG(INFO)
-          << "Frame " << nextFrameIndex_ << " depends on frame "
-          << requiredFrameIndex
-          << " and no required frames are cached. Using blank slate instead.";
+      FML_LOG(ERROR) << "Frame " << nextFrameIndex_ << " depends on frame "
+                     << requiredFrameIndex
+                     << " and no required frames are cached.";
+      return nullptr;
     } else if (lastRequiredFrameIndex_ != requiredFrameIndex) {
       FML_DLOG(INFO) << "Required frame " << requiredFrameIndex
-                     << " is not cached. Using blank slate instead.";
-    } else {
-      // Copy the previous frame's output buffer into the current frame as the
-      // starting point.
-      if (lastRequiredFrame_->getPixels() &&
-          CopyToBitmap(&bitmap, lastRequiredFrame_->colorType(),
-                       *lastRequiredFrame_)) {
-        prior_frame_index = requiredFrameIndex;
-      }
+                     << " is not cached. Using " << lastRequiredFrameIndex_
+                     << " instead";
+    }
+
+    if (lastRequiredFrame_->getPixels() &&
+        CopyToBitmap(&bitmap, lastRequiredFrame_->colorType(),
+                     *lastRequiredFrame_)) {
+      prior_frame_index = requiredFrameIndex;
     }
   }
 
-  // Write the new frame to the output buffer. The bitmap pixels as supplied
-  // are already set in accordance with the previous frame's disposal policy.
   if (!generator_->GetPixels(info, bitmap.getPixels(), bitmap.rowBytes(),
                              nextFrameIndex_, requiredFrameIndex)) {
     FML_LOG(ERROR) << "Could not getPixels for frame " << nextFrameIndex_;
diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc
index 0bd2d9e..6f3418f 100644
--- a/shell/common/shell_unittests.cc
+++ b/shell/common/shell_unittests.cc
@@ -2249,7 +2249,7 @@
 
   unsigned int GetPlayCount() const { return 1; }
 
-  const ImageGenerator::FrameInfo GetFrameInfo(unsigned int frame_index) {
+  const ImageGenerator::FrameInfo GetFrameInfo(unsigned int frame_index) const {
     return {std::nullopt, 0, SkCodecAnimation::DisposalMethod::kKeep};
   }
 
diff --git a/shell/platform/android/android_image_generator.cc b/shell/platform/android/android_image_generator.cc
index 9ba4c05..714eef9 100644
--- a/shell/platform/android/android_image_generator.cc
+++ b/shell/platform/android/android_image_generator.cc
@@ -38,7 +38,7 @@
 }
 
 const ImageGenerator::FrameInfo AndroidImageGenerator::GetFrameInfo(
-    unsigned int frame_index) {
+    unsigned int frame_index) const {
   return {.required_frame = std::nullopt,
           .duration = 0,
           .disposal_method = SkCodecAnimation::DisposalMethod::kKeep};
diff --git a/shell/platform/android/android_image_generator.h b/shell/platform/android/android_image_generator.h
index 53cbadd..6d13c94 100644
--- a/shell/platform/android/android_image_generator.h
+++ b/shell/platform/android/android_image_generator.h
@@ -32,7 +32,7 @@
 
   // |ImageGenerator|
   const ImageGenerator::FrameInfo GetFrameInfo(
-      unsigned int frame_index) override;
+      unsigned int frame_index) const override;
 
   // |ImageGenerator|
   SkISize GetScaledDimensions(float desired_scale) override;