[go: nahoru, domu]

cc: Support animated images in PaintFilters.

Since we don't analyze or decode images in PaintFilters in cc, they
completely skip our image decode and animation stack. This change
ensures that we analyze images in these cases to track their
invalidation rects and register them with the animation system, and
creates a snaphot of the filter, if it has an animated image, during
raster. This ensures we use the correct frame for this image.

Since its not performant to re-create the filter graph for every
raster, the change is restricted to filters with animated images. In
addition, since its not easy to figure out the final scale for these
images, we decode them at the original size.

This also (hopefully) ties the last loose end where an image in a
paint recording could be skipped in cc.

R=chrishtr@chromium.org, enne@chromium.org

Bug: 821031
Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel;master.tryserver.chromium.android:android_optional_gpu_tests_rel
Change-Id: I4a0ecb1c6dff8b816861ffbcf68bdd2cd5aa5973
Reviewed-on: https://chromium-review.googlesource.com/965442
Reviewed-by: enne <enne@chromium.org>
Commit-Queue: Khushal <khushalsagar@chromium.org>
Cr-Commit-Position: refs/heads/master@{#544614}
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 6cc7ef6c..ed0afe6f 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -616,6 +616,7 @@
     "paint/display_item_list_unittest.cc",
     "paint/filter_operations_unittest.cc",
     "paint/oop_pixeltest.cc",
+    "paint/paint_filter_unittest.cc",
     "paint/paint_image_unittest.cc",
     "paint/paint_op_buffer_unittest.cc",
     "paint/paint_op_helper_unittest.cc",
diff --git a/cc/paint/BUILD.gn b/cc/paint/BUILD.gn
index 7d72ee55..535acba 100644
--- a/cc/paint/BUILD.gn
+++ b/cc/paint/BUILD.gn
@@ -24,6 +24,7 @@
     "filter_operation.h",
     "filter_operations.cc",
     "filter_operations.h",
+    "image_analysis_state.h",
     "image_animation_count.h",
     "image_id.h",
     "image_provider.cc",
diff --git a/cc/paint/decode_stashing_image_provider.cc b/cc/paint/decode_stashing_image_provider.cc
index e995f51..f8b4f94 100644
--- a/cc/paint/decode_stashing_image_provider.cc
+++ b/cc/paint/decode_stashing_image_provider.cc
@@ -15,8 +15,8 @@
 ImageProvider::ScopedDecodedDrawImage
 DecodeStashingImageProvider::GetDecodedDrawImage(const DrawImage& draw_image) {
   auto decode = source_provider_->GetDecodedDrawImage(draw_image);
-  if (!decode)
-    return ScopedDecodedDrawImage();
+  if (!decode.needs_unlock())
+    return decode;
 
   // No need to add any destruction callback to the returned image. The images
   // decoded here match the lifetime of this provider.
diff --git a/cc/paint/discardable_image_map.cc b/cc/paint/discardable_image_map.cc
index e963a14..e27659a 100644
--- a/cc/paint/discardable_image_map.cc
+++ b/cc/paint/discardable_image_map.cc
@@ -14,6 +14,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/trace_event/trace_event.h"
+#include "cc/paint/paint_filter.h"
 #include "cc/paint/paint_op_buffer.h"
 #include "third_party/skia/include/utils/SkNoDrawCanvas.h"
 #include "ui/gfx/geometry/rect_conversions.h"
@@ -124,6 +125,26 @@
   }
 
  private:
+  class ImageGatheringProvider : public ImageProvider {
+   public:
+    ImageGatheringProvider(DiscardableImageGenerator* generator,
+                           const gfx::Rect& op_rect)
+        : generator_(generator), op_rect_(op_rect) {}
+    ~ImageGatheringProvider() override = default;
+
+    ScopedDecodedDrawImage GetDecodedDrawImage(
+        const DrawImage& draw_image) override {
+      generator_->AddImage(draw_image.paint_image(),
+                           SkRect::Make(draw_image.src_rect()), op_rect_,
+                           SkMatrix::I(), draw_image.filter_quality());
+      return ScopedDecodedDrawImage();
+    }
+
+   private:
+    DiscardableImageGenerator* generator_;
+    gfx::Rect op_rect_;
+  };
+
   // Adds discardable images from |buffer| to the set of images tracked by
   // this generator. If |buffer| is being used in a DrawOp that requires
   // rasterization of the buffer as a pre-processing step for execution of the
@@ -144,12 +165,13 @@
     // TODO(khushalsagar): Optimize out save/restore blocks if there are no
     // images in the draw ops between them.
     for (auto* op : PaintOpBuffer::Iterator(buffer)) {
-      if (!op->IsDrawOp()) {
+      // We need to play non-draw ops on the SkCanvas since they can affect the
+      // transform/clip state.
+      if (!op->IsDrawOp())
         op->Raster(canvas, params);
+
+      if (!PaintOp::OpHasDiscardableImages(op))
         continue;
-      } else if (!PaintOp::OpHasDiscardableImages(op)) {
-        continue;
-      }
 
       gfx::Rect op_rect;
       base::Optional<gfx::Rect> local_op_rect;
@@ -202,9 +224,10 @@
 
     gfx::Rect transformed_rect;
     SkRect op_rect;
-    if (!PaintOp::GetBounds(op, &op_rect)) {
+    if (!op->IsDrawOp() || !PaintOp::GetBounds(op, &op_rect)) {
       // If we can't provide a conservative bounding rect for the op, assume it
       // covers the complete current clip.
+      // TODO(khushalsagar): See if we can do something better for non-draw ops.
       transformed_rect = gfx::ToEnclosingRect(gfx::SkRectToRectF(clip_rect));
     } else {
       const PaintFlags* flags =
@@ -247,34 +270,48 @@
   void AddImageFromFlags(const gfx::Rect& op_rect,
                          const PaintFlags& flags,
                          const SkMatrix& ctm) {
-    if (!flags.getShader())
+    AddImageFromShader(op_rect, flags.getShader(), ctm,
+                       flags.getFilterQuality());
+    AddImageFromFilter(op_rect, flags.getImageFilter().get());
+  }
+
+  void AddImageFromShader(const gfx::Rect& op_rect,
+                          const PaintShader* shader,
+                          const SkMatrix& ctm,
+                          SkFilterQuality filter_quality) {
+    if (!shader || !shader->has_discardable_images())
       return;
 
-    if (flags.getShader()->shader_type() == PaintShader::Type::kImage) {
-      const PaintImage& paint_image = flags.getShader()->paint_image();
+    if (shader->shader_type() == PaintShader::Type::kImage) {
+      const PaintImage& paint_image = shader->paint_image();
       SkMatrix matrix = ctm;
-      matrix.postConcat(flags.getShader()->GetLocalMatrix());
+      matrix.postConcat(shader->GetLocalMatrix());
       AddImage(paint_image,
                SkRect::MakeWH(paint_image.width(), paint_image.height()),
-               op_rect, matrix, flags.getFilterQuality());
-    } else if (flags.getShader()->shader_type() ==
-                   PaintShader::Type::kPaintRecord &&
-               flags.getShader()->paint_record()->HasDiscardableImages()) {
+               op_rect, matrix, filter_quality);
+      return;
+    }
+
+    if (shader->shader_type() == PaintShader::Type::kPaintRecord) {
+      // For record backed shaders, only analyze them if they have animated
+      // images.
+      if (shader->image_analysis_state() ==
+          ImageAnalysisState::kNoAnimatedImages) {
+        return;
+      }
+
       SkRect scaled_tile_rect;
-      if (!flags.getShader()->GetRasterizationTileRect(ctm,
-                                                       &scaled_tile_rect)) {
+      if (!shader->GetRasterizationTileRect(ctm, &scaled_tile_rect)) {
         return;
       }
 
       PaintTrackingCanvas canvas(scaled_tile_rect.width(),
                                  scaled_tile_rect.height());
-      canvas.setMatrix(SkMatrix::MakeRectToRect(flags.getShader()->tile(),
-                                                scaled_tile_rect,
-                                                SkMatrix::kFill_ScaleToFit));
-      base::AutoReset<bool> auto_reset(&iterating_record_shaders_, true);
+      canvas.setMatrix(SkMatrix::MakeRectToRect(
+          shader->tile(), scaled_tile_rect, SkMatrix::kFill_ScaleToFit));
+      base::AutoReset<bool> auto_reset(&only_gather_animated_images_, true);
       size_t prev_image_set_size = image_set_.size();
-      GatherDiscardableImages(flags.getShader()->paint_record().get(), &op_rect,
-                              &canvas);
+      GatherDiscardableImages(shader->paint_record().get(), &op_rect, &canvas);
 
       // We only track animated images for PaintShaders. If we added any entry
       // to the |image_set_|, this shader any has animated images.
@@ -282,11 +319,31 @@
       // PaintShader here since the analysis is done on the main thread, before
       // the PaintOpBuffer is used for rasterization.
       DCHECK_GE(image_set_.size(), prev_image_set_size);
-      if (image_set_.size() > prev_image_set_size)
-        const_cast<PaintShader*>(flags.getShader())->set_has_animated_images();
+      const bool has_animated_images = image_set_.size() > prev_image_set_size;
+      const_cast<PaintShader*>(shader)->set_has_animated_images(
+          has_animated_images);
     }
   }
 
+  void AddImageFromFilter(const gfx::Rect& op_rect, const PaintFilter* filter) {
+    // Only analyze filters if they have animated images.
+    if (!filter || !filter->has_discardable_images() ||
+        filter->image_analysis_state() ==
+            ImageAnalysisState::kNoAnimatedImages) {
+      return;
+    }
+
+    base::AutoReset<bool> auto_reset(&only_gather_animated_images_, true);
+    size_t prev_image_set_size = image_set_.size();
+    ImageGatheringProvider image_provider(this, op_rect);
+    filter->SnapshotWithImages(&image_provider);
+
+    DCHECK_GE(image_set_.size(), prev_image_set_size);
+    const bool has_animated_images = image_set_.size() > prev_image_set_size;
+    const_cast<PaintFilter*>(filter)->set_has_animated_images(
+        has_animated_images);
+  }
+
   void AddImage(PaintImage paint_image,
                 const SkRect& src_rect,
                 const gfx::Rect& image_rect,
@@ -335,7 +392,8 @@
     // are animated. We defer decoding of images in record shaders to skia, but
     // we still need to track animated images to invalidate and advance the
     // animation in cc.
-    bool add_image = !iterating_record_shaders_ || paint_image.ShouldAnimate();
+    bool add_image =
+        !only_gather_animated_images_ || paint_image.ShouldAnimate();
     if (add_image) {
       image_set_.emplace_back(
           DrawImage(std::move(paint_image), src_irect, filter_quality, matrix),
@@ -348,7 +406,7 @@
   std::vector<DiscardableImageMap::AnimatedImageMetadata>
       animated_images_metadata_;
   base::flat_map<PaintImage::Id, PaintImage::DecodingMode> decoding_mode_map_;
-  bool iterating_record_shaders_ = false;
+  bool only_gather_animated_images_ = false;
 
   // Statistics about the number of images and pixels that will require color
   // conversion if the target color space is not sRGB.
diff --git a/cc/paint/discardable_image_map_unittest.cc b/cc/paint/discardable_image_map_unittest.cc
index 72906af..72d608b 100644
--- a/cc/paint/discardable_image_map_unittest.cc
+++ b/cc/paint/discardable_image_map_unittest.cc
@@ -756,9 +756,11 @@
   display_list->EndPaintOfUnpaired(visible_rect);
   display_list->Finalize();
 
-  EXPECT_FALSE(flags.getShader()->has_animated_images());
+  EXPECT_EQ(flags.getShader()->image_analysis_state(),
+            ImageAnalysisState::kNoAnalysis);
   display_list->GenerateDiscardableImagesMetadata();
-  EXPECT_TRUE(flags.getShader()->has_animated_images());
+  EXPECT_EQ(flags.getShader()->image_analysis_state(),
+            ImageAnalysisState::kAnimatedImages);
   const auto& image_map = display_list->discardable_image_map();
 
   // The image rect is set to the rect for the DrawRectOp, and only animated
@@ -776,6 +778,75 @@
   EXPECT_EQ(SkSize::Make(4.f, 4.f), draw_images[0].scale);
 }
 
+TEST_F(DiscardableImageMapTest, CapturesImagesInPaintFilters) {
+  // Create the record to use in the filter.
+  auto filter_record = sk_make_sp<PaintOpBuffer>();
+
+  PaintImage static_image = CreateDiscardablePaintImage(gfx::Size(100, 100));
+  filter_record->push<DrawImageOp>(static_image, 0.f, 0.f, nullptr);
+
+  std::vector<FrameMetadata> frames = {
+      FrameMetadata(true, base::TimeDelta::FromMilliseconds(1)),
+      FrameMetadata(true, base::TimeDelta::FromMilliseconds(1))};
+  PaintImage animated_image = CreateAnimatedImage(gfx::Size(100, 100), frames);
+  filter_record->push<DrawImageOp>(animated_image, 0.f, 0.f, nullptr);
+
+  gfx::Rect visible_rect(500, 500);
+  scoped_refptr<DisplayItemList> display_list = new DisplayItemList();
+  display_list->StartPaint();
+  PaintFlags flags;
+  flags.setImageFilter(sk_make_sp<RecordPaintFilter>(
+      filter_record, SkRect::MakeWH(100.f, 100.f)));
+  display_list->push<DrawRectOp>(SkRect::MakeWH(200, 200), flags);
+  display_list->EndPaintOfUnpaired(visible_rect);
+  display_list->Finalize();
+
+  EXPECT_EQ(flags.getImageFilter()->image_analysis_state(),
+            ImageAnalysisState::kNoAnalysis);
+  display_list->GenerateDiscardableImagesMetadata();
+  EXPECT_EQ(flags.getImageFilter()->image_analysis_state(),
+            ImageAnalysisState::kAnimatedImages);
+  const auto& image_map = display_list->discardable_image_map();
+
+  // The image rect is set to the rect for the DrawRectOp, and only animated
+  // images in a filter are tracked.
+  std::vector<PositionScaleDrawImage> draw_images =
+      GetDiscardableImagesInRect(image_map, visible_rect);
+  std::vector<gfx::Rect> inset_rects = InsetImageRects(draw_images);
+  ASSERT_EQ(draw_images.size(), 1u);
+  EXPECT_EQ(draw_images[0].image, animated_image);
+  // The position of the image is the position of the DrawRectOp that uses the
+  // filter.
+  EXPECT_EQ(gfx::Rect(200, 200), inset_rects[0]);
+  // Images in a filter are decoded at the original size.
+  EXPECT_EQ(SkSize::Make(1.f, 1.f), draw_images[0].scale);
+}
+
+TEST_F(DiscardableImageMapTest, CapturesImagesInSaveLayers) {
+  PaintFlags flags;
+  PaintImage image = CreateDiscardablePaintImage(gfx::Size(100, 100));
+  flags.setShader(PaintShader::MakeImage(image, SkShader::kClamp_TileMode,
+                                         SkShader::kClamp_TileMode, nullptr));
+
+  gfx::Rect visible_rect(500, 500);
+  scoped_refptr<DisplayItemList> display_list = new DisplayItemList();
+  display_list->StartPaint();
+  display_list->push<SaveLayerOp>(nullptr, &flags);
+  display_list->push<DrawColorOp>(SK_ColorBLUE, SkBlendMode::kSrc);
+  display_list->EndPaintOfUnpaired(visible_rect);
+  display_list->Finalize();
+
+  display_list->GenerateDiscardableImagesMetadata();
+  const auto& image_map = display_list->discardable_image_map();
+  std::vector<PositionScaleDrawImage> draw_images =
+      GetDiscardableImagesInRect(image_map, visible_rect);
+  std::vector<gfx::Rect> inset_rects = InsetImageRects(draw_images);
+  ASSERT_EQ(draw_images.size(), 1u);
+  EXPECT_EQ(draw_images[0].image, image);
+  EXPECT_EQ(gfx::Rect(500, 500), inset_rects[0]);
+  EXPECT_EQ(SkSize::Make(1.f, 1.f), draw_images[0].scale);
+}
+
 TEST_F(DiscardableImageMapTest, EmbeddedShaderWithAnimatedImages) {
   // Create the record with animated image to use in the shader.
   SkRect tile = SkRect::MakeWH(100, 100);
@@ -806,8 +877,10 @@
   display_list->EndPaintOfUnpaired(visible_rect);
   display_list->Finalize();
   display_list->GenerateDiscardableImagesMetadata();
-  EXPECT_TRUE(shader_with_image->has_animated_images());
-  EXPECT_TRUE(shader_with_shader_with_image->has_animated_images());
+  EXPECT_EQ(shader_with_image->image_analysis_state(),
+            ImageAnalysisState::kAnimatedImages);
+  EXPECT_EQ(shader_with_shader_with_image->image_analysis_state(),
+            ImageAnalysisState::kAnimatedImages);
 }
 
 TEST_F(DiscardableImageMapTest, DecodingModeHintsBasic) {
diff --git a/cc/paint/image_analysis_state.h b/cc/paint/image_analysis_state.h
new file mode 100644
index 0000000..ebf085dc
--- /dev/null
+++ b/cc/paint/image_analysis_state.h
@@ -0,0 +1,18 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CC_PAINT_IMAGE_ANALYSIS_STATE_H_
+#define CC_PAINT_IMAGE_ANALYSIS_STATE_H_
+
+namespace cc {
+
+enum class ImageAnalysisState {
+  kNoAnalysis,
+  kAnimatedImages,
+  kNoAnimatedImages,
+};
+
+}  // namespace cc
+
+#endif  // CC_PAINT_IMAGE_ANALYSIS_STATE_H_
diff --git a/cc/paint/paint_filter.cc b/cc/paint/paint_filter.cc
index 514fb3e..947973c 100644
--- a/cc/paint/paint_filter.cc
+++ b/cc/paint/paint_filter.cc
@@ -5,6 +5,7 @@
 #include "cc/paint/paint_filter.h"
 
 #include "cc/paint/filter_operations.h"
+#include "cc/paint/paint_image_builder.h"
 #include "cc/paint/paint_op_writer.h"
 #include "cc/paint/paint_record.h"
 #include "third_party/skia/include/core/SkColorFilter.h"
@@ -26,6 +27,8 @@
 
 namespace cc {
 namespace {
+const bool kHasNoDiscardableImages = false;
+
 bool AreFiltersEqual(const PaintFilter* one, const PaintFilter* two) {
   if (!one || !two)
     return !one && !two;
@@ -36,9 +39,31 @@
   return PaintOp::AreEqualEvenIfNaN(one, two);
 }
 
+bool HasDiscardableImages(const sk_sp<PaintFilter>& filter) {
+  return filter ? filter->has_discardable_images() : false;
+}
+
+bool HasDiscardableImages(const sk_sp<PaintFilter>* const filters, int count) {
+  for (int i = 0; i < count; ++i) {
+    if (filters[i] && filters[i]->has_discardable_images())
+      return true;
+  }
+  return false;
+}
+
+sk_sp<PaintFilter> Snapshot(const sk_sp<PaintFilter>& filter,
+                            ImageProvider* image_provider) {
+  if (!filter)
+    return nullptr;
+  return filter->SnapshotWithImages(image_provider);
+}
+
 }  // namespace
 
-PaintFilter::PaintFilter(Type type, const CropRect* crop_rect) : type_(type) {
+PaintFilter::PaintFilter(Type type,
+                         const CropRect* crop_rect,
+                         bool has_discardable_images)
+    : type_(type), has_discardable_images_(has_discardable_images) {
   if (crop_rect)
     crop_rect_.emplace(*crop_rect);
 }
@@ -120,6 +145,13 @@
   return total_size;
 }
 
+sk_sp<PaintFilter> PaintFilter::SnapshotWithImages(
+    ImageProvider* image_provider) const {
+  if (!has_discardable_images_)
+    return sk_ref_sp<PaintFilter>(this);
+  return SnapshotWithImagesInternal(image_provider);
+}
+
 bool PaintFilter::operator==(const PaintFilter& other) const {
   if (type_ != other.type_)
     return false;
@@ -211,7 +243,7 @@
     sk_sp<SkColorFilter> color_filter,
     sk_sp<PaintFilter> input,
     const CropRect* crop_rect)
-    : PaintFilter(kType, crop_rect),
+    : PaintFilter(kType, crop_rect, HasDiscardableImages(input)),
       color_filter_(std::move(color_filter)),
       input_(std::move(input)) {
   DCHECK(color_filter_);
@@ -229,6 +261,12 @@
   return total_size.ValueOrDefault(0u);
 }
 
+sk_sp<PaintFilter> ColorFilterPaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  return sk_make_sp<ColorFilterPaintFilter>(
+      color_filter_, Snapshot(input_, image_provider), crop_rect());
+}
+
 bool ColorFilterPaintFilter::operator==(
     const ColorFilterPaintFilter& other) const {
   return PaintOp::AreSkFlattenablesEqual(color_filter_.get(),
@@ -241,7 +279,7 @@
                                  TileMode tile_mode,
                                  sk_sp<PaintFilter> input,
                                  const CropRect* crop_rect)
-    : PaintFilter(kType, crop_rect),
+    : PaintFilter(kType, crop_rect, HasDiscardableImages(input)),
       sigma_x_(sigma_x),
       sigma_y_(sigma_y),
       tile_mode_(tile_mode),
@@ -260,6 +298,13 @@
   return total_size.ValueOrDefault(0u);
 }
 
+sk_sp<PaintFilter> BlurPaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  return sk_make_sp<BlurPaintFilter>(sigma_x_, sigma_y_, tile_mode_,
+                                     Snapshot(input_, image_provider),
+                                     crop_rect());
+}
+
 bool BlurPaintFilter::operator==(const BlurPaintFilter& other) const {
   return PaintOp::AreEqualEvenIfNaN(sigma_x_, other.sigma_x_) &&
          PaintOp::AreEqualEvenIfNaN(sigma_y_, other.sigma_y_) &&
@@ -275,7 +320,7 @@
                                              ShadowMode shadow_mode,
                                              sk_sp<PaintFilter> input,
                                              const CropRect* crop_rect)
-    : PaintFilter(kType, crop_rect),
+    : PaintFilter(kType, crop_rect, HasDiscardableImages(input)),
       dx_(dx),
       dy_(dy),
       sigma_x_(sigma_x),
@@ -298,6 +343,13 @@
   return total_size.ValueOrDefault(0u);
 }
 
+sk_sp<PaintFilter> DropShadowPaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  return sk_make_sp<DropShadowPaintFilter>(
+      dx_, dy_, sigma_x_, sigma_y_, color_, shadow_mode_,
+      Snapshot(input_, image_provider), crop_rect());
+}
+
 bool DropShadowPaintFilter::operator==(
     const DropShadowPaintFilter& other) const {
   return PaintOp::AreEqualEvenIfNaN(dx_, other.dx_) &&
@@ -312,7 +364,7 @@
                                            SkScalar inset,
                                            sk_sp<PaintFilter> input,
                                            const CropRect* crop_rect)
-    : PaintFilter(kType, crop_rect),
+    : PaintFilter(kType, crop_rect, HasDiscardableImages(input)),
       src_rect_(src_rect),
       inset_(inset),
       input_(std::move(input)) {
@@ -329,6 +381,12 @@
   return total_size.ValueOrDefault(0u);
 }
 
+sk_sp<PaintFilter> MagnifierPaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  return sk_make_sp<MagnifierPaintFilter>(
+      src_rect_, inset_, Snapshot(input_, image_provider), crop_rect());
+}
+
 bool MagnifierPaintFilter::operator==(const MagnifierPaintFilter& other) const {
   return PaintOp::AreSkRectsEqual(src_rect_, other.src_rect_) &&
          PaintOp::AreEqualEvenIfNaN(inset_, other.inset_) &&
@@ -337,7 +395,9 @@
 
 ComposePaintFilter::ComposePaintFilter(sk_sp<PaintFilter> outer,
                                        sk_sp<PaintFilter> inner)
-    : PaintFilter(Type::kCompose, nullptr),
+    : PaintFilter(Type::kCompose,
+                  nullptr,
+                  HasDiscardableImages(outer) || HasDiscardableImages(inner)),
       outer_(std::move(outer)),
       inner_(std::move(inner)) {
   cached_sk_filter_ = SkComposeImageFilter::Make(GetSkFilter(outer_.get()),
@@ -353,6 +413,12 @@
   return total_size.ValueOrDefault(0u);
 }
 
+sk_sp<PaintFilter> ComposePaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  return sk_make_sp<ComposePaintFilter>(Snapshot(outer_, image_provider),
+                                        Snapshot(inner_, image_provider));
+}
+
 bool ComposePaintFilter::operator==(const ComposePaintFilter& other) const {
   return AreFiltersEqual(outer_.get(), other.outer_.get()) &&
          AreFiltersEqual(inner_.get(), other.inner_.get());
@@ -363,7 +429,7 @@
                                                      SkScalar outer_max,
                                                      sk_sp<PaintFilter> input,
                                                      const CropRect* crop_rect)
-    : PaintFilter(kType, crop_rect),
+    : PaintFilter(kType, crop_rect, HasDiscardableImages(input)),
       region_(region),
       inner_min_(inner_min),
       outer_max_(outer_max),
@@ -383,6 +449,13 @@
   return total_size.ValueOrDefault(0u);
 }
 
+sk_sp<PaintFilter> AlphaThresholdPaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  return sk_make_sp<AlphaThresholdPaintFilter>(region_, inner_min_, outer_max_,
+                                               Snapshot(input_, image_provider),
+                                               crop_rect());
+}
+
 bool AlphaThresholdPaintFilter::operator==(
     const AlphaThresholdPaintFilter& other) const {
   return region_ == other.region_ &&
@@ -395,7 +468,10 @@
                                          sk_sp<PaintFilter> background,
                                          sk_sp<PaintFilter> foreground,
                                          const CropRect* crop_rect)
-    : PaintFilter(kType, crop_rect),
+    : PaintFilter(
+          kType,
+          crop_rect,
+          HasDiscardableImages(background) || HasDiscardableImages(foreground)),
       blend_mode_(blend_mode),
       background_(std::move(background)),
       foreground_(std::move(foreground)) {
@@ -414,6 +490,13 @@
   return total_size.ValueOrDefault(0u);
 }
 
+sk_sp<PaintFilter> XfermodePaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  return sk_make_sp<XfermodePaintFilter>(
+      blend_mode_, Snapshot(background_, image_provider),
+      Snapshot(foreground_, image_provider), crop_rect());
+}
+
 bool XfermodePaintFilter::operator==(const XfermodePaintFilter& other) const {
   return blend_mode_ == other.blend_mode_ &&
          AreFiltersEqual(background_.get(), other.background_.get()) &&
@@ -428,7 +511,10 @@
                                              sk_sp<PaintFilter> background,
                                              sk_sp<PaintFilter> foreground,
                                              const CropRect* crop_rect)
-    : PaintFilter(kType, crop_rect),
+    : PaintFilter(
+          kType,
+          crop_rect,
+          HasDiscardableImages(background) || HasDiscardableImages(foreground)),
       k1_(k1),
       k2_(k2),
       k3_(k3),
@@ -452,6 +538,14 @@
   return total_size.ValueOrDefault(0u);
 }
 
+sk_sp<PaintFilter> ArithmeticPaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  return sk_make_sp<ArithmeticPaintFilter>(
+      k1_, k2_, k3_, k4_, enforce_pm_color_,
+      Snapshot(background_, image_provider),
+      Snapshot(foreground_, image_provider), crop_rect());
+}
+
 bool ArithmeticPaintFilter::operator==(
     const ArithmeticPaintFilter& other) const {
   return PaintOp::AreEqualEvenIfNaN(k1_, other.k1_) &&
@@ -473,7 +567,7 @@
     bool convolve_alpha,
     sk_sp<PaintFilter> input,
     const CropRect* crop_rect)
