Avoid use of SkMatrixImageFilter to handle CTM for backdrop filtering
The DrawDeviceWithFilter function used to rely on SkImageFilter's
applyCTMForBackdrop, which would create a new DAG and use an
SkMatrixImageFilter node to account for the transformation. However,
that filter is really meant to perform a transformation prior to the
CTM matrix. Abusing matrix math to instead have it map the backdrop
contents back into the CTM local space grossly inflated the size of the
temporary images needed when under many perspective transforms.
This CL instead takes over control of the CTM decomposition and draws
the backdrop (src) content into a temporary layer directly, and then
feeds that through the image filtering. The code is somewhat cumbersome
to follow because of the combination of coordinate spaces and need to
make everything relative to the top left corner of the images.
When the rest of my image filter refactor is more underway, I believe that
a lot of the coordinate space math and image origin accounting will
be much cleaner, but the overall backdrop filtering strategy would remain
similar to this new implementation.
Bug: skia:9074
Change-Id: Ibd2995e81d2b308c74b2d298da5e38daa74c8677
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/239108
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
diff --git a/samplecode/SampleBackdropBounds.cpp b/samplecode/SampleBackdropBounds.cpp
new file mode 100644
index 0000000..a42f830
--- /dev/null
+++ b/samplecode/SampleBackdropBounds.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "samplecode/Sample.h"
+
+#include "include/core/SkCanvas.h"
+#include "include/core/SkColor.h"
+#include "include/core/SkFont.h"
+#include "include/core/SkPaint.h"
+#include "include/core/SkPath.h"
+#include "include/core/SkPoint.h"
+#include "include/core/SkRect.h"
+
+#include "tools/ToolUtils.h"
+
+namespace {
+
+} // anonymous namespace
+
+static constexpr float kLineHeight = 16.f;
+static constexpr float kLineInset = 8.f;
+
+static float print_size(SkCanvas* canvas, const char* prefix, const SkIRect& rect,
+ float x, float y, const SkFont& font, const SkPaint& paint) {
+ canvas->drawString(prefix, x, y, font, paint);
+ y += kLineHeight;
+ SkString sz;
+ sz.appendf("%d x %d", rect.width(), rect.height());
+ canvas->drawString(sz, x, y, font, paint);
+ return y + kLineHeight;
+}
+
+static float print_info(SkCanvas* canvas, const SkIRect& origLayerBounds, const SkIRect& localLayerBounds,
+ const SkIRect& filterInputBounds, const SkIRect& devLayerBounds) {
+ SkFont font(nullptr, 12);
+ SkPaint text;
+ text.setAntiAlias(true);
+
+ float y = kLineHeight;
+
+ text.setColor(SK_ColorBLACK);
+ y = print_size(canvas, "Orig layer", origLayerBounds, kLineInset, y, font, text);
+ text.setColor(SK_ColorRED);
+ y = print_size(canvas, "Filter layer", localLayerBounds, kLineInset, y, font, text);
+ text.setColor(SK_ColorBLUE);
+ y = print_size(canvas, "Filter input", filterInputBounds, kLineInset, y, font, text);
+ text.setColor(SK_ColorMAGENTA);
+ y = print_size(canvas, "Backdrop size", devLayerBounds, kLineInset, y, font, text);
+
+ return y;
+}
+
+static SkPaint line_paint(SkScalar width, SkColor color) {
+ SkPaint paint;
+ paint.setColor(color);
+ paint.setStrokeWidth(width);
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setAntiAlias(true);
+ return paint;
+}
+
+class BackdropBoundsSample : public Sample {
+public:
+ BackdropBoundsSample() {}
+
+ void onDrawContent(SkCanvas* canvas) override {
+ SkMatrix ctm = canvas->getTotalMatrix();
+
+ // This decomposition is for the backdrop filtering, and does not represent the CTM that
+ // the layer actually uses (unless it also has a filter during restore).
+ SkMatrix toGlobal, layerMatrix;
+ SkSize scale;
+ if (ctm.isScaleTranslate()) {
+ // No decomposition needed
+ toGlobal = SkMatrix::I();
+ layerMatrix = ctm;
+ } else if (ctm.decomposeScale(&scale, &toGlobal)) {
+ layerMatrix = SkMatrix::MakeScale(scale.fWidth, scale.fHeight);
+ } else {
+ toGlobal = ctm;
+ layerMatrix = SkMatrix::I();
+ }
+
+ SkMatrix fromGlobal;
+ if (!toGlobal.invert(&fromGlobal)) {
+ SkDebugf("Unable to invert CTM\n");
+ return;
+ }
+
+ // The local content, e.g. what would be submitted to drawRect
+ const SkRect localContentRect = SkRect::MakeLTRB(45.5f, 23.123f, 150.f, 140.45f);
+ canvas->drawRect(localContentRect, line_paint(0.f, SK_ColorBLACK));
+
+ canvas->save();
+ // The layer bounds of the content, this is the size of the actual layer and does not
+ // reflect the backdrop specific decomposition.
+ canvas->setMatrix(SkMatrix::I());
+ SkIRect origLayerBounds = ctm.mapRect(localContentRect).roundOut();
+ canvas->drawRect(SkRect::Make(origLayerBounds), line_paint(1.f, SK_ColorBLACK));
+
+ // Have to undo the full CTM transform on the layer bounds to get the layer bounds
+ // for the specific backdrop filter decomposition
+ canvas->setMatrix(toGlobal);
+ SkIRect layerBounds = fromGlobal.mapRect(SkRect::Make(origLayerBounds)).roundOut();
+ canvas->drawRect(SkRect::Make(layerBounds), line_paint(0.5f, SK_ColorRED));
+
+ // Input bounds for the backdrop filter to cover the actual layer bounds (emulate some
+ // blur that must outset by 5px for reading on the edge).
+ SkIRect filterInputBounds = layerBounds;
+ filterInputBounds.outset(5, 5);
+ canvas->drawRect(SkRect::Make(filterInputBounds), line_paint(1.f, SK_ColorBLUE));
+
+
+ // The destination bounds that must be snapped in order to transform and fill the
+ // filterInputBounds
+ canvas->setMatrix(SkMatrix::I());
+ SkIRect devLayerBounds = toGlobal.mapRect(SkRect::Make(filterInputBounds)).roundOut();
+ canvas->drawRect(SkRect::Make(devLayerBounds), line_paint(2.f, SK_ColorMAGENTA));
+
+ // The destination bounds mapped back into the layer space, which should cover 'layerBounds'
+ SkPath backdropCoveringBounds;
+
+ // Add axis lines, to show perspective distortion
+ SkIRect local = fromGlobal.mapRect(SkRect::Make(devLayerBounds)).roundOut();
+ static int kAxisSpace = 10;
+ for (int y = local.fTop + kAxisSpace; y <= local.fBottom - kAxisSpace; y += kAxisSpace) {
+ backdropCoveringBounds.moveTo(local.fLeft, y);
+ backdropCoveringBounds.lineTo(local.fRight, y);
+ }
+ for (int x = local.fLeft + kAxisSpace; x <= local.fRight - kAxisSpace; x += kAxisSpace) {
+ backdropCoveringBounds.moveTo(x, local.fTop);
+ backdropCoveringBounds.lineTo(x, local.fBottom);
+ }
+
+ canvas->setMatrix(toGlobal);
+ canvas->drawPath(backdropCoveringBounds, line_paint(0.f, SK_ColorGREEN));
+
+ canvas->resetMatrix();
+ print_info(canvas, origLayerBounds, layerBounds, filterInputBounds, devLayerBounds);
+
+ canvas->restore();
+ }
+
+ SkString name() override { return SkString("BackdropBounds"); }
+
+private:
+
+ typedef Sample INHERITED;
+};
+
+DEF_SAMPLE(return new BackdropBoundsSample();)