Add clipShader with perspective GM

Bug: skia:10206
Change-Id: Iad24cb1134e8f501bce6434ea8511b21039abea2
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/293565
Commit-Queue: Mike Reed <reed@google.com>
Reviewed-by: Mike Reed <reed@google.com>
Auto-Submit: Michael Ludwig <michaelludwig@google.com>
diff --git a/gm/complexclip.cpp b/gm/complexclip.cpp
index 7974136..e6bef93 100644
--- a/gm/complexclip.cpp
+++ b/gm/complexclip.cpp
@@ -290,3 +290,156 @@
     canvas->drawRect(SkRect::MakeWH(w, h), p);
     canvas->restore();
 }
+
+namespace {
+
+// Where is canvas->concat(persp) called relative to the clipShader calls.
+enum ConcatPerspective {
+    kConcatBeforeClips,
+    kConcatAfterClips,
+    kConcatBetweenClips
+};
+// Order in which clipShader(image) and clipShader(gradient) are specified; only meaningful
+// when CanvasPerspective is kConcatBetweenClips.
+enum ClipOrder {
+    kClipImageFirst,
+    kClipGradientFirst,
+
+    kDoesntMatter = kClipImageFirst
+};
+// Which shaders have perspective applied as a local matrix.
+enum LocalMatrix {
+    kNoLocalMat,
+    kImageWithLocalMat,
+    kGradientWithLocalMat,
+    kBothWithLocalMat
+};
+struct Config {
+    ConcatPerspective fConcat;
+    ClipOrder         fOrder;
+    LocalMatrix       fLM;
+};
+
+static void draw_banner(SkCanvas* canvas, Config config) {
+    SkString banner;
+    banner.append("Persp: ");
+
+    if (config.fConcat == kConcatBeforeClips || config.fLM == kBothWithLocalMat) {
+        banner.append("Both Clips");
+    } else {
+        SkASSERT((config.fConcat == kConcatBetweenClips && config.fLM == kNoLocalMat) ||
+                 (config.fConcat == kConcatAfterClips && (config.fLM == kImageWithLocalMat ||
+                                                          config.fLM == kGradientWithLocalMat)));
+        if ((config.fConcat == kConcatBetweenClips && config.fOrder == kClipImageFirst) ||
+            config.fLM == kGradientWithLocalMat) {
+            banner.append("Gradient");
+        } else {
+            SkASSERT(config.fOrder == kClipGradientFirst || config.fLM == kImageWithLocalMat);
+            banner.append("Image");
+        }
+    }
+    if (config.fLM != kNoLocalMat) {
+        banner.append(" (w/ LM, should equal top row)");
+    }
+
+    static const SkFont kFont(ToolUtils::create_portable_typeface(), 12);
+    canvas->drawString(banner.c_str(), 20.f, -30.f, kFont, SkPaint());
+};
+
+} // anonymous
+
+DEF_SIMPLE_GM(clip_shader_persp, canvas, 1370, 1030) {
+    // Each draw has a clipShader(image-shader), a clipShader(gradient-shader), a concat(persp-mat),
+    // and each shader may or may not be wrapped with a perspective local matrix.
+
+    // Pairs of configs that should match in appearance where first config doesn't use a local
+    // matrix (top row of GM) and the second does (bottom row of GM).
+    Config matches[][2] = {
+            // Everything has perspective
+            {{kConcatBeforeClips,  kDoesntMatter,      kNoLocalMat},
+             {kConcatAfterClips,   kDoesntMatter,      kBothWithLocalMat}},
+            // Image shader has perspective
+            {{kConcatBetweenClips, kClipGradientFirst, kNoLocalMat},
+             {kConcatAfterClips,   kDoesntMatter,      kImageWithLocalMat}},
+            // Gradient shader has perspective
+            {{kConcatBetweenClips, kClipImageFirst,    kNoLocalMat},
+             {kConcatAfterClips,   kDoesntMatter,      kGradientWithLocalMat}}
+    };
+
+    // The image that is drawn
+    auto img = GetResourceAsImage("images/yellow_rose.png");
+    // Scale factor always applied to the image shader so that it tiles
+    SkMatrix scale = SkMatrix::Scale(1.f / 4.f, 1.f / 4.f);
+    // The perspective matrix applied wherever needed
+    SkPoint src[4];
+    SkRect::Make(img->dimensions()).toQuad(src);
+    SkPoint dst[4] = {{0, 80.f},
+                      {img->width() + 28.f, -100.f},
+                      {img->width() - 28.f, img->height() + 100.f},
+                      {0.f, img->height() - 80.f}};
+    SkMatrix persp;
+    SkAssertResult(persp.setPolyToPoly(src, dst, 4));
+
+    SkMatrix perspScale = SkMatrix::Concat(persp, scale);
+
+    auto drawConfig = [&](Config config) {
+        canvas->save();
+
+        draw_banner(canvas, config);
+
+        // Make clipShaders (possibly with local matrices)
+        bool gradLM = config.fLM == kGradientWithLocalMat || config.fLM == kBothWithLocalMat;
+        const SkColor gradColors[] = {SK_ColorBLACK, SkColorSetARGB(128, 128, 128, 128)};
+        auto gradShader = SkGradientShader::MakeRadial({0.5f * img->width(), 0.5f * img->height()},
+                                                        0.1f * img->width(), gradColors, nullptr, 2,
+                                                        SkTileMode::kRepeat, 0,
+                                                        gradLM ? &persp : nullptr);
+        bool imageLM = config.fLM == kImageWithLocalMat || config.fLM == kBothWithLocalMat;
+        auto imgShader = img->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat,
+                                         imageLM ? perspScale : scale);
+
+        // Perspective before any clipShader
+        if (config.fConcat == kConcatBeforeClips) {
+            canvas->concat(persp);
+        }
+
+        // First clipshader
+        canvas->clipShader(config.fOrder == kClipImageFirst ? imgShader : gradShader);
+
+        // Perspective between clipShader
+        if (config.fConcat == kConcatBetweenClips) {
+            canvas->concat(persp);
+        }
+
+        // Second clipShader
+        canvas->clipShader(config.fOrder == kClipImageFirst ? gradShader : imgShader);
+
+        // Perspective after clipShader
+        if (config.fConcat == kConcatAfterClips) {
+            canvas->concat(persp);
+        }
+
+        // Actual draw and clip boundary are the same for all configs
+        canvas->clipRect(SkRect::MakeIWH(img->width(), img->height()));
+        canvas->clear(SK_ColorBLACK);
+        canvas->drawImage(img, 0, 0);
+
+        canvas->restore();
+    };
+
+    SkIRect grid = persp.mapRect(SkRect::Make(img->dimensions())).roundOut();
+    grid.fLeft -= 20; // manual adjust to look nicer
+
+    canvas->translate(10.f, 10.f);
+
+    for (size_t i = 0; i < SK_ARRAY_COUNT(matches); ++i) {
+        canvas->save();
+        canvas->translate(-grid.fLeft, -grid.fTop);
+        drawConfig(matches[i][0]);
+        canvas->translate(0.f, grid.height());
+        drawConfig(matches[i][1]);
+        canvas->restore();
+
+        canvas->translate(grid.width(), 0.f);
+    }
+}