Add filter param to picture-shader

Bug: skia:7650
Change-Id: I5eb6d73cf92c22b3846d4f4b81c7a8c06e2889a3
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/354659
Commit-Queue: Mike Reed <reed@google.com>
Reviewed-by: Florin Malita <fmalita@chromium.org>
diff --git a/gm/pictureshader.cpp b/gm/pictureshader.cpp
index c5a067e..6f22043 100644
--- a/gm/pictureshader.cpp
+++ b/gm/pictureshader.cpp
@@ -225,3 +225,65 @@
     p.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat));
     canvas->drawPaint(p);
 }
+
+/*
+    Test picture-shader's filtering (after the tile is created.
+    The GM draws a 2x2 grid of tiled images (circle, square, X)
+
+    Column 0 should be hard-edged
+    Column 1 should be filtered
+
+    Row 0 deduces this from the paint (legacy behavior)
+    Row 1 takes this as an explicit parameter (SkFilterMode)
+ */
+DEF_SIMPLE_GM(picture_shader_filter, canvas, 230, 230) {
+    auto pic = [&] {
+        SkRect r = SkRect::MakeWH(100, 100);
+        SkPictureRecorder recorder;
+        SkCanvas* c = recorder.beginRecording(r);
+        SkPaint paint;
+        paint.setStroke(true);
+        c->drawRect({5, 5, 95, 95}, paint);
+        c->drawCircle(50, 50, 30, paint);
+        c->drawLine(5, 1, 95,95, paint);
+        c->drawLine(5,95, 95, 1, paint);
+        return recorder.finishRecordingAsPicture();
+    }();
+
+    struct {
+        SkPoint         fLoc;
+        SkFilterMode    fFilter;
+        bool            fInheritFromPaint;
+
+        void setup(SkPaint* paint, sk_sp<SkPicture> pic) const {
+            SkTileMode tm = SkTileMode::kRepeat;
+            sk_sp<SkShader> sh;
+            if (fInheritFromPaint) {
+                sh = pic->makeShader(tm, tm, nullptr, nullptr);
+                paint->setFilterQuality(fFilter == SkFilterMode::kNearest ? kNone_SkFilterQuality
+                                                                          : kLow_SkFilterQuality);
+            } else {
+                sh = pic->makeShader(tm, tm, fFilter, nullptr, nullptr);
+                // the draw should ignore paint's filterquality,
+                // but we'll set it to something wacky just to be test that
+                paint->setFilterQuality(kHigh_SkFilterQuality);
+            }
+            paint->setShader(sh);
+        }
+    } recs[] = {
+        { {0, 0}, SkFilterMode::kNearest, true },
+        { {1, 0}, SkFilterMode::kLinear,  true },
+        { {0, 1}, SkFilterMode::kNearest, false },
+        { {1, 1}, SkFilterMode::kLinear,  false },
+    };
+
+    canvas->translate(10, 10);
+    canvas->scale(1.0f/3, 1.0f/3);
+    for (const auto& r : recs) {
+        SkAutoCanvasRestore acr(canvas, true);
+        canvas->translate(r.fLoc.fX * 330, r.fLoc.fY * 330);
+        SkPaint paint;
+        r.setup(&paint, pic);
+        canvas->drawRect({0, 0, 300, 300}, paint);
+    }
+}
diff --git a/include/core/SkPicture.h b/include/core/SkPicture.h
index 919840c..ebac1c9 100644
--- a/include/core/SkPicture.h
+++ b/include/core/SkPicture.h
@@ -10,6 +10,7 @@
 
 #include "include/core/SkRect.h"
 #include "include/core/SkRefCnt.h"
+#include "include/core/SkSamplingOptions.h"
 #include "include/core/SkTileMode.h"
 #include "include/core/SkTypes.h"
 
@@ -218,6 +219,7 @@
      *
      *  @param tmx  The tiling mode to use when sampling in the x-direction.
      *  @param tmy  The tiling mode to use when sampling in the y-direction.
+     *  @param mode How to filter the tiles
      *  @param localMatrix Optional matrix used when sampling
      *  @param tile The tile rectangle in picture coordinates: this represents the subset
      *              (or superset) of the picture used when building a tile. It is not