-    : PaintFilter(kType, crop_rect),
+    : PaintFilter(kType, crop_rect, HasDiscardableImages(input)),
       kernel_size_(kernel_size),
       gain_(gain),
       bias_(bias),
@@ -503,6 +597,13 @@
   return total_size.ValueOrDefault(0u);
 }
 
+sk_sp<PaintFilter> MatrixConvolutionPaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  return sk_make_sp<MatrixConvolutionPaintFilter>(
+      kernel_size_, &kernel_[0], gain_, bias_, kernel_offset_, tile_mode_,
+      convolve_alpha_, Snapshot(input_, image_provider), crop_rect());
+}
+
 bool MatrixConvolutionPaintFilter::operator==(
     const MatrixConvolutionPaintFilter& other) const {
   return kernel_size_ == other.kernel_size_ &&
@@ -523,7 +624,10 @@
     sk_sp<PaintFilter> displacement,
     sk_sp<PaintFilter> color,
     const CropRect* crop_rect)
-    : PaintFilter(kType, crop_rect),
+    : PaintFilter(
+          kType,
+          crop_rect,
+          HasDiscardableImages(displacement) || HasDiscardableImages(color)),
       channel_x_(channel_x),
       channel_y_(channel_y),
       scale_(scale),
@@ -545,6 +649,13 @@
   return total_size.ValueOrDefault(0u);
 }
 
