Revert "Support multiple image filters in SaveLayerRec"

This reverts commit 7a97b766b14bc1d839643b9edd71a7b56ac14718.

Reason for revert: Fuzzer bug - abundance of caution

Original change's description:
> Support multiple image filters in SaveLayerRec
>
> The new API tries to match the WHATWG spec behavior: the layer contents
> are rendered once, then filtered and composited (using the layer's
> paint) in order.
>
> Bug: chromium:1418141
> Change-Id: Ia67f11ab622831d7f968bfa364fe54226b5d1d8c
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/784549
> Commit-Queue: Brian Osman <brianosman@google.com>
> Reviewed-by: Michael Ludwig <michaelludwig@google.com>

Bug: chromium:1418141
Change-Id: I18ee6f9f84023913f8e4e47ba15d78d9691dcdff
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/790339
Commit-Queue: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Auto-Submit: Brian Osman <brianosman@google.com>
diff --git a/gm/imagefilters.cpp b/gm/imagefilters.cpp
index d98e4de..6004852 100644
--- a/gm/imagefilters.cpp
+++ b/gm/imagefilters.cpp
@@ -29,7 +29,6 @@
 #include "include/effects/SkShaderMaskFilter.h"
 #include "include/gpu/GrDirectContext.h"
 #include "include/gpu/ganesh/SkImageGanesh.h"
-#include "src/core/SkCanvasPriv.h"
 #include "tools/DecodeUtils.h"
 #include "tools/Resources.h"
 #include "tools/ToolUtils.h"
@@ -283,47 +282,3 @@
     canvas->drawImage(image, 0, 0, SkSamplingOptions(), &testMaskPaint);
     canvas->restore();
 }
-
-DEF_SIMPLE_GM(multiple_filters, canvas, 415, 210) {
-    ToolUtils::draw_checkerboard(canvas);
-    canvas->translate(5, 5);
-
-    auto drawFilteredLayer = [=](SkCanvas::FilterSpan filters) {
-        SkPaint restorePaint;
-        restorePaint.setAlphaf(0.5f);
-        SkCanvas::SaveLayerRec rec = SkCanvasPriv::ScaledBackdropLayer(
-                /*bounds=*/nullptr,
-                &restorePaint,
-                /*backdrop=*/nullptr,
-                /*backdropScale=*/1,
-                /*saveLayerFlags=*/0,
-                filters);
-        canvas->save();
-        canvas->clipRect({0, 0, 200, 200});
-        canvas->saveLayer(rec);
-
-        SkPaint paint;
-        paint.setStyle(SkPaint::kStroke_Style);
-        paint.setStrokeWidth(20);
-        paint.setColor(SK_ColorGREEN);
-        canvas->drawCircle(100, 100, 70, paint);
-
-        canvas->restore();
-        canvas->restore();
-        canvas->translate(205, 0);
-    };
-
-    {
-        // Test with two non-null filters that each change bounds in a different way:
-        sk_sp<SkImageFilter> filters[2] = {SkImageFilters::Dilate(5, 5, nullptr),
-                                           SkImageFilters::Erode(5, 5, nullptr)};
-        drawFilteredLayer(filters);
-    }
-
-    {
-        // Test with one null filter, to more closely mimic the canvas2D layers use-case:
-        sk_sp<SkImageFilter> filters[2] = {
-                SkImageFilters::DropShadowOnly(7, 7, 5, 5, SK_ColorBLUE, nullptr), nullptr};
-        drawFilteredLayer(filters);
-    }
-}
diff --git a/include/core/SkCanvas.h b/include/core/SkCanvas.h
index f9d034c..32a9de8 100644
--- a/include/core/SkCanvas.h
+++ b/include/core/SkCanvas.h
@@ -24,13 +24,11 @@
 #include "include/core/SkSamplingOptions.h"
 #include "include/core/SkScalar.h"
 #include "include/core/SkSize.h"
-#include "include/core/SkSpan.h"
 #include "include/core/SkString.h"
 #include "include/core/SkSurfaceProps.h"
 #include "include/core/SkTypes.h"
 #include "include/private/base/SkCPUTypes.h"
 #include "include/private/base/SkDeque.h"
-#include "include/private/base/SkTArray.h"
 
 #include <cstdint>
 #include <cstring>
@@ -672,8 +670,7 @@
         kF16ColorType                   = 1 << 4,
     };
 
-    using SaveLayerFlags = uint32_t;
-    using FilterSpan = SkSpan<sk_sp<SkImageFilter>>;
+    typedef uint32_t SaveLayerFlags;
 
     /** \struct SkCanvas::SaveLayerRec
         SaveLayerRec contains the state used to create the layer.
@@ -693,7 +690,7 @@
             @return                SaveLayerRec with empty fBackdrop
         */
         SaveLayerRec(const SkRect* bounds, const SkPaint* paint, SaveLayerFlags saveLayerFlags = 0)
-            : SaveLayerRec(bounds, paint, nullptr, 1.f, saveLayerFlags, /*filters=*/{}) {}
+            : SaveLayerRec(bounds, paint, nullptr, 1.f, saveLayerFlags) {}
 
         /** Sets fBounds, fPaint, fBackdrop, and fSaveLayerFlags.
 
@@ -709,7 +706,7 @@
         */
         SaveLayerRec(const SkRect* bounds, const SkPaint* paint, const SkImageFilter* backdrop,
                      SaveLayerFlags saveLayerFlags)
-            : SaveLayerRec(bounds, paint, backdrop, 1.f, saveLayerFlags, /*filters=*/{}) {}
+            : SaveLayerRec(bounds, paint, backdrop, 1.f, saveLayerFlags) {}
 
         /** hints at layer size limit */
         const SkRect*        fBounds         = nullptr;