@@ -226,6 +228,10 @@
      *              bounds.
      *  @return     Returns a new shader object. Note: this function never returns null.
      */
+    sk_sp<SkShader> makeShader(SkTileMode tmx, SkTileMode tmy, SkFilterMode mode,
+                               const SkMatrix* localMatrix, const SkRect* tileRect) const;
+
+    // DEPRECATED
     sk_sp<SkShader> makeShader(SkTileMode tmx, SkTileMode tmy,
                                const SkMatrix* localMatrix, const SkRect* tileRect) const;
     sk_sp<SkShader> makeShader(SkTileMode tmx, SkTileMode tmy,
diff --git a/src/core/SkPicturePriv.h b/src/core/SkPicturePriv.h
index 9a40143..101f4c9 100644
--- a/src/core/SkPicturePriv.h
+++ b/src/core/SkPicturePriv.h
@@ -93,10 +93,11 @@
         kCubicResamplerImageShader_Version  = 79,
         kSamplingInImageShader_Version      = 80,
         kSamplingInDrawImage_Version        = 81,
+        kPictureShaderFilterParam_Version   = 82,
 
         // Only SKPs within the min/current picture version range (inclusive) can be read.
         kMin_Version     = kEdgeAAQuadColor4f_Version,
-        kCurrent_Version = kSamplingInDrawImage_Version
+        kCurrent_Version = kPictureShaderFilterParam_Version
     };
 
     static_assert(SkPicturePriv::kMin_Version <= SkPicturePriv::kCubicResamplerImageShader_Version,
diff --git a/src/shaders/SkPictureShader.cpp b/src/shaders/SkPictureShader.cpp
index 58a2ac1..3005e93 100644
--- a/src/shaders/SkPictureShader.cpp
+++ b/src/shaders/SkPictureShader.cpp
@@ -32,12 +32,22 @@
 #include "src/gpu/SkGr.h"
 #endif
 
+sk_sp<SkShader> SkPicture::makeShader(SkTileMode tmx, SkTileMode tmy, SkFilterMode filter,
+                                      const SkMatrix* localMatrix, const SkRect* tile) const {
+    if (localMatrix && !localMatrix->invert(nullptr)) {
+        return nullptr;
+    }
+    return SkPictureShader::Make(sk_ref_sp(this), tmx, tmy, (SkPictureShader::FilterEnum)filter,
+                                 localMatrix, tile);
+}
+
 sk_sp<SkShader> SkPicture::makeShader(SkTileMode tmx, SkTileMode tmy, const SkMatrix* localMatrix,
                                       const SkRect* tile) const {
     if (localMatrix && !localMatrix->invert(nullptr)) {
         return nullptr;
     }
-    return SkPictureShader::Make(sk_ref_sp(this), tmx, tmy, localMatrix, tile);
+    return SkPictureShader::Make(sk_ref_sp(this), tmx, tmy, SkPictureShader::kInheritFromPaint,
+                                 localMatrix, tile);
 }
 
 sk_sp<SkShader> SkPicture::makeShader(SkTileMode tmx, SkTileMode tmy,
@@ -123,12 +133,13 @@
 } // namespace
 
 SkPictureShader::SkPictureShader(sk_sp<SkPicture> picture, SkTileMode tmx, SkTileMode tmy,
-                                 const SkMatrix* localMatrix, const SkRect* tile)
+                                 FilterEnum filter, const SkMatrix* localMatrix, const SkRect* tile)
     : INHERITED(localMatrix)
     , fPicture(std::move(picture))
     , fTile(tile ? *tile : fPicture->cullRect())
     , fTmx(tmx)
     , fTmy(tmy)
+    , fFilter(filter)
     , fUniqueID(next_id())
     , fAddedToCache(false) {}
 
@@ -139,11 +150,11 @@
 }
 
 sk_sp<SkShader> SkPictureShader::Make(sk_sp<SkPicture> picture, SkTileMode tmx, SkTileMode tmy,
-                                      const SkMatrix* localMatrix, const SkRect* tile) {
+                                      FilterEnum filter, const SkMatrix* lm, const SkRect* tile) {
     if (!picture || picture->cullRect().isEmpty() || (tile && tile->isEmpty())) {
         return SkShaders::Empty();
     }
-    return sk_sp<SkShader>(new SkPictureShader(std::move(picture), tmx, tmy, localMatrix, tile));
+    return sk_sp<SkShader>(new SkPictureShader(std::move(picture), tmx, tmy, filter, lm, tile));
 }
 
 sk_sp<SkFlattenable> SkPictureShader::CreateProc(SkReadBuffer& buffer) {
@@ -151,16 +162,21 @@
     buffer.readMatrix(&lm);
     auto tmx = buffer.read32LE(SkTileMode::kLastTileMode);
     auto tmy = buffer.read32LE(SkTileMode::kLastTileMode);
-    SkRect tile;
-    buffer.readRect(&tile);
+    SkRect tile = buffer.readRect();
 
     sk_sp<SkPicture> picture;
-
-    bool didSerialize = buffer.readBool();
-    if (didSerialize) {
+    FilterEnum filter;
+    if (buffer.isVersionLT(SkPicturePriv::kPictureShaderFilterParam_Version)) {
+        filter = kInheritFromPaint;
+        bool didSerialize = buffer.readBool();
+        if (didSerialize) {
+            picture = SkPicturePriv::MakeFromBuffer(buffer);
+        }
+    } else {
+        filter = buffer.read32LE(SkPictureShader::kLastFilterEnum);
         picture = SkPicturePriv::MakeFromBuffer(buffer);
     }
-    return SkPictureShader::Make(picture, tmx, tmy, &lm, &tile);
+    return SkPictureShader::Make(picture, tmx, tmy, filter, &lm, &tile);
 }
 
 void SkPictureShader::flatten(SkWriteBuffer& buffer) const {
@@ -168,8 +184,7 @@
     buffer.write32((unsigned)fTmx);
     buffer.write32((unsigned)fTmy);
     buffer.writeRect(fTile);
-
-    buffer.writeBool(true);
+    buffer.write32((unsigned)fFilter);
     SkPicturePriv::Flatten(fPicture, buffer);
 }
 
@@ -179,6 +194,7 @@
                                                  SkTCopyOnFirstWrite<SkMatrix>* localMatrix,
                                                  SkColorType dstColorType,
                                                  SkColorSpace* dstColorSpace,
+                                                 SkFilterQuality paintFQ,
                                                  const int maxTextureSize) const {
     SkASSERT(fPicture && !fPicture->cullRect().isEmpty());
 
@@ -256,12 +272,13 @@
 #ifdef SK_SUPPORT_LEGACY_INHERITED_PICTURE_SHADER_FILTER
         tileShader = SkImage_makeShaderImplicitFilterQuality(tileImage.get(), fTmx, fTmy, nullptr);
 #else
-
-#ifdef SK_SUPPORT_NEAREST_PICTURESHADER_POSTFILTER
-        SkFilterMode filter = SkFilterMode::kNearest;
-#else
-        SkFilterMode filter = SkFilterMode::kLinear;
-#endif
+        SkFilterMode filter;
+        if (fFilter == kInheritFromPaint) {
+            filter = (paintFQ == kNone_SkFilterQuality) ? SkFilterMode::kNearest
+                                                        : SkFilterMode::kLinear;
+        } else {
+            filter = (SkFilterMode)fFilter;
+        }
         tileShader = tileImage->makeShader(fTmx, fTmy, SkSamplingOptions(filter), nullptr);
 #endif
 
@@ -282,7 +299,8 @@
     // Keep bitmapShader alive by using alloc instead of stack memory
     auto& bitmapShader = *rec.fAlloc->make<sk_sp<SkShader>>();
     bitmapShader = this->refBitmapShader(rec.fMatrixProvider.localToDevice(), &lm,
-                                         rec.fDstColorType, rec.fDstCS);
+                                         rec.fDstColorType, rec.fDstCS,
+                                         rec.fPaint.getFilterQuality());
 
     if (!bitmapShader) {
         return false;
@@ -304,7 +322,7 @@
     // Keep bitmapShader alive by using alloc instead of stack memory
     auto& bitmapShader = *alloc->make<sk_sp<SkShader>>();
     bitmapShader = this->refBitmapShader(matrices.localToDevice(), &lm,
-                                         dst.colorType(), dst.colorSpace());
+                                         dst.colorType(), dst.colorSpace(), quality);
     if (!bitmapShader) {
         return {};
     }