+sk_sp<PaintFilter> DisplacementMapEffectPaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  return sk_make_sp<DisplacementMapEffectPaintFilter>(
+      channel_x_, channel_y_, scale_, Snapshot(displacement_, image_provider),
+      Snapshot(color_, image_provider), crop_rect());
+}
+
 bool DisplacementMapEffectPaintFilter::operator==(
     const DisplacementMapEffectPaintFilter& other) const {
   return channel_x_ == other.channel_x_ && channel_y_ == other.channel_y_ &&
@@ -557,7 +668,7 @@
                                    const SkRect& src_rect,
                                    const SkRect& dst_rect,
                                    SkFilterQuality filter_quality)
-    : PaintFilter(kType, nullptr),
+    : PaintFilter(kType, nullptr, image.IsLazyGenerated()),
       image_(std::move(image)),
       src_rect_(src_rect),
       dst_rect_(dst_rect),
@@ -576,6 +687,26 @@
   return total_size.ValueOrDefault(0u);
 }
 
+sk_sp<PaintFilter> ImagePaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  DrawImage draw_image(image_, SkIRect::MakeWH(image_.width(), image_.height()),
+                       filter_quality_, SkMatrix::I());
+  auto scoped_decoded_image = image_provider->GetDecodedDrawImage(draw_image);
+  if (!scoped_decoded_image)
+    return nullptr;
+
+  auto decoded_sk_image = sk_ref_sp<SkImage>(
+      const_cast<SkImage*>(scoped_decoded_image.decoded_image().image().get()));
+  PaintImage decoded_paint_image =
+      PaintImageBuilder::WithDefault()
+          .set_id(image_.stable_id())
+          .set_image(decoded_sk_image, PaintImage::GetNextContentId())
+          .TakePaintImage();
+
+  return sk_make_sp<ImagePaintFilter>(std::move(decoded_paint_image), src_rect_,
+                                      dst_rect_, filter_quality_);
+}
+
 bool ImagePaintFilter::operator==(const ImagePaintFilter& other) const {
   return !!image_ == !!other.image_ &&
          PaintOp::AreSkRectsEqual(src_rect_, other.src_rect_) &&
@@ -585,11 +716,16 @@
 
 RecordPaintFilter::RecordPaintFilter(sk_sp<PaintRecord> record,
                                      const SkRect& record_bounds)
-    : PaintFilter(kType, nullptr),
+    : RecordPaintFilter(std::move(record), record_bounds, nullptr) {}
+
+RecordPaintFilter::RecordPaintFilter(sk_sp<PaintRecord> record,
+                                     const SkRect& record_bounds,
+                                     ImageProvider* image_provider)
+    : PaintFilter(kType, nullptr, record->HasDiscardableImages()),
       record_(std::move(record)),
       record_bounds_(record_bounds) {
-  cached_sk_filter_ =
-      SkPictureImageFilter::Make(ToSkPicture(record_, record_bounds_));
+  cached_sk_filter_ = SkPictureImageFilter::Make(
+      ToSkPicture(record_, record_bounds_, image_provider));
 }
 
 RecordPaintFilter::~RecordPaintFilter() = default;
@@ -601,21 +737,35 @@
   return total_size.ValueOrDefault(0u);
 }
 