@@ -717,8 +714,6 @@
         /** modifies overlay */
         const SkPaint*       fPaint          = nullptr;
 
-        FilterSpan           fFilters        = {};
-
         /**
          *  If not null, this triggers the same initialization behavior as setting
          *  kInitWithPrevious_SaveLayerFlag on fSaveLayerFlags: the current layer is copied into
@@ -734,21 +729,13 @@
         friend class SkCanvas;
         friend class SkCanvasPriv;
 
-        SaveLayerRec(const SkRect* bounds,
-                     const SkPaint* paint,
-                     const SkImageFilter* backdrop,
-                     SkScalar backdropScale,
-                     SaveLayerFlags saveLayerFlags,
-                     FilterSpan filters)
-                : fBounds(bounds)
-                , fPaint(paint)
-            , fFilters(filters)
-                , fBackdrop(backdrop)
-                , fSaveLayerFlags(saveLayerFlags)
-                , fExperimentalBackdropScale(backdropScale) {
-            // We only allow the paint's image filter or the side-car list of filters -- not both.
-            SkASSERT(fFilters.empty() || !paint || !paint->getImageFilter());
-        }
+        SaveLayerRec(const SkRect* bounds, const SkPaint* paint, const SkImageFilter* backdrop,
+                     SkScalar backdropScale, SaveLayerFlags saveLayerFlags)
+            : fBounds(bounds)
+            , fPaint(paint)
+            , fBackdrop(backdrop)
+            , fSaveLayerFlags(saveLayerFlags)
+            , fExperimentalBackdropScale(backdropScale) {}
 
         // Relative scale factor that the image content used to initialize the layer when the
         // kInitFromPrevious flag or a backdrop filter is used.
@@ -2342,14 +2329,14 @@
     // clip, and matrix commands. There is a layer per call to saveLayer() using the
     // kFullLayer_SaveLayerStrategy.
     struct Layer {
-        sk_sp<SkDevice>                                fDevice;
-        skia_private::STArray<1, sk_sp<SkImageFilter>> fImageFilters;
-        SkPaint                                        fPaint;
-        bool                                           fIsCoverage;
-        bool                                           fDiscard;
+        sk_sp<SkDevice>      fDevice;
+        sk_sp<SkImageFilter> fImageFilter; // applied to layer *before* being drawn by paint
+        SkPaint              fPaint;
+        bool                 fIsCoverage;
+        bool                 fDiscard;
 
         Layer(sk_sp<SkDevice> device,
-              FilterSpan imageFilters,
+              sk_sp<SkImageFilter> imageFilter,
               const SkPaint& paint,
               bool isCoverage);
     };
@@ -2387,7 +2374,7 @@
         ~MCRec();
 
         void newLayer(sk_sp<SkDevice> layerDevice,
-                      FilterSpan filters,
+                      sk_sp<SkImageFilter> filter,
                       const SkPaint& restorePaint,
                       bool layerIsCoverage);
 
@@ -2523,7 +2510,7 @@
      * relative transforms between the devices).
      */
     void internalDrawDeviceWithFilter(SkDevice* src, SkDevice* dst,
-                                      FilterSpan filters, const SkPaint& paint,
+                                      const SkImageFilter* filter, const SkPaint& paint,
                                       DeviceCompatibleWithFilter compat,
                                       SkScalar scaleFactor = 1.f,
                                       bool srcIsCoverageLayer = false);
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index 6289c0d..8ee0d51 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -24,7 +24,6 @@
 #include "include/core/SkRRect.h"
 #include "include/core/SkRSXform.h"
 #include "include/core/SkRasterHandleAllocator.h"
-#include "include/core/SkRefCnt.h"
 #include "include/core/SkRegion.h"
 #include "include/core/SkShader.h"
 #include "include/core/SkSurface.h"
@@ -34,6 +33,7 @@
 #include "include/core/SkVertices.h"
 #include "include/private/base/SkDebug.h"
 #include "include/private/base/SkSafe32.h"
+#include "include/private/base/SkSpan_impl.h"
 #include "include/private/base/SkTPin.h"
 #include "include/private/base/SkTemplates.h"
 #include "include/private/base/SkTo.h"
@@ -177,11 +177,11 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 SkCanvas::Layer::Layer(sk_sp<SkDevice> device,
-                       FilterSpan imageFilters,
+                       sk_sp<SkImageFilter> imageFilter,
                        const SkPaint& paint,
                        bool isCoverage)
         : fDevice(std::move(device))
-        , fImageFilters(imageFilters.data(), imageFilters.size())
+        , fImageFilter(std::move(imageFilter))
         , fPaint(paint)
         , fIsCoverage(isCoverage)
         , fDiscard(false) {
@@ -209,12 +209,12 @@
 SkCanvas::MCRec::~MCRec() {}
 
 void SkCanvas::MCRec::newLayer(sk_sp<SkDevice> layerDevice,
-                               FilterSpan filters,
+                               sk_sp<SkImageFilter> filter,
                                const SkPaint& restorePaint,
                                bool layerIsCoverage) {
     SkASSERT(!fBackImage);
-    fLayer =
-            std::make_unique<Layer>(std::move(layerDevice), filters, restorePaint, layerIsCoverage);
+    fLayer = std::make_unique<Layer>(std::move(layerDevice), std::move(filter),
+                                     restorePaint, layerIsCoverage);
     fDevice = fLayer->fDevice.get();
 }
 
@@ -553,17 +553,6 @@
     return skif::ParameterSpace<SkPoint>(center);
 }
 
-// Helper when we need to upgrade a single filter to a FilterSpan
-struct FilterToSpan {
-    FilterToSpan(const SkImageFilter* filter) : fFilter(sk_ref_sp(filter)) {}
-
-    operator SkCanvas::FilterSpan() {
-        return fFilter ? SkCanvas::FilterSpan{&fFilter, 1} : SkCanvas::FilterSpan{};
-    }
-
-    sk_sp<SkImageFilter> fFilter;
-};
-
 // Compute suitable transformations and layer bounds for a new layer that will be used as the source
 // input into 'filter' before being drawn into 'dst' via the returned skif::Mapping.
 // Null filters are permitted and act as the identity. The returned mapping will be compatible with
@@ -574,7 +563,7 @@
 // if the image filter doesn't require an input image to produce a valid output.
 static std::optional<std::pair<skif::Mapping, skif::LayerSpace<SkIRect>>>
 get_layer_mapping_and_bounds(
-        SkCanvas::FilterSpan filters,
+        const SkImageFilter* filter,
         const SkMatrix& localToDst,
         const skif::DeviceSpace<SkIRect>& targetOutput,
         std::optional<skif::ParameterSpace<SkRect>> contentBounds = {},
@@ -601,13 +590,7 @@
     // Determine initial mapping and a reasonable maximum dimension to prevent layer-to-device
     // transforms with perspective and skew from triggering excessive buffer allocations.
     skif::Mapping mapping;
-    skif::MatrixCapability capability = skif::MatrixCapability::kComplex;
-    for (const sk_sp<SkImageFilter>& filter : filters) {
-        if (filter) {
-            capability = std::min(capability, as_IFB(filter)->getCTMCapability());
-        }
-    }
-    if (!mapping.decomposeCTM(localToDst, capability, center)) {
+    if (!mapping.decomposeCTM(localToDst, filter, center)) {
         return {};
     }
     // Push scale factor into layer matrix and device matrix (net no change, but the layer will have
@@ -627,42 +610,28 @@
                                                             SkIRect(targetOutput).height64())),
                                kMinDimThreshold);
 
-    auto baseLayerBounds = mapping.deviceToLayer(targetOutput);
-    if (contentBounds) {
-        // For better or for worse, user bounds currently act as a hard clip on the layer's
-        // extent (i.e., they implement the CSS filter-effects 'filter region' feature).
-        skif::LayerSpace<SkIRect> knownBounds = mapping.paramToLayer(*contentBounds).roundOut();
-        if (!baseLayerBounds.intersect(knownBounds)) {
-            baseLayerBounds = skif::LayerSpace<SkIRect>::Empty();
-        }
-    }
-
     skif::LayerSpace<SkIRect> layerBounds;
-    if (!filters.empty()) {
-        layerBounds = skif::LayerSpace<SkIRect>::Union(filters.size(), [&](int i) {
-            return filters[i] ? as_IFB(filters[i])
-                                        ->getInputBounds(mapping, targetOutput, contentBounds)
-                              : baseLayerBounds;
-        });
+    if (filter) {
+        layerBounds = as_IFB(filter)->getInputBounds(mapping, targetOutput, contentBounds);
         // When a filter is involved, the layer size may be larger than the default maxLayerDim due
         // to required inputs for filters (e.g. a displacement map with a large radius).
         if (layerBounds.width() > maxLayerDim || layerBounds.height() > maxLayerDim) {
             skif::Mapping idealMapping{mapping.layerMatrix()};
-            for (const sk_sp<SkImageFilter>& filter : filters) {
-                if (filter) {
-                    auto idealLayerBounds = as_IFB(filter)->getInputBounds(
-                            idealMapping, targetOutput, contentBounds);
-                    maxLayerDim = std::max(std::max(idealLayerBounds.width(),
-                                                    idealLayerBounds.height()),
-                                                    maxLayerDim);
-                }
-            }
+            auto idealLayerBounds = as_IFB(filter)->getInputBounds(idealMapping, targetOutput,
+                                                                   contentBounds);
+            maxLayerDim = std::max(std::max(idealLayerBounds.width(), idealLayerBounds.height()),
+                                   maxLayerDim);
         }
     } else {
-        if (baseLayerBounds.isEmpty()) {
-            return {};
+        layerBounds = mapping.deviceToLayer(targetOutput);
+        if (contentBounds) {
+            // For better or for worse, user bounds currently act as a hard clip on the layer's
+            // extent (i.e., they implement the CSS filter-effects 'filter region' feature).
+            skif::LayerSpace<SkIRect> knownBounds = mapping.paramToLayer(*contentBounds).roundOut();
+            if (!layerBounds.intersect(knownBounds)) {
+                return {};
+            }
         }
-        layerBounds = baseLayerBounds;
     }
 
     if (layerBounds.width() > maxLayerDim || layerBounds.height() > maxLayerDim) {
@@ -717,7 +686,7 @@
 
 void SkCanvas::internalDrawDeviceWithFilter(SkDevice* src,
                                             SkDevice* dst,
-                                            FilterSpan filters,
+                                            const SkImageFilter* filter,
                                             const SkPaint& paint,
                                             DeviceCompatibleWithFilter compat,
                                             SkScalar scaleFactor,
@@ -775,7 +744,7 @@
         // Compute the image filter mapping by decomposing the local->device matrix of dst and
         // re-determining the required input.
         auto mappingAndBounds = get_layer_mapping_and_bounds(
-                filters, dst->localToDevice(), outputBounds, {}, SkTPin(scaleFactor, 0.f, 1.f));
+                filter, dst->localToDevice(), outputBounds, {}, SkTPin(scaleFactor, 0.f, 1.f));
         if (!mappingAndBounds) {
             return;
         }
@@ -859,28 +828,24 @@
     ctx = ctx.withNewDesiredOutput(mapping.deviceToLayer(outputBounds))
              .withNewSource(source);
 
-    // Here, we allow a single-element FilterSpan with a null entry, to simplify the loop:
-    sk_sp<SkImageFilter> nullFilter;
-    FilterSpan filtersOrNull = filters.empty() ? FilterSpan{&nullFilter, 1} : filters;
+    auto result = filter ? as_IFB(filter)->filterImage(ctx) : source;
 
-    for (const sk_sp<SkImageFilter>& filter : filtersOrNull) {
-        auto result = filter ? as_IFB(filter)->filterImage(ctx) : source;
-
-        if (srcIsCoverageLayer) {
-            SkASSERT(dst->useDrawCoverageMaskForMaskFilters());
-            // TODO: Can FilterResult optimize this in any meaningful way if it still has to go
-            // through drawCoverageMask that requires an image (vs a coverage shader)?
-            auto [coverageMask, origin] = result.imageAndOffset(ctx);
-            if (coverageMask) {
-                SkMatrix deviceMatrixWithOffset = mapping.layerToDevice();
-                deviceMatrixWithOffset.preTranslate(origin.x(), origin.y());
-                dst->drawCoverageMask(
-                        coverageMask.get(), deviceMatrixWithOffset, result.sampling(), paint);
-            }
-        } else {
-            result = apply_alpha_and_colorfilter(ctx, result, paint);
-            result.draw(ctx, dst, paint.getBlender());
+    if (srcIsCoverageLayer) {
+        SkASSERT(dst->useDrawCoverageMaskForMaskFilters());
+        // TODO: Can FilterResult optimize this in any meaningful way if it still has to go through
+        // drawCoverageMask that requires an image (vs a coverage shader)?
+        auto [coverageMask, origin] = result.imageAndOffset(ctx);
+        if (coverageMask) {
+            SkMatrix deviceMatrixWithOffset = mapping.layerToDevice();
+            deviceMatrixWithOffset.preTranslate(origin.x(), origin.y());
+            dst->drawCoverageMask(coverageMask.get(),
+                                  deviceMatrixWithOffset,
+                                  result.sampling(),
+                                  paint);
         }
+    } else {
+        result = apply_alpha_and_colorfilter(ctx, result, paint);
+        result.draw(ctx, dst, paint.getBlender());
     }
 }
 
@@ -897,13 +862,11 @@
 
 void SkCanvas::internalDrawDeviceWithFilter(SkDevice* src,
                                             SkDevice* dst,
-                                            FilterSpan filters,
+                                            const SkImageFilter* filter,
                                             const SkPaint& paint,
                                             DeviceCompatibleWithFilter compat,
                                             SkScalar scaleFactor,
                                             bool srcIsCoverageLayer) {
-    const SkImageFilter* filter = filters.empty() ? nullptr : filters.front().get();
-
     // coverage image filters won't be supported in the old filter rendering code path
     (void) srcIsCoverageLayer;
 
@@ -952,7 +915,7 @@
         // Compute the image filter mapping by decomposing the local->device matrix of dst and
         // re-determining the required input.
         auto mappingAndBounds = get_layer_mapping_and_bounds(
-                filters, dst->localToDevice(), skif::DeviceSpace<SkIRect>(dst->devClipBounds()),
+                filter, dst->localToDevice(), skif::DeviceSpace<SkIRect>(dst->devClipBounds()),
                 {}, true, SkTPin(scaleFactor, 0.f, 1.f));
         if (!mappingAndBounds) {
             return;
@@ -1170,12 +1133,8 @@
     restorePaint.setAntiAlias(true);
 
 #if defined(SK_RESOLVE_FILTERS_BEFORE_RESTORE)
-    sk_sp<SkImageFilter> paintFilter = sk_ref_sp(optimize_layer_filter(
-            rec.fPaint ? rec.fPaint->getImageFilter() : nullptr, &restorePaint));
-
-    // Don't support multiple filters while using the old code path
-    SkASSERT(rec.fFilters.empty());
-    FilterSpan filters = paintFilter ? FilterSpan{&paintFilter, 1} : FilterSpan{};
+    const SkImageFilter* filter = optimize_layer_filter(
+            rec.fPaint ? rec.fPaint->getImageFilter() : nullptr, &restorePaint);
 
     // Size the new layer relative to the prior device, which may already be aligned for filters.
     SkDevice* priorDevice = this->topDevice();
@@ -1185,15 +1144,14 @@
     if (rec.fBounds) {
         contentBounds = skif::ParameterSpace<SkRect>(*rec.fBounds);
     }
-
     auto mappingAndBounds = get_layer_mapping_and_bounds(
-            filters, priorDevice->localToDevice(),
+            filter, priorDevice->localToDevice(),
             skif::DeviceSpace<SkIRect>(priorDevice->devClipBounds()),
             contentBounds,
             must_cover_prior_device(rec.fBackdrop, restorePaint));
 #else
-    sk_sp<SkImageFilter> paintFilter = rec.fPaint ? rec.fPaint->refImageFilter() : nullptr;
-    FilterSpan filters = paintFilter ? FilterSpan{&paintFilter, 1} : rec.fFilters;
+
+    const SkImageFilter* filter = rec.fPaint ? rec.fPaint->getImageFilter() : nullptr;
     const SkColorFilter* cf = restorePaint.getColorFilter();
     const SkBlender* blender = restorePaint.getBlender();
 
@@ -1207,7 +1165,7 @@
     // TODO(b/314968012): Chrome needs to be updated to clip saveAlphaLayer bounds explicitly when
     // it uses kInitWithPrevious and LCD text.
     filtersPriorDevice |= ((rec.fSaveLayerFlags & kInitWithPrevious_SaveLayerFlag) &&
-             (!filters.empty() || cf || blender || restorePaint.getAlphaf() < 1.f));
+             (filter || cf || blender || restorePaint.getAlphaf() < 1.f));
 #endif
     // If the restorePaint has a transparency-affecting colorfilter or blender, the output is
     // unbounded during restore(). `internalDrawDeviceWithFilter` automatically applies these
@@ -1215,7 +1173,7 @@
     // not apply effects beyond the layer's image so we mark `trivialRestore` as false too.
     // TODO: drawDevice() could be updated to apply transparency-affecting effects to a content-
     // clipped image, but this is the simplest solution when considering document-based SkDevices.
-    const bool drawDeviceMustFillClip = filters.empty() &&
+    const bool drawDeviceMustFillClip = !filter &&
             ((cf && as_CFB(cf)->affectsTransparentBlack()) ||
                 (blender && as_BB(blender)->affectsTransparentBlack()));
     const bool trivialRestore = !filtersPriorDevice && !drawDeviceMustFillClip;
@@ -1233,7 +1191,7 @@
     }
 
     auto mappingAndBounds = get_layer_mapping_and_bounds(
-            filters, priorDevice->localToDevice(), outputBounds, contentBounds);
+            filter, priorDevice->localToDevice(), outputBounds, contentBounds);
 #endif
 
     auto abortLayer = [this]() {
@@ -1257,9 +1215,8 @@
         // produce output, or that the filter DAG has no references to the dynamic source image.
         // In this case it still has an output that we need to render, but do so now since there is
         // no new layer pushed on the stack and the paired restore() will be a no-op.
-        if (!filters.empty() && !priorDevice->isNoPixelsDevice()) {
+        if (filter && !priorDevice->isNoPixelsDevice()) {
 #if defined(SK_RESOLVE_FILTERS_BEFORE_RESTORE)
-            const SkImageFilter* filter = filters.empty() ? nullptr : filters.front().get();
             skif::ParameterSpace<SkRect> emptyInput{SkRect::MakeEmpty()};
             std::optional<skif::DeviceSpace<SkIRect>> output =
                     as_IFB(filter)->getOutputBounds(newLayerMapping, emptyInput);
@@ -1278,7 +1235,7 @@
                                                restorePaint);
             }
 #else
-            this->internalDrawDeviceWithFilter(/*src=*/nullptr, priorDevice, filters, restorePaint,
+            this->internalDrawDeviceWithFilter(/*src=*/nullptr, priorDevice, filter, restorePaint,
                                                DeviceCompatibleWithFilter::kUnknown);
 #endif
         }
@@ -1344,23 +1301,22 @@
 #else
         const SkImageFilter* backdropFilter = rec.fBackdrop;
 #endif
-        FilterToSpan backdropAsSpan(backdropFilter);
         // The new device was constructed to be compatible with 'filter', not necessarily
         // 'rec.fBackdrop', so allow DrawDeviceWithFilter to transform the prior device contents
         // if necessary to evaluate the backdrop filter. If no filters are involved, then the
         // devices differ by integer translations and are always compatible.
         bool scaleBackdrop = rec.fExperimentalBackdropScale != 1.0f;
-        auto compat = (!filters.empty() || backdropFilter || scaleBackdrop)
+        auto compat = (filter || backdropFilter || scaleBackdrop)
                 ? DeviceCompatibleWithFilter::kUnknown : DeviceCompatibleWithFilter::kYes;
         this->internalDrawDeviceWithFilter(priorDevice,     // src
                                            newDevice.get(), // dst
-                                           backdropAsSpan,
+                                           backdropFilter,
                                            backdropPaint,
                                            compat,
                                            rec.fExperimentalBackdropScale);
     }
 
-    fMCRec->newLayer(std::move(newDevice), filters, restorePaint, coverageOnly);
+    fMCRec->newLayer(std::move(newDevice), sk_ref_sp(filter), restorePaint, coverageOnly);
     fQuickRejectBounds = this->computeDeviceClipBounds();
 }
 
@@ -1451,10 +1407,10 @@
         // internalSaveLayer and internalRestore.
         if (this->predrawNotify()) {
             SkDevice* dstDev = this->topDevice();
-            if (!layer->fImageFilters.empty()) {
+            if (layer->fImageFilter) {
                 this->internalDrawDeviceWithFilter(layer->fDevice.get(), // src
                                                    dstDev,               // dst
-                                                   layer->fImageFilters,
+                                                   layer->fImageFilter.get(),
                                                    layer->fPaint,
                                                    DeviceCompatibleWithFilter::kYes,
                                                    /*scaleFactor=*/1.0f,
@@ -2544,8 +2500,7 @@
 
         skif::ParameterSpace<SkRect> imageBounds{dst};
         skif::DeviceSpace<SkIRect> outputBounds{device->devClipBounds()};
-        FilterToSpan filterAsSpan(realPaint.getImageFilter());
-        auto mappingAndBounds = get_layer_mapping_and_bounds(filterAsSpan,
+        auto mappingAndBounds = get_layer_mapping_and_bounds(realPaint.getImageFilter(),
                                                              device->localToDevice(),
                                                              outputBounds,
                                                              imageBounds);
diff --git a/src/core/SkCanvasPriv.h b/src/core/SkCanvasPriv.h
index 43df100..35c9226b 100644
--- a/src/core/SkCanvasPriv.h
+++ b/src/core/SkCanvasPriv.h
@@ -80,10 +80,8 @@
                                                       const SkPaint* paint,
                                                       const SkImageFilter* backdrop,
                                                       SkScalar backdropScale,
-                                                      SkCanvas::SaveLayerFlags saveLayerFlags,
-                                                      SkCanvas::FilterSpan filters = {}) {
-        return SkCanvas::SaveLayerRec(
-                bounds, paint, backdrop, backdropScale, saveLayerFlags, filters);
+                                                      SkCanvas::SaveLayerFlags saveLayerFlags) {
+        return SkCanvas::SaveLayerRec(bounds, paint, backdrop, backdropScale, saveLayerFlags);
     }
 
     static SkScalar GetBackdropScaleFactor(const SkCanvas::SaveLayerRec& rec) {
diff --git a/src/core/SkImageFilterTypes.cpp b/src/core/SkImageFilterTypes.cpp
index 3935a54..386bdbf 100644
--- a/src/core/SkImageFilterTypes.cpp
+++ b/src/core/SkImageFilterTypes.cpp
@@ -418,9 +418,12 @@
 
 SkIRect RoundIn(SkRect r) { return r.makeOutset(kRoundEpsilon, kRoundEpsilon).roundIn(); }
 
-bool Mapping::decomposeCTM(const SkMatrix& ctm, MatrixCapability capability,
+bool Mapping::decomposeCTM(const SkMatrix& ctm, const SkImageFilter* filter,
                            const skif::ParameterSpace<SkPoint>& representativePt) {
     SkMatrix remainder, layer;
+    using MatrixCapability = SkImageFilter_Base::MatrixCapability;
+    MatrixCapability capability =
+            filter ? as_IFB(filter)->getCTMCapability() : MatrixCapability::kComplex;
     if (capability == MatrixCapability::kTranslate) {
         // Apply the entire CTM post-filtering
         remainder = ctm;
@@ -451,15 +454,6 @@
     }
 }
 
-bool Mapping::decomposeCTM(const SkMatrix& ctm,
-                           const SkImageFilter* filter,
-                           const skif::ParameterSpace<SkPoint>& representativePt) {
-    return this->decomposeCTM(
-            ctm,
-            filter ? as_IFB(filter)->getCTMCapability() : MatrixCapability::kComplex,
-            representativePt);
-}
-
 bool Mapping::adjustLayerSpace(const SkMatrix& layer) {
     SkMatrix invLayer;
     if (!layer.invert(&invLayer)) {
diff --git a/src/core/SkImageFilterTypes.h b/src/core/SkImageFilterTypes.h
index 1b9ff5c..1233c72 100644
--- a/src/core/SkImageFilterTypes.h
+++ b/src/core/SkImageFilterTypes.h
@@ -533,16 +533,6 @@
     SkMatrix fData;
 };
 
-/**
- * Most ImageFilters can natively handle scaling and translate components in the CTM. Only some of
- * them can handle affine (or more complex) matrices. Some may only handle translation.
- */
-enum class MatrixCapability {
-    kTranslate,
-    kScaleTranslate,
-    kComplex,
-};
-
 // Mapping is the primary definition of the shared layer space used when evaluating an image filter
 // DAG. It encapsulates any needed decomposition of the total CTM into the parameter-to-layer matrix
 // (that filters use to map their parameters to the layer space), and the layer-to-device matrix
@@ -572,9 +562,6 @@
     [[nodiscard]] bool decomposeCTM(const SkMatrix& ctm,
                                     const SkImageFilter* filter,
                                     const skif::ParameterSpace<SkPoint>& representativePt);
-    [[nodiscard]] bool decomposeCTM(const SkMatrix& ctm,
-                                    MatrixCapability,
-                                    const skif::ParameterSpace<SkPoint>& representativePt);
 
     // Update the mapping's parameter-to-layer matrix to be pre-concatenated with the specified
     // local space transformation. This changes the definition of parameter space, any
diff --git a/src/core/SkImageFilter_Base.h b/src/core/SkImageFilter_Base.h
index 64becbb..ef2fce0 100644
--- a/src/core/SkImageFilter_Base.h
+++ b/src/core/SkImageFilter_Base.h
@@ -122,9 +122,15 @@
     bool usesSource() const { return fUsesSrcInput; }
 
     /**
+     *  Most ImageFilters can natively handle scaling and translate components in the CTM. Only
+     *  some of them can handle affine (or more complex) matrices. Some may only handle translation.
      *  This call returns the maximum "kind" of CTM for a filter and all of its (non-null) inputs.
      */
-    using MatrixCapability = skif::MatrixCapability;
+    enum class MatrixCapability {
+        kTranslate,
+        kScaleTranslate,
+        kComplex,
+    };
     MatrixCapability getCTMCapability() const;
 
     uint32_t uniqueID() const { return fUniqueID; }
diff --git a/src/core/SkPictureFlat.h b/src/core/SkPictureFlat.h
index 9519557..99f1ce2 100644
--- a/src/core/SkPictureFlat.h
+++ b/src/core/SkPictureFlat.h
@@ -145,14 +145,13 @@
 };
 
 enum SaveLayerRecFlatFlags {
-    SAVELAYERREC_HAS_BOUNDS              = 1 << 0,
-    SAVELAYERREC_HAS_PAINT               = 1 << 1,
-    SAVELAYERREC_HAS_BACKDROP            = 1 << 2,
-    SAVELAYERREC_HAS_FLAGS               = 1 << 3,
+    SAVELAYERREC_HAS_BOUNDS     = 1 << 0,
+    SAVELAYERREC_HAS_PAINT      = 1 << 1,
+    SAVELAYERREC_HAS_BACKDROP   = 1 << 2,
+    SAVELAYERREC_HAS_FLAGS      = 1 << 3,
     SAVELAYERREC_HAS_CLIPMASK_OBSOLETE   = 1 << 4,  // 6/13/2020
     SAVELAYERREC_HAS_CLIPMATRIX_OBSOLETE = 1 << 5,  // 6/13/2020
-    SAVELAYERREC_HAS_BACKDROP_SCALE      = 1 << 6,
-    SAVELAYERREC_HAS_MULTIPLE_FILTERS    = 1 << 7,
+    SAVELAYERREC_HAS_BACKDROP_SCALE = 1 << 6
 };
 
 enum SaveBehindFlatFlags {
diff --git a/src/core/SkPicturePlayback.cpp b/src/core/SkPicturePlayback.cpp
index 7f1f204..b6526e2 100644
--- a/src/core/SkPicturePlayback.cpp
+++ b/src/core/SkPicturePlayback.cpp
@@ -12,7 +12,6 @@
 #include "include/core/SkColor.h"
 #include "include/core/SkData.h"
 #include "include/core/SkImage.h"
-#include "include/core/SkImageFilter.h"
 #include "include/core/SkMatrix.h"
 #include "include/core/SkPaint.h"
 #include "include/core/SkPoint.h"
@@ -672,7 +671,6 @@
             SkCanvas::SaveLayerRec rec(nullptr, nullptr, nullptr, 0);
             const uint32_t flatFlags = reader->readInt();
             SkRect bounds;
-            skia_private::AutoSTArray<2, sk_sp<SkImageFilter>> filters;
             if (flatFlags & SAVELAYERREC_HAS_BOUNDS) {
                 reader->readRect(&bounds);
                 rec.fBounds = &bounds;
@@ -698,16 +696,6 @@
                 (flatFlags & SAVELAYERREC_HAS_BACKDROP_SCALE)) {
                 SkCanvasPriv::SetBackdropScaleFactor(&rec, reader->readScalar());
             }
-            if (!reader->isVersionLT(SkPicturePriv::Version::kMultipleFiltersOnSaveLayer) &&
-                (flatFlags & SAVELAYERREC_HAS_MULTIPLE_FILTERS)) {
-                uint32_t filterCount = reader->readUInt();
-                filters.reset(filterCount);
-                for (uint32_t i = 0; i < filterCount; ++i) {
-                    const SkPaint& paint = fPictureData->requiredPaint(reader);
-                    filters[i] = paint.refImageFilter();
-                }
-                rec.fFilters = filters;
-            }
             BREAK_ON_READ_ERROR(reader);
 
             canvas->saveLayer(rec);
diff --git a/src/core/SkPicturePriv.h b/src/core/SkPicturePriv.h
index 7371b63..0d09909 100644
--- a/src/core/SkPicturePriv.h
+++ b/src/core/SkPicturePriv.h
@@ -114,7 +114,6 @@
     // V101: Crop image filter supports all SkTileModes instead of just kDecal
     // V102: Convolution image filter uses ::Crop to apply tile mode
     // V103: Remove deprecated per-image filter crop rect
-    // v104: SaveLayer supports multiple image filters
 
     enum Version {
         kPictureShaderFilterParam_Version   = 82,
@@ -139,7 +138,6 @@
         kCropImageFilterSupportsTiling      = 101,
         kConvolutionImageFilterTilingUpdate = 102,
         kRemoveDeprecatedCropRect           = 103,
-        kMultipleFiltersOnSaveLayer         = 104,
 
         // Only SKPs within the min/current picture version range (inclusive) can be read.
         //
@@ -164,7 +162,7 @@
         //
         // Contact the Infra Gardener if the above steps do not work for you.
         kMin_Version     = kPictureShaderFilterParam_Version,
-        kCurrent_Version = kMultipleFiltersOnSaveLayer
+        kCurrent_Version = kRemoveDeprecatedCropRect
     };
 };
 
diff --git a/src/core/SkPictureRecord.cpp b/src/core/SkPictureRecord.cpp
index cdb137d..d9d8391 100644
--- a/src/core/SkPictureRecord.cpp
+++ b/src/core/SkPictureRecord.cpp
@@ -111,7 +111,6 @@
     // op + flatflags
     size_t size = 2 * kUInt32Size;
     uint32_t flatFlags = 0;
-    uint32_t filterCount = SkToU32(rec.fFilters.size());
 
     if (rec.fBounds) {
         flatFlags |= SAVELAYERREC_HAS_BOUNDS;
@@ -133,11 +132,6 @@
         flatFlags |= SAVELAYERREC_HAS_BACKDROP_SCALE;
         size += sizeof(SkScalar);
     }
-    if (filterCount) {
-        flatFlags |= SAVELAYERREC_HAS_MULTIPLE_FILTERS;
-        size += sizeof(uint32_t);  // count
-        size += sizeof(uint32_t) * filterCount;  // N (paint) indices
-    }
 
     const size_t initialOffset = this->addDraw(SAVE_LAYER_SAVELAYERREC, &size);
     this->addInt(flatFlags);
@@ -159,15 +153,6 @@
     if (flatFlags & SAVELAYERREC_HAS_BACKDROP_SCALE) {
         this->addScalar(SkCanvasPriv::GetBackdropScaleFactor(rec));
     }
-    if (flatFlags & SAVELAYERREC_HAS_MULTIPLE_FILTERS) {
-        this->addInt(filterCount);
-        for (uint32_t i = 0; i < filterCount; ++i) {
-            // overkill to store a paint, oh well.
-            SkPaint paint;
-            paint.setImageFilter(rec.fFilters[i]);
-            this->addPaint(paint);
-        }
-    }
     this->validate(initialOffset, size);
 }
 
diff --git a/src/core/SkRecordDraw.cpp b/src/core/SkRecordDraw.cpp
index 4f13602..8627070 100644
--- a/src/core/SkRecordDraw.cpp
+++ b/src/core/SkRecordDraw.cpp
@@ -40,8 +40,6 @@
 #include <optional>
 #include <vector>
 
-class SkImageFilter;
-
 void SkRecordDraw(const SkRecord& record,
                   SkCanvas* canvas,
                   SkPicture const* const drawablePicts[],
@@ -95,15 +93,11 @@
 #define DRAW(T, call) template <> void Draw::draw(const T& r) { fCanvas->call; }
 DRAW(Restore, restore())
 DRAW(Save, save())
-DRAW(SaveLayer,
-     saveLayer(SkCanvasPriv::ScaledBackdropLayer(
-             r.bounds,
-             r.paint,
-             r.backdrop.get(),
-             r.backdropScale,
-             r.saveLayerFlags,
-             SkCanvas::FilterSpan{const_cast<sk_sp<SkImageFilter>*>(r.filters.data()),
-                                  r.filters.size()})))
+DRAW(SaveLayer, saveLayer(SkCanvasPriv::ScaledBackdropLayer(r.bounds,
+                                                            r.paint,
+                                                            r.backdrop.get(),
+                                                            r.backdropScale,
+                                                            r.saveLayerFlags)))
 
 template <> void Draw::draw(const SaveBehind& r) {
     SkCanvasPriv::SaveBehind(fCanvas, r.subset);
diff --git a/src/core/SkRecordOpts.cpp b/src/core/SkRecordOpts.cpp
index 3d87e52..630ef7e 100644
--- a/src/core/SkRecordOpts.cpp
+++ b/src/core/SkRecordOpts.cpp
@@ -13,7 +13,6 @@
 #include "include/core/SkPaint.h"
 #include "include/core/SkRefCnt.h"
 #include "include/private/base/SkMath.h"
-#include "include/private/base/SkTemplates.h"
 #include "src/core/SkRecord.h"
 #include "src/core/SkRecordPattern.h"
 #include "src/core/SkRecords.h"
@@ -165,11 +164,6 @@
             return false;
         }
 
-        if (!match->first<SaveLayer>()->filters.empty()) {
-            // Our optimizations don't handle the filter list correctly - don't bother trying
-            return false;
-        }
-
         // A SaveLayer's bounds field is just a hint, so we should be free to ignore it.
         SkPaint* layerPaint = match->first<SaveLayer>()->paint;
         SkPaint* drawPaint = match->second<SkPaint>();
@@ -223,12 +217,6 @@
             return false;
         }
 
-        if (!match->first<SaveLayer>()->filters.empty() ||
-            !match->fourth<SaveLayer>()->filters.empty()) {
-            // Our optimizations don't handle the filter list correctly - don't bother trying
-            return false;
-        }
-
         SkPaint* opacityPaint = match->first<SaveLayer>()->paint;
         if (nullptr == opacityPaint) {
             // There wasn't really any point to this SaveLayer at all.
diff --git a/src/core/SkRecorder.cpp b/src/core/SkRecorder.cpp
index 04d73a0..b7b08da 100644
--- a/src/core/SkRecorder.cpp
+++ b/src/core/SkRecorder.cpp
@@ -11,7 +11,6 @@
 #include "include/core/SkData.h"
 #include "include/core/SkDrawable.h"
 #include "include/core/SkImage.h"
-#include "include/core/SkImageFilter.h"
 #include "include/core/SkImageInfo.h"
 #include "include/core/SkMatrix.h"
 #include "include/core/SkPaint.h"
@@ -341,17 +340,11 @@
 }
 
 SkCanvas::SaveLayerStrategy SkRecorder::getSaveLayerStrategy(const SaveLayerRec& rec) {
-    AutoTArray<sk_sp<SkImageFilter>> filters(rec.fFilters.size());
-    for (size_t i = 0; i < rec.fFilters.size(); ++i) {
-        filters[i] = rec.fFilters[i];
-    }
-
-    this->append<SkRecords::SaveLayer>(this->copy(rec.fBounds),
-                                       this->copy(rec.fPaint),
-                                       sk_ref_sp(rec.fBackdrop),
-                                       rec.fSaveLayerFlags,
-                                       SkCanvasPriv::GetBackdropScaleFactor(rec),
-                                       std::move(filters));
+    this->append<SkRecords::SaveLayer>(this->copy(rec.fBounds)
+                    , this->copy(rec.fPaint)
+                    , sk_ref_sp(rec.fBackdrop)
+                    , rec.fSaveLayerFlags
+                    , SkCanvasPriv::GetBackdropScaleFactor(rec));
     return SkCanvas::kNoLayer_SaveLayerStrategy;
 }
 
diff --git a/src/core/SkRecords.h b/src/core/SkRecords.h
index de9a9a8..840f127 100644
--- a/src/core/SkRecords.h
+++ b/src/core/SkRecords.h
@@ -184,8 +184,7 @@
        Optional<SkPaint> paint;
        sk_sp<const SkImageFilter> backdrop;
        SkCanvas::SaveLayerFlags saveLayerFlags;
-       SkScalar backdropScale;
-       skia_private::AutoTArray<sk_sp<SkImageFilter>> filters)
+       SkScalar backdropScale)
 
 RECORD(SaveBehind, 0,
        Optional<SkRect> subset)