@@ -322,7 +340,9 @@
 const {
     auto lm = this->totalLocalMatrix(rec.fLocalMatrix);
     sk_sp<SkShader> bitmapShader = this->refBitmapShader(*rec.fMatrix, &lm, rec.fDstColorType,
-                                                         rec.fDstColorSpace);
+                                                         rec.fDstColorSpace,
+                                                         rec.fPaint ? rec.fPaint->getFilterQuality()
+                                                                    : kNone_SkFilterQuality);
     if (!bitmapShader) {
         return nullptr;
     }
@@ -363,6 +383,11 @@
 
 #if SK_SUPPORT_GPU
 
+// Legacy: reconstruct what was in the paint (just care about none/low)
+static SkFilterQuality toFilterQuality(const SkSamplingOptions& sampling) {
+    return sampling == SkSamplingOptions() ? kNone_SkFilterQuality : kLow_SkFilterQuality;
+}
+
 std::unique_ptr<GrFragmentProcessor> SkPictureShader::asFragmentProcessor(
         const GrFPArgs& args) const {
     int maxTextureSize = 0;
@@ -377,7 +402,8 @@
     }
     sk_sp<SkShader> bitmapShader(
             this->refBitmapShader(args.fMatrixProvider.localToDevice(), &lm, dstColorType,
-                                  args.fDstColorInfo->colorSpace(), maxTextureSize));
+                                  args.fDstColorInfo->colorSpace(), toFilterQuality(args.fSampling),
+                                  maxTextureSize));
     if (!bitmapShader) {
         return nullptr;
     }
diff --git a/src/shaders/SkPictureShader.h b/src/shaders/SkPictureShader.h
index 1ab69a2..0f86999 100644
--- a/src/shaders/SkPictureShader.h
+++ b/src/shaders/SkPictureShader.h
@@ -26,8 +26,16 @@
 public:
     ~SkPictureShader() override;
 
-    static sk_sp<SkShader> Make(sk_sp<SkPicture>, SkTileMode, SkTileMode, const SkMatrix*,
-                                const SkRect*);
+    enum FilterEnum {
+        kNearest,           // SkFilterMode::kNearest
+        kLinear,            // SkFilterMode::kLinear
+        kInheritFromPaint,
+
+        kLastFilterEnum = kInheritFromPaint,
+    };
+
+    static sk_sp<SkShader> Make(sk_sp<SkPicture>, SkTileMode, SkTileMode, FilterEnum,
+                                const SkMatrix*, const SkRect*);
 
 #if SK_SUPPORT_GPU
     std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&) const override;
@@ -49,10 +57,12 @@
 private:
     SK_FLATTENABLE_HOOKS(SkPictureShader)
 
-    SkPictureShader(sk_sp<SkPicture>, SkTileMode, SkTileMode, const SkMatrix*, const SkRect*);
+    SkPictureShader(sk_sp<SkPicture>, SkTileMode, SkTileMode, FilterEnum,
+                    const SkMatrix*, const SkRect*);
 
     sk_sp<SkShader> refBitmapShader(const SkMatrix&, SkTCopyOnFirstWrite<SkMatrix>* localMatrix,
                                     SkColorType dstColorType, SkColorSpace* dstColorSpace,
+                                    SkFilterQuality fromPaint,
                                     const int maxTextureSize = 0) const;
 
     class PictureShaderContext : public Context {
@@ -74,6 +84,7 @@
     sk_sp<SkPicture>    fPicture;
     SkRect              fTile;
     SkTileMode          fTmx, fTmy;
+    FilterEnum          fFilter;
 
     const uint32_t            fUniqueID;
     mutable std::atomic<bool> fAddedToCache;