+sk_sp<PaintFilter> RecordPaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  return sk_sp<RecordPaintFilter>(
+      new RecordPaintFilter(record_, record_bounds_, image_provider));
+}
+
 bool RecordPaintFilter::operator==(const RecordPaintFilter& other) const {
   return !!record_ == !!other.record_ &&
          PaintOp::AreSkRectsEqual(record_bounds_, other.record_bounds_);
 }
 
-MergePaintFilter::MergePaintFilter(sk_sp<PaintFilter>* const filters,
+MergePaintFilter::MergePaintFilter(const sk_sp<PaintFilter>* const filters,
                                    int count,
                                    const CropRect* crop_rect)
-    : PaintFilter(kType, crop_rect) {
+    : MergePaintFilter(filters, count, crop_rect, nullptr) {}
+
+MergePaintFilter::MergePaintFilter(const sk_sp<PaintFilter>* const filters,
+                                   int count,
+                                   const CropRect* crop_rect,
+                                   ImageProvider* image_provider)
+    : PaintFilter(kType, crop_rect, HasDiscardableImages(filters, count)) {
   std::vector<sk_sp<SkImageFilter>> sk_filters;
   sk_filters.reserve(count);
 
   for (int i = 0; i < count; ++i) {
-    inputs_->push_back(filters[i]);
-    sk_filters.push_back(GetSkFilter(filters[i].get()));
+    auto filter =
+        image_provider ? Snapshot(filters[i], image_provider) : filters[i];
+    inputs_->push_back(std::move(filter));
+    sk_filters.push_back(GetSkFilter(inputs_->back().get()));
   }
 
   cached_sk_filter_ = SkMergeImageFilter::Make(
@@ -633,6 +783,12 @@
   return total_size.ValueOrDefault(0u);
 }
 
+sk_sp<PaintFilter> MergePaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  return sk_sp<MergePaintFilter>(new MergePaintFilter(
+      &inputs_[0], inputs_->size(), crop_rect(), image_provider));
+}
+
 bool MergePaintFilter::operator==(const MergePaintFilter& other) const {
   if (inputs_->size() != other.inputs_->size())
     return false;
@@ -648,7 +804,7 @@
                                              int radius_y,
                                              sk_sp<PaintFilter> input,
                                              const CropRect* crop_rect)
-    : PaintFilter(kType, crop_rect),
+    : PaintFilter(kType, crop_rect, HasDiscardableImages(input)),
       morph_type_(morph_type),
       radius_x_(radius_x),
       radius_y_(radius_y),
@@ -675,6 +831,13 @@
   return total_size.ValueOrDefault(0u);
 }
 
+sk_sp<PaintFilter> MorphologyPaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  return sk_make_sp<MorphologyPaintFilter>(morph_type_, radius_x_, radius_y_,
+                                           Snapshot(input_, image_provider),
+                                           crop_rect());
+}
+
 bool MorphologyPaintFilter::operator==(
     const MorphologyPaintFilter& other) const {
   return morph_type_ == other.morph_type_ && radius_x_ == other.radius_x_ &&
@@ -686,7 +849,7 @@
                                      SkScalar dy,
                                      sk_sp<PaintFilter> input,
                                      const CropRect* crop_rect)
-    : PaintFilter(kType, crop_rect),
+    : PaintFilter(kType, crop_rect, HasDiscardableImages(input)),
       dx_(dx),
       dy_(dy),
       input_(std::move(input)) {
@@ -703,6 +866,12 @@
   return total_size.ValueOrDefault(0u);
 }
 
+sk_sp<PaintFilter> OffsetPaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  return sk_make_sp<OffsetPaintFilter>(
+      dx_, dy_, Snapshot(input_, image_provider), crop_rect());
+}
+
 bool OffsetPaintFilter::operator==(const OffsetPaintFilter& other) const {
   return PaintOp::AreEqualEvenIfNaN(dx_, other.dx_) &&
          PaintOp::AreEqualEvenIfNaN(dy_, other.dy_) &&
@@ -712,7 +881,7 @@
 TilePaintFilter::TilePaintFilter(const SkRect& src,
                                  const SkRect& dst,
                                  sk_sp<PaintFilter> input)
-    : PaintFilter(kType, nullptr),
+    : PaintFilter(kType, nullptr, HasDiscardableImages(input)),
       src_(src),
       dst_(dst),
       input_(std::move(input)) {
@@ -729,6 +898,12 @@
   return total_size.ValueOrDefault(0u);
 }
 
+sk_sp<PaintFilter> TilePaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  return sk_make_sp<TilePaintFilter>(src_, dst_,
+                                     Snapshot(input_, image_provider));
+}
+
 bool TilePaintFilter::operator==(const TilePaintFilter& other) const {
   return PaintOp::AreSkRectsEqual(src_, other.src_) &&
          PaintOp::AreSkRectsEqual(dst_, other.dst_) &&
@@ -742,7 +917,7 @@
                                              SkScalar seed,
                                              const SkISize* tile_size,
                                              const CropRect* crop_rect)
-    : PaintFilter(kType, crop_rect),
+    : PaintFilter(kType, crop_rect, kHasNoDiscardableImages),
       turbulence_type_(turbulence_type),
       base_frequency_x_(base_frequency_x),
       base_frequency_y_(base_frequency_y),
@@ -776,6 +951,13 @@
          sizeof(num_octaves_) + sizeof(seed_) + sizeof(tile_size_);
 }
 
+sk_sp<PaintFilter> TurbulencePaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  return sk_make_sp<TurbulencePaintFilter>(turbulence_type_, base_frequency_x_,
+                                           base_frequency_y_, num_octaves_,
+                                           seed_, &tile_size_, crop_rect());
+}
+
 bool TurbulencePaintFilter::operator==(
     const TurbulencePaintFilter& other) const {
   return turbulence_type_ == other.turbulence_type_ &&
@@ -790,8 +972,20 @@
 
 PaintFlagsPaintFilter::PaintFlagsPaintFilter(PaintFlags flags,
                                              const CropRect* crop_rect)
-    : PaintFilter(kType, crop_rect), flags_(std::move(flags)) {
-  cached_sk_filter_ = SkPaintImageFilter::Make(flags_.ToSkPaint(), crop_rect);
+    : PaintFlagsPaintFilter(std::move(flags), nullptr, crop_rect) {}
+
+PaintFlagsPaintFilter::PaintFlagsPaintFilter(PaintFlags flags,
+                                             ImageProvider* image_provider,
+                                             const CropRect* crop_rect)
+    : PaintFilter(kType, crop_rect, flags.HasDiscardableImages()),
+      flags_(std::move(flags)) {
+  if (image_provider) {
+    raster_flags_.emplace(&flags_, image_provider, SkMatrix::I(), 255u,
+                          true /* create_skia_shaders */);
+  }
+  cached_sk_filter_ = SkPaintImageFilter::Make(
+      raster_flags_ ? raster_flags_->flags()->ToSkPaint() : flags_.ToSkPaint(),
+      crop_rect);
 }
 
 PaintFlagsPaintFilter::~PaintFlagsPaintFilter() = default;
@@ -802,6 +996,12 @@
   return total_size.ValueOrDefault(0u);
 }
 
+sk_sp<PaintFilter> PaintFlagsPaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  return sk_sp<PaintFilter>(
+      new PaintFlagsPaintFilter(flags_, image_provider, crop_rect()));
+}
+
 bool PaintFlagsPaintFilter::operator==(
     const PaintFlagsPaintFilter& other) const {
   return flags_ == other.flags_;
@@ -810,7 +1010,7 @@
 MatrixPaintFilter::MatrixPaintFilter(const SkMatrix& matrix,
                                      SkFilterQuality filter_quality,
                                      sk_sp<PaintFilter> input)
-    : PaintFilter(Type::kMatrix, nullptr),
+    : PaintFilter(Type::kMatrix, nullptr, HasDiscardableImages(input)),
       matrix_(matrix),
       filter_quality_(filter_quality),
       input_(std::move(input)) {
@@ -827,6 +1027,12 @@
   return total_size.ValueOrDefault(0u);
 }
 
+sk_sp<PaintFilter> MatrixPaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  return sk_make_sp<MatrixPaintFilter>(matrix_, filter_quality_,
+                                       Snapshot(input_, image_provider));
+}
+
 bool MatrixPaintFilter::operator==(const MatrixPaintFilter& other) const {
   return PaintOp::AreSkMatricesEqual(matrix_, other.matrix_) &&
          filter_quality_ == other.filter_quality_ &&
@@ -842,7 +1048,7 @@
     SkScalar shininess,
     sk_sp<PaintFilter> input,
     const CropRect* crop_rect)
-    : PaintFilter(kType, crop_rect),
+    : PaintFilter(kType, crop_rect, HasDiscardableImages(input)),
       lighting_type_(lighting_type),
       direction_(direction),
       light_color_(light_color),
@@ -875,6 +1081,13 @@
   return total_size.ValueOrDefault(0u);
 }
 
+sk_sp<PaintFilter> LightingDistantPaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  return sk_make_sp<LightingDistantPaintFilter>(
+      lighting_type_, direction_, light_color_, surface_scale_, kconstant_,
+      shininess_, Snapshot(input_, image_provider), crop_rect());
+}
+
 bool LightingDistantPaintFilter::operator==(
     const LightingDistantPaintFilter& other) const {
   return lighting_type_ == other.lighting_type_ &&
@@ -894,7 +1107,7 @@
                                                    SkScalar shininess,
                                                    sk_sp<PaintFilter> input,
                                                    const CropRect* crop_rect)
-    : PaintFilter(kType, crop_rect),
+    : PaintFilter(kType, crop_rect, HasDiscardableImages(input)),
       lighting_type_(lighting_type),
       location_(location),
       light_color_(light_color),
@@ -927,6 +1140,13 @@
   return total_size.ValueOrDefault(0u);
 }
 
+sk_sp<PaintFilter> LightingPointPaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  return sk_make_sp<LightingPointPaintFilter>(
+      lighting_type_, location_, light_color_, surface_scale_, kconstant_,
+      shininess_, Snapshot(input_, image_provider), crop_rect());
+}
+
 bool LightingPointPaintFilter::operator==(
     const LightingPointPaintFilter& other) const {
   return lighting_type_ == other.lighting_type_ &&
@@ -949,7 +1169,7 @@
                                                  SkScalar shininess,
                                                  sk_sp<PaintFilter> input,
                                                  const CropRect* crop_rect)
-    : PaintFilter(kType, crop_rect),
+    : PaintFilter(kType, crop_rect, HasDiscardableImages(input)),
       lighting_type_(lighting_type),
       location_(location),
       target_(target),
@@ -987,6 +1207,14 @@
   return total_size.ValueOrDefault(0u);
 }
 
+sk_sp<PaintFilter> LightingSpotPaintFilter::SnapshotWithImagesInternal(
+    ImageProvider* image_provider) const {
+  return sk_make_sp<LightingSpotPaintFilter>(
+      lighting_type_, location_, target_, specular_exponent_, cutoff_angle_,
+      light_color_, surface_scale_, kconstant_, shininess_,
+      Snapshot(input_, image_provider), crop_rect());
+}
+
 bool LightingSpotPaintFilter::operator==(
     const LightingSpotPaintFilter& other) const {
   return lighting_type_ == other.lighting_type_ &&
diff --git a/cc/paint/paint_filter.h b/cc/paint/paint_filter.h
index 12e3a54..57897f5 100644
--- a/cc/paint/paint_filter.h
+++ b/cc/paint/paint_filter.h
@@ -12,6 +12,7 @@
 #include "cc/paint/paint_export.h"
 #include "cc/paint/paint_flags.h"
 #include "cc/paint/paint_image.h"
+#include "cc/paint/scoped_raster_flags.h"
 #include "third_party/skia/include/core/SkBlendMode.h"
 #include "third_party/skia/include/core/SkImageFilter.h"
 #include "third_party/skia/include/core/SkPoint3.h"
@@ -27,6 +28,7 @@
 }  // namespace viz
 
 namespace cc {
+class ImageProvider;
 
 class CC_PAINT_EXPORT PaintFilter : public SkRefCnt {
  public:
@@ -102,8 +104,23 @@
     return base::OptionalOrNullptr(crop_rect_);
   }
 
+  bool has_discardable_images() const { return has_discardable_images_; }
+  ImageAnalysisState image_analysis_state() const {
+    return image_analysis_state_;
+  }
+  void set_has_animated_images(bool has_animated_images) {
+    image_analysis_state_ = has_animated_images
+                                ? ImageAnalysisState::kAnimatedImages
+                                : ImageAnalysisState::kNoAnimatedImages;
+  }
+
   virtual size_t SerializedSize() const = 0;
 
+  // Returns a snaphot of the PaintFilter with images replaced using
+  // |image_provider|. Note that this may return the same filter if the filter
+  // has no images.
+  sk_sp<PaintFilter> SnapshotWithImages(ImageProvider* image_provider) const;
+
   // Note that this operation is potentially slow. It also only compares things
   // that are easy to compare. As an example, it doesn't compare equality of
   // images, rather only its existence. This is meant to be used only by tests
@@ -114,7 +131,9 @@
   bool operator!=(const PaintFilter& other) const { return !(*this == other); }
 
  protected:
-  PaintFilter(Type type, const CropRect* crop_rect);
+  PaintFilter(Type type,
+              const CropRect* crop_rect,
+              bool has_discardable_images);
 
   static sk_sp<SkImageFilter> GetSkFilter(const PaintFilter* paint_filter) {
     return paint_filter ? paint_filter->cached_sk_filter_ : nullptr;
@@ -124,6 +143,8 @@
   }
 
   size_t BaseSerializedSize() const;
+  virtual sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const = 0;
 
   // This should be created by each sub-class at construction time, to ensure
   // that subsequent access to the filter is thread-safe.
@@ -138,6 +159,9 @@
 
   const Type type_;
   base::Optional<CropRect> crop_rect_;
+  const bool has_discardable_images_;
+
+  ImageAnalysisState image_analysis_state_ = ImageAnalysisState::kNoAnalysis;
 
   DISALLOW_COPY_AND_ASSIGN(PaintFilter);
 };
@@ -156,6 +180,10 @@
   size_t SerializedSize() const override;
   bool operator==(const ColorFilterPaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
   sk_sp<SkColorFilter> color_filter_;
   sk_sp<PaintFilter> input_;
@@ -181,6 +209,10 @@
   size_t SerializedSize() const override;
   bool operator==(const BlurPaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
   SkScalar sigma_x_;
   SkScalar sigma_y_;
@@ -213,6 +245,10 @@
   size_t SerializedSize() const override;
   bool operator==(const DropShadowPaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
   SkScalar dx_;
   SkScalar dy_;
@@ -239,6 +275,10 @@
   size_t SerializedSize() const override;
   bool operator==(const MagnifierPaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
   SkRect src_rect_;
   SkScalar inset_;
@@ -257,6 +297,10 @@
   size_t SerializedSize() const override;
   bool operator==(const ComposePaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
   sk_sp<PaintFilter> outer_;
   sk_sp<PaintFilter> inner_;
@@ -280,6 +324,10 @@
   size_t SerializedSize() const override;
   bool operator==(const AlphaThresholdPaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
   SkRegion region_;
   SkScalar inner_min_;
@@ -303,6 +351,10 @@
   size_t SerializedSize() const override;
   bool operator==(const XfermodePaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
   SkBlendMode blend_mode_;
   sk_sp<PaintFilter> background_;
@@ -333,6 +385,10 @@
   size_t SerializedSize() const override;
   bool operator==(const ArithmeticPaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
   float k1_;
   float k2_;
@@ -370,6 +426,10 @@
   size_t SerializedSize() const override;
   bool operator==(const MatrixConvolutionPaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
   SkISize kernel_size_;
   base::StackVector<SkScalar, 3> kernel_;
@@ -403,6 +463,10 @@
   size_t SerializedSize() const override;
   bool operator==(const DisplacementMapEffectPaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
   ChannelSelectorType channel_x_;
   ChannelSelectorType channel_y_;
@@ -428,6 +492,10 @@
   size_t SerializedSize() const override;
   bool operator==(const ImagePaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
   PaintImage image_;
   SkRect src_rect_;
@@ -447,7 +515,15 @@
   size_t SerializedSize() const override;
   bool operator==(const RecordPaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
+  RecordPaintFilter(sk_sp<PaintRecord> record,
+                    const SkRect& record_bounds,
+                    ImageProvider* image_provider);
+
   sk_sp<PaintRecord> record_;
   SkRect record_bounds_;
 };
@@ -455,7 +531,7 @@
 class CC_PAINT_EXPORT MergePaintFilter final : public PaintFilter {
  public:
   static constexpr Type kType = Type::kMerge;
-  MergePaintFilter(sk_sp<PaintFilter>* const filters,
+  MergePaintFilter(const sk_sp<PaintFilter>* const filters,
                    int count,
                    const CropRect* crop_rect = nullptr);
   ~MergePaintFilter() override;
@@ -469,7 +545,15 @@
   size_t SerializedSize() const override;
   bool operator==(const MergePaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
+  MergePaintFilter(const sk_sp<PaintFilter>* const filters,
+                   int count,
+                   const CropRect* crop_rect,
+                   ImageProvider* image_provider);
   base::StackVector<sk_sp<PaintFilter>, 2> inputs_;
 };
 
@@ -492,6 +576,10 @@
   size_t SerializedSize() const override;
   bool operator==(const MorphologyPaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
   MorphType morph_type_;
   int radius_x_;
@@ -515,6 +603,10 @@
   size_t SerializedSize() const override;
   bool operator==(const OffsetPaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
   SkScalar dx_;
   SkScalar dy_;
@@ -536,6 +628,10 @@
   size_t SerializedSize() const override;
   bool operator==(const TilePaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
   SkRect src_;
   SkRect dst_;
@@ -569,6 +665,10 @@
   size_t SerializedSize() const override;
   bool operator==(const TurbulencePaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
   TurbulenceType turbulence_type_;
   SkScalar base_frequency_x_;
@@ -590,8 +690,17 @@
   size_t SerializedSize() const override;
   bool operator==(const PaintFlagsPaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
+  PaintFlagsPaintFilter(PaintFlags flags,
+                        ImageProvider* image_provider,
+                        const CropRect* crop_rect);
+
   PaintFlags flags_;
+  base::Optional<ScopedRasterFlags> raster_flags_;
 };
 
 class CC_PAINT_EXPORT MatrixPaintFilter final : public PaintFilter {
@@ -609,6 +718,10 @@
   size_t SerializedSize() const override;
   bool operator==(const MatrixPaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
   SkMatrix matrix_;
   SkFilterQuality filter_quality_;
@@ -642,6 +755,10 @@
   size_t SerializedSize() const override;
   bool operator==(const LightingDistantPaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
   LightingType lighting_type_;
   SkPoint3 direction_;
@@ -679,6 +796,10 @@
   size_t SerializedSize() const override;
   bool operator==(const LightingPointPaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
   LightingType lighting_type_;
   SkPoint3 location_;
@@ -722,6 +843,10 @@
   size_t SerializedSize() const override;
   bool operator==(const LightingSpotPaintFilter& other) const;
 
+ protected:
+  sk_sp<PaintFilter> SnapshotWithImagesInternal(
+      ImageProvider* image_provider) const override;
+
  private:
   LightingType lighting_type_;
   SkPoint3 location_;
diff --git a/cc/paint/paint_filter_unittest.cc b/cc/paint/paint_filter_unittest.cc
new file mode 100644
index 0000000..de1ea451
--- /dev/null
+++ b/cc/paint/paint_filter_unittest.cc
@@ -0,0 +1,197 @@
+// Copyright 2018 The Chromium 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 "cc/paint/paint_filter.h"
+
+#include "cc/paint/paint_op_buffer.h"
+#include "cc/test/skia_common.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/effects/SkLumaColorFilter.h"
+
+namespace cc {
+namespace {
+
+class MockImageProvider : public ImageProvider {
+ public:
+  MockImageProvider() = default;
+  ~MockImageProvider() override = default;
+
+  ScopedDecodedDrawImage GetDecodedDrawImage(
+      const DrawImage& draw_image) override {
+    image_count_++;
+    return ScopedDecodedDrawImage(DecodedDrawImage(
+        CreateBitmapImage(gfx::Size(10, 10)).GetSkImage(), SkSize::MakeEmpty(),
+        SkSize::Make(1.0f, 1.0f), draw_image.filter_quality(), true));
+  }
+  int image_count_ = 0;
+};
+
+sk_sp<PaintFilter> CreateTestFilter(PaintFilter::Type filter_type,
+                                    bool has_discardable_images) {
+  PaintImage image;
+  if (has_discardable_images)
+    image = CreateDiscardablePaintImage(gfx::Size(100, 100));
+  else
+    image = CreateBitmapImage(gfx::Size(100, 100));
+
+  auto image_filter = sk_make_sp<ImagePaintFilter>(
+      image, SkRect::MakeWH(100.f, 100.f), SkRect::MakeWH(100.f, 100.f),
+      kNone_SkFilterQuality);
+  auto record = sk_make_sp<PaintOpBuffer>();
+  record->push<DrawImageOp>(image, 0.f, 0.f, nullptr);
+  auto record_filter =
+      sk_make_sp<RecordPaintFilter>(record, SkRect::MakeWH(100.f, 100.f));
+
+  SkImageFilter::CropRect crop_rect(SkRect::MakeWH(100.f, 100.f));
+
+  switch (filter_type) {
+    case PaintFilter::Type::kNullFilter:
+      NOTREACHED();
+      return nullptr;
+    case PaintFilter::Type::kColorFilter:
+      return sk_make_sp<ColorFilterPaintFilter>(SkLumaColorFilter::Make(),
+                                                image_filter, &crop_rect);
+    case PaintFilter::Type::kBlur:
+      return sk_make_sp<BlurPaintFilter>(0.1f, 0.2f,
+                                         SkBlurImageFilter::kClamp_TileMode,
+                                         record_filter, &crop_rect);
+    case PaintFilter::Type::kDropShadow:
+      return sk_make_sp<DropShadowPaintFilter>(
+          0.1, 0.2f, 0.3f, 0.4f, SK_ColorWHITE,
+          SkDropShadowImageFilter::kDrawShadowOnly_ShadowMode, image_filter,
+          &crop_rect);
+    case PaintFilter::Type::kMagnifier:
+      return sk_make_sp<MagnifierPaintFilter>(SkRect::MakeWH(100.f, 100.f),
+                                              0.1f, record_filter, &crop_rect);
+    case PaintFilter::Type::kCompose:
+      return sk_make_sp<ComposePaintFilter>(image_filter, record_filter);
+    case PaintFilter::Type::kAlphaThreshold:
+      return sk_make_sp<AlphaThresholdPaintFilter>(
+          SkRegion(SkIRect::MakeWH(100, 100)), 0.1f, 0.2f, image_filter,
+          &crop_rect);
+    case PaintFilter::Type::kXfermode:
+      return sk_make_sp<XfermodePaintFilter>(SkBlendMode::kSrc, image_filter,
+                                             record_filter, &crop_rect);
+    case PaintFilter::Type::kArithmetic:
+      return sk_make_sp<ArithmeticPaintFilter>(0.1f, 0.2f, 0.3f, 0.4f, true,
+                                               image_filter, record_filter,
+                                               &crop_rect);
+    case PaintFilter::Type::kMatrixConvolution: {
+      SkScalar scalars[9] = {1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, 9.f};
+      return sk_make_sp<MatrixConvolutionPaintFilter>(
+          SkISize::Make(3, 3), scalars, 0.1f, 0.2f, SkIPoint::Make(2, 2),
+          SkMatrixConvolutionImageFilter::TileMode::kRepeat_TileMode, false,
+          image_filter, &crop_rect);
+    }
+    case PaintFilter::Type::kDisplacementMapEffect:
+      return sk_make_sp<DisplacementMapEffectPaintFilter>(
+          SkDisplacementMapEffect::ChannelSelectorType::kR_ChannelSelectorType,
+          SkDisplacementMapEffect::ChannelSelectorType::kR_ChannelSelectorType,
+          0.1f, image_filter, record_filter, &crop_rect);
+    case PaintFilter::Type::kImage:
+      return image_filter;
+    case PaintFilter::Type::kPaintRecord:
+      return record_filter;
+    case PaintFilter::Type::kMerge: {
+      sk_sp<PaintFilter> filters[2] = {image_filter, record_filter};
+      return sk_make_sp<MergePaintFilter>(filters, 2, &crop_rect);
+    }
+    case PaintFilter::Type::kMorphology:
+      return sk_make_sp<MorphologyPaintFilter>(
+          MorphologyPaintFilter::MorphType::kDilate, 1, 2, image_filter,
+          &crop_rect);
+    case PaintFilter::Type::kOffset:
+      return sk_make_sp<OffsetPaintFilter>(0.1f, 0.2f, image_filter,
+                                           &crop_rect);
+    case PaintFilter::Type::kTile:
+      return sk_make_sp<TilePaintFilter>(SkRect::MakeWH(100.f, 100.f),
+                                         SkRect::MakeWH(200.f, 200.f),
+                                         record_filter);
+    case PaintFilter::Type::kTurbulence:
+      return sk_make_sp<TurbulencePaintFilter>(
+          TurbulencePaintFilter::TurbulenceType::kTurbulence, 0.1f, 0.2f, 2,
+          0.3f, nullptr, &crop_rect);
+    case PaintFilter::Type::kPaintFlags: {
+      PaintFlags flags;
+      flags.setShader(PaintShader::MakeImage(image, SkShader::kClamp_TileMode,
+                                             SkShader::kClamp_TileMode,
+                                             nullptr));
+      return sk_make_sp<PaintFlagsPaintFilter>(flags, &crop_rect);
+    }
+    case PaintFilter::Type::kMatrix:
+      return sk_make_sp<MatrixPaintFilter>(SkMatrix::I(), kNone_SkFilterQuality,
+                                           record_filter);
+    case PaintFilter::Type::kLightingDistant:
+      return sk_make_sp<LightingDistantPaintFilter>(
+          PaintFilter::LightingType::kDiffuse, SkPoint3::Make(0.1f, 0.2f, 0.3f),
+          SK_ColorWHITE, 0.1f, 0.2f, 0.3f, image_filter, &crop_rect);
+    case PaintFilter::Type::kLightingPoint:
+      return sk_make_sp<LightingPointPaintFilter>(
+          PaintFilter::LightingType::kDiffuse, SkPoint3::Make(0.1f, 0.2f, 0.3f),
+          SK_ColorWHITE, 0.1f, 0.2f, 0.3f, record_filter, &crop_rect);
+    case PaintFilter::Type::kLightingSpot:
+      return sk_make_sp<LightingSpotPaintFilter>(
+          PaintFilter::LightingType::kDiffuse, SkPoint3::Make(0.1f, 0.2f, 0.3f),
+          SkPoint3::Make(0.4f, 0.5f, 0.6f), 0.1f, 0.2f, SK_ColorWHITE, 0.4f,
+          0.5f, 0.6f, image_filter, &crop_rect);
+  }
+  NOTREACHED();
+  return nullptr;
+}
+
+}  // namespace
+
+class PaintFilterTest : public ::testing::TestWithParam<uint8_t> {
+ public:
+  PaintFilter::Type GetParamType() const {
+    return static_cast<PaintFilter::Type>(GetParam());
+  }
+};
+
+INSTANTIATE_TEST_CASE_P(
+    P,
+    PaintFilterTest,
+    ::testing::Range(static_cast<uint8_t>(PaintFilter::Type::kColorFilter),
+                     static_cast<uint8_t>(PaintFilter::Type::kMaxFilterType)));
+
+TEST_P(PaintFilterTest, HasDiscardableImagesYes) {
+  // TurbulencePaintFilter can not embed images.
+  if (GetParamType() == PaintFilter::Type::kTurbulence)
+    return;
+
+  EXPECT_TRUE(CreateTestFilter(GetParamType(), true)->has_discardable_images())
+      << PaintFilter::TypeToString(GetParamType());
+}
+
+TEST_P(PaintFilterTest, HasDiscardableImagesNo) {
+  EXPECT_FALSE(
+      CreateTestFilter(GetParamType(), false)->has_discardable_images())
+      << PaintFilter::TypeToString(GetParamType());
+}
+
+TEST_P(PaintFilterTest, SnapshotWithImages) {
+  auto filter = CreateTestFilter(GetParamType(), true);
+  MockImageProvider image_provider;
+  auto snapshot_filter = filter->SnapshotWithImages(&image_provider);
+  if (GetParamType() != PaintFilter::Type::kTurbulence) {
+    // TurbulencePaintFilter can not embed images.
+    EXPECT_GT(image_provider.image_count_, 0)
+        << PaintFilter::TypeToString(GetParamType());
+  }
+  EXPECT_EQ(*filter, *snapshot_filter)
+      << PaintFilter::TypeToString(GetParamType());
+}
+
+TEST(PaintFilterTest, ImageAnalysisState) {
+  auto filter = CreateTestFilter(PaintFilter::Type::kImage, true);
+  EXPECT_EQ(filter->image_analysis_state(), ImageAnalysisState::kNoAnalysis);
+  filter->set_has_animated_images(true);
+  EXPECT_EQ(filter->image_analysis_state(),
+            ImageAnalysisState::kAnimatedImages);
+  filter->set_has_animated_images(false);
+  EXPECT_EQ(filter->image_analysis_state(),
+            ImageAnalysisState::kNoAnimatedImages);
+}
+
+}  // namespace cc
diff --git a/cc/paint/paint_flags.cc b/cc/paint/paint_flags.cc
index 3a75c09f..e194750 100644
--- a/cc/paint/paint_flags.cc
+++ b/cc/paint/paint_flags.cc
@@ -199,13 +199,8 @@
 }
 
 bool PaintFlags::HasDiscardableImages() const {
-  if (!shader_)
-    return false;
-  else if (shader_->shader_type() == PaintShader::Type::kImage)
-    return shader_->paint_image().IsLazyGenerated();
-  else if (shader_->shader_type() == PaintShader::Type::kPaintRecord)
-    return shader_->paint_record()->HasDiscardableImages();
-  return false;
+  return (shader_ && shader_->has_discardable_images()) ||
+         (image_filter_ && image_filter_->has_discardable_images());
 }
 
 size_t PaintFlags::GetSerializedSize() const {
diff --git a/cc/paint/paint_op_buffer.cc b/cc/paint/paint_op_buffer.cc
index 3ec40ab..e5ca5c1 100644
--- a/cc/paint/paint_op_buffer.cc
+++ b/cc/paint/paint_op_buffer.cc
@@ -1893,7 +1893,8 @@
 
 // static
 bool PaintOp::QuickRejectDraw(const PaintOp* op, const SkCanvas* canvas) {
-  DCHECK(op->IsDrawOp());
+  if (!op->IsDrawOp())
+    return false;
 
   SkRect rect;
   if (!PaintOp::GetBounds(op, &rect))
@@ -1937,7 +1938,7 @@
 }
 
 bool PaintOpWithFlags::HasDiscardableImagesFromFlags() const {
-  return IsDrawOp() && flags.HasDiscardableImages();
+  return flags.HasDiscardableImages();
 }
 
 void PaintOpWithFlags::RasterWithFlags(SkCanvas* canvas,
diff --git a/cc/paint/paint_op_buffer_unittest.cc b/cc/paint/paint_op_buffer_unittest.cc
index 520b354..7c6ce78 100644
--- a/cc/paint/paint_op_buffer_unittest.cc
+++ b/cc/paint/paint_op_buffer_unittest.cc
@@ -3101,7 +3101,7 @@
       record_buffer, SkRect::MakeWH(10.f, 10.f),
       SkShader::TileMode::kRepeat_TileMode,
       SkShader::TileMode::kRepeat_TileMode, nullptr);
-  shader->set_has_animated_images();
+  shader->set_has_animated_images(true);
   auto buffer = sk_make_sp<PaintOpBuffer>();
   buffer->push<ScaleOp>(0.5f, 0.8f);
   PaintFlags flags;
diff --git a/cc/paint/paint_record.cc b/cc/paint/paint_record.cc
index 9593bbb..6609e82 100644
--- a/cc/paint/paint_record.cc
+++ b/cc/paint/paint_record.cc
@@ -9,18 +9,23 @@
 
 namespace cc {
 
-sk_sp<SkPicture> ToSkPicture(sk_sp<PaintRecord> record, const SkRect& bounds) {
+sk_sp<SkPicture> ToSkPicture(sk_sp<PaintRecord> record,
+                             const SkRect& bounds,
+                             ImageProvider* image_provider) {
   SkPictureRecorder recorder;
   SkCanvas* canvas = recorder.beginRecording(bounds);
-  record->Playback(canvas);
+  PlaybackParams params(image_provider);
+  record->Playback(canvas, params);
   return recorder.finishRecordingAsPicture();
 }
 
 sk_sp<const SkPicture> ToSkPicture(sk_sp<const PaintRecord> record,
-                                   const SkRect& bounds) {
+                                   const SkRect& bounds,
+                                   ImageProvider* image_provider) {
   SkPictureRecorder recorder;
   SkCanvas* canvas = recorder.beginRecording(bounds);
-  record->Playback(canvas);
+  PlaybackParams params(image_provider);
+  record->Playback(canvas, params);
   return recorder.finishRecordingAsPicture();
 }
 
diff --git a/cc/paint/paint_record.h b/cc/paint/paint_record.h
index 1509bac..6e0c1d7 100644
--- a/cc/paint/paint_record.h
+++ b/cc/paint/paint_record.h
@@ -10,6 +10,7 @@
 #include "third_party/skia/include/core/SkPicture.h"
 
 namespace cc {
+class ImageProvider;
 
 // TODO(enne): Don't want to rename the world for this.  Using these as the
 // same types for now prevents an extra allocation.  Probably PaintRecord
@@ -17,12 +18,15 @@
 using PaintRecord = PaintOpBuffer;
 
 // TODO(enne): Remove these if possible, they are really expensive.
-CC_PAINT_EXPORT sk_sp<SkPicture> ToSkPicture(sk_sp<PaintRecord> record,
-                                             const SkRect& bounds);
+CC_PAINT_EXPORT sk_sp<SkPicture> ToSkPicture(
+    sk_sp<PaintRecord> record,
+    const SkRect& bounds,
+    ImageProvider* image_provider = nullptr);
 
 CC_PAINT_EXPORT sk_sp<const SkPicture> ToSkPicture(
     sk_sp<const PaintRecord> record,
-    const SkRect& bounds);
+    const SkRect& bounds,
+    ImageProvider* image_provider = nullptr);
 
 }  // namespace cc
 
diff --git a/cc/paint/paint_shader.cc b/cc/paint/paint_shader.cc
index dccf2ca3..a53c6ed 100644
--- a/cc/paint/paint_shader.cc
+++ b/cc/paint/paint_shader.cc
@@ -186,6 +186,11 @@
 PaintShader::PaintShader(Type type) : shader_type_(type) {}
 PaintShader::~PaintShader() = default;
 
+bool PaintShader::has_discardable_images() const {
+  return (image_ && image_.IsLazyGenerated()) ||
+         (record_ && record_->HasDiscardableImages());
+}
+
 bool PaintShader::GetRasterizationTileRect(const SkMatrix& ctm,
                                            SkRect* tile_rect) const {
   DCHECK_EQ(shader_type_, Type::kPaintRecord);
diff --git a/cc/paint/paint_shader.h b/cc/paint/paint_shader.h
index 4fffd255..6c197b7 100644
--- a/cc/paint/paint_shader.h
+++ b/cc/paint/paint_shader.h
@@ -10,6 +10,7 @@
 
 #include "base/optional.h"
 #include "base/stl_util.h"
+#include "cc/paint/image_analysis_state.h"
 #include "cc/paint/paint_export.h"
 #include "cc/paint/paint_image.h"
 #include "third_party/skia/include/core/SkImage.h"
@@ -106,8 +107,16 @@
 
   ~PaintShader() override;
 
-  void set_has_animated_images() { has_animated_images_ = true; }
-  bool has_animated_images() const { return has_animated_images_; }
+  void set_has_animated_images(bool has_animated_images) {
+    image_analysis_state_ = has_animated_images
+                                ? ImageAnalysisState::kAnimatedImages
+                                : ImageAnalysisState::kNoAnimatedImages;
+  }
+  ImageAnalysisState image_analysis_state() const {
+    return image_analysis_state_;
+  }
+
+  bool has_discardable_images() const;
 
   SkMatrix GetLocalMatrix() const {
     return local_matrix_ ? *local_matrix_ : SkMatrix::I();
@@ -200,7 +209,7 @@
   // accesses to it are thread-safe.
   sk_sp<SkShader> cached_shader_;
 
-  bool has_animated_images_ = false;
+  ImageAnalysisState image_analysis_state_ = ImageAnalysisState::kNoAnalysis;
 
   DISALLOW_COPY_AND_ASSIGN(PaintShader);
 };
diff --git a/cc/paint/paint_shader_unittest.cc b/cc/paint/paint_shader_unittest.cc
index 55ce7c5..ac6f9e0 100644
--- a/cc/paint/paint_shader_unittest.cc
+++ b/cc/paint/paint_shader_unittest.cc
@@ -84,7 +84,7 @@
   auto record_shader = PaintShader::MakePaintRecord(
       record, SkRect::MakeWH(100, 100), SkShader::TileMode::kClamp_TileMode,
       SkShader::TileMode::kClamp_TileMode, &local_matrix);
-  record_shader->set_has_animated_images();
+  record_shader->set_has_animated_images(true);
 
   PaintOpBuffer buffer;
   PaintFlags flags;
diff --git a/cc/paint/scoped_raster_flags.cc b/cc/paint/scoped_raster_flags.cc
index 6e45512..e8590ac 100644
--- a/cc/paint/scoped_raster_flags.cc
+++ b/cc/paint/scoped_raster_flags.cc
@@ -5,6 +5,7 @@
 #include "cc/paint/scoped_raster_flags.h"
 
 #include "cc/paint/image_provider.h"
+#include "cc/paint/paint_filter.h"
 #include "cc/paint/paint_image_builder.h"
 
 namespace cc {
@@ -15,20 +16,13 @@
                                      bool create_skia_shader)
     : original_flags_(flags) {
   if (flags->HasDiscardableImages() && image_provider) {
-    DCHECK(flags->HasShader());
-
     // TODO(khushalsagar): The decoding of images in PaintFlags here is a bit of
     // a mess. We decode image shaders at the correct scale but ignore that
     // during serialization and just use the original image.
     decode_stashing_image_provider_.emplace(image_provider);
-    if (flags->getShader()->shader_type() == PaintShader::Type::kImage) {
-      DecodeImageShader(ctm);
-    } else if (flags->getShader()->shader_type() ==
-               PaintShader::Type::kPaintRecord) {
-      DecodeRecordShader(ctm, create_skia_shader);
-    } else {
-      NOTREACHED();
-    }
+    DecodeImageShader(ctm);
+    DecodeRecordShader(ctm, create_skia_shader);
+    DecodeFilter();
 
     // We skip the op if any images fail to decode.
     if (decode_failed_)
@@ -46,6 +40,10 @@
 ScopedRasterFlags::~ScopedRasterFlags() = default;
 
 void ScopedRasterFlags::DecodeImageShader(const SkMatrix& ctm) {
+  if (!flags()->HasShader() ||
+      flags()->getShader()->shader_type() != PaintShader::Type::kImage)
+    return;
+
   const PaintImage& paint_image = flags()->getShader()->paint_image();
   SkMatrix matrix = flags()->getShader()->GetLocalMatrix();
 
@@ -100,9 +98,14 @@
 
 void ScopedRasterFlags::DecodeRecordShader(const SkMatrix& ctm,
                                            bool create_skia_shader) {
+  if (!flags()->HasShader() ||
+      flags()->getShader()->shader_type() != PaintShader::Type::kPaintRecord)
+    return;
+
   // TODO(khushalsagar): For OOP, we have to decode everything during
   // serialization. This will force us to use original sized decodes.
-  if (!flags()->getShader()->has_animated_images())
+  if (flags()->getShader()->image_analysis_state() !=
+      ImageAnalysisState::kAnimatedImages)
     return;
 
   auto decoded_shader = flags()->getShader()->CreateDecodedPaintRecord(
@@ -117,6 +120,18 @@
   MutableFlags()->setShader(std::move(decoded_shader));
 }
 
+void ScopedRasterFlags::DecodeFilter() {
+  if (!flags()->getImageFilter() ||
+      !flags()->getImageFilter()->has_discardable_images() ||
+      flags()->getImageFilter()->image_analysis_state() !=
+          ImageAnalysisState::kAnimatedImages) {
+    return;
+  }
+
+  MutableFlags()->setImageFilter(flags()->getImageFilter()->SnapshotWithImages(
+      &*decode_stashing_image_provider_));
+}
+
 void ScopedRasterFlags::AdjustStrokeIfNeeded(const SkMatrix& ctm) {
   // With anti-aliasing turned off, strokes with a device space width in (0, 1)
   // may not raster at all.  To avoid this, we have two options:
diff --git a/cc/paint/scoped_raster_flags.h b/cc/paint/scoped_raster_flags.h
index 5b2f859..5b8185b1 100644
--- a/cc/paint/scoped_raster_flags.h
+++ b/cc/paint/scoped_raster_flags.h
@@ -37,6 +37,7 @@
  private:
   void DecodeImageShader(const SkMatrix& ctm);
   void DecodeRecordShader(const SkMatrix& ctm, bool create_skia_shader);
+  void DecodeFilter();
   void AdjustStrokeIfNeeded(const SkMatrix& ctm);
 
   PaintFlags* MutableFlags() {
diff --git a/cc/paint/scoped_raster_flags_unittest.cc b/cc/paint/scoped_raster_flags_unittest.cc
index c912d07..b0ac265 100644
--- a/cc/paint/scoped_raster_flags_unittest.cc
+++ b/cc/paint/scoped_raster_flags_unittest.cc
@@ -54,7 +54,7 @@
   auto record_shader = PaintShader::MakePaintRecord(
       record, SkRect::MakeWH(100, 100), SkShader::TileMode::kClamp_TileMode,
       SkShader::TileMode::kClamp_TileMode, &SkMatrix::I());
-  record_shader->set_has_animated_images();
+  record_shader->set_has_animated_images(true);
 
   MockImageProvider provider;
   PaintFlags flags;
diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc
index 6a436bf1..8e7da40a 100644
--- a/cc/trees/layer_tree_host_unittest.cc
+++ b/cc/trees/layer_tree_host_unittest.cc
@@ -8594,6 +8594,20 @@
 
 MULTI_THREAD_TEST_F(LayerTreeHostTestImageAnimationDrawRecordShader);
 
+class LayerTreeHostTestImageAnimationPaintFilter
+    : public LayerTreeHostTestImageAnimation {
+  void AddImageOp(const PaintImage& image) override {
+    auto record = sk_make_sp<PaintOpBuffer>();
+    record->push<DrawImageOp>(image, 0.f, 0.f, nullptr);
+    PaintFlags flags;
+    flags.setImageFilter(
+        sk_make_sp<RecordPaintFilter>(record, SkRect::MakeWH(500, 500)));
+    content_layer_client_.add_draw_rect(gfx::Rect(500, 500), flags);
+  }
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestImageAnimationPaintFilter);
+
 class LayerTreeHostTestImageAnimationSynchronousScheduling
     : public LayerTreeHostTestImageAnimationDrawImage {
  public: