Visualize perspective scaling in filter bounds sample
Bug: skia:9074
Change-Id: Icdac64276e0a403950fd990a619d523b0ee784de
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/326942
Reviewed-by: Jim Van Verth <jvanverth@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
diff --git a/samplecode/SampleFilterBounds.cpp b/samplecode/SampleFilterBounds.cpp
index 48b31d3..56fc8bf 100644
--- a/samplecode/SampleFilterBounds.cpp
+++ b/samplecode/SampleFilterBounds.cpp
@@ -15,10 +15,12 @@
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "include/effects/SkDashPathEffect.h"
+#include "include/effects/SkGradientShader.h"
#include "include/effects/SkImageFilters.h"
#include "src/core/SkImageFilterTypes.h"
#include "src/core/SkImageFilter_Base.h"
+#include "src/core/SkMatrixPriv.h"
#include "tools/ToolUtils.h"
@@ -58,6 +60,17 @@
return y;
}
+static void print_label(SkCanvas* canvas, float x, float y, float value) {
+ SkFont font(nullptr, 12);
+ SkPaint text;
+ text.setAntiAlias(true);
+
+ SkString label;
+ label.printf("%.3f", value);
+
+ canvas->drawString(label, x, y + kLineHeight / 2.f, font, text);
+}
+
static SkPaint line_paint(SkColor color, bool dashed = false) {
SkPaint paint;
paint.setColor(color);
@@ -84,6 +97,71 @@
return localSpace;
}
+static const SkColor4f kScaleGradientColors[] =
+ { { 0.05f, 0.0f, 6.f, 1.f }, // Severe downscaling, s < 1/8, log(s) < -3
+ { 0.6f, 0.6f, 0.8f, 0.6f }, // Okay downscaling, s < 1/2, log(s) < -1
+ { 1.f, 1.f, 1.f, 0.2f }, // No scaling, s = 1, log(s) = 0
+ { 0.95f, 0.6f, 0.5f, 0.6f }, // Okay upscaling, s > 2, log(s) > 1
+ { 0.8f, 0.1f, 0.f, 1.f } }; // Severe upscaling, s > 8, log(s) > 3
+static const SkScalar kLogScaleFactors[] = { -3.f, -1.f, 0.f, 1.f, 3.f };
+static const SkScalar kGradientStops[] = { 0.f, 0.33333f, 0.5f, 0.66667f, 1.f };
+static const int kStopCount = (int) SK_ARRAY_COUNT(kScaleGradientColors);
+
+static void draw_scale_key(SkCanvas* canvas, float y) {
+ SkRect key = SkRect::MakeXYWH(15.f, y + 30.f, 15.f, 100.f);
+ SkPoint pts[] = {{key.centerX(), key.fTop}, {key.centerX(), key.fBottom}};
+ sk_sp<SkShader> gradient = SkGradientShader::MakeLinear(
+ pts, kScaleGradientColors, nullptr, kGradientStops, kStopCount, SkTileMode::kClamp,
+ SkGradientShader::kInterpolateColorsInPremul_Flag, nullptr);
+ SkPaint keyPaint;
+ keyPaint.setShader(gradient);
+ canvas->drawRect(key, keyPaint);
+ for (int i = 0; i < kStopCount; ++i) {
+ print_label(canvas, key.fRight + 5.f, key.fTop + kGradientStops[i] * key.height(),
+ SkScalarPow(2.f, kLogScaleFactors[i]));
+ }
+}
+
+static void draw_scale_factors(SkCanvas* canvas, const skif::Mapping& mapping, const SkRect& rect) {
+ SkPoint testPoints[5];
+ testPoints[0] = {rect.centerX(), rect.centerY()};
+ rect.toQuad(testPoints + 1);
+ for (int i = 0; i < 5; ++i) {
+ float scale = SkMatrixPriv::DifferentialScale(
+ mapping.deviceMatrix(),
+ SkPoint(mapping.paramToLayer(skif::ParameterSpace<SkPoint>(testPoints[i]))));
+ SkColor4f color = {0.f, 0.f, 0.f, 1.f};
+
+ if (SkScalarIsFinite(scale)) {
+ float logScale = SkScalarLog2(scale);
+ for (int j = 0; j <= kStopCount; ++j) {
+ if (j == kStopCount) {
+ color = kScaleGradientColors[j - 1];
+ break;
+ } else if (kLogScaleFactors[j] >= logScale) {
+ if (j == 0) {
+ color = kScaleGradientColors[0];
+ } else {
+ SkScalar t = (logScale - kLogScaleFactors[j - 1]) /
+ (kLogScaleFactors[j] - kLogScaleFactors[j - 1]);
+
+ SkColor4f a = kScaleGradientColors[j - 1] * (1.f - t);
+ SkColor4f b = kScaleGradientColors[j] * t;
+ color = {a.fR + b.fR, a.fG + b.fG, a.fB + b.fB, a.fA + b.fA};
+ }
+ break;
+ }
+ }
+ }
+
+ SkPaint p;
+ p.setAntiAlias(true);
+ p.setColor4f(color, nullptr);
+ canvas->drawRect(SkRect::MakeLTRB(testPoints[i].fX - 4.f, testPoints[i].fY - 4.f,
+ testPoints[i].fX + 4.f, testPoints[i].fY + 4.f), p);
+ }
+}
+
class FilterBoundsSample : public Sample {
public:
FilterBoundsSample() {}
@@ -114,6 +192,9 @@
// Add axis lines, to show perspective distortion
canvas->drawPath(create_axis_path(localContentRect, 10.f), line_paint(SK_ColorGRAY));
+ // Visualize scale factors at the four corners and center of the local rect
+ draw_scale_factors(canvas, mapping, localContentRect);
+
// The device content rect, e.g. the clip bounds if 'localContentRect' were used as a clip
// before the draw or saveLayer, representing what the filter must cover if it affects
// transparent black or doesn't have a local content hint.
@@ -146,10 +227,13 @@
canvas->drawRect(SkRect::Make(SkIRect(hintedOutputBounds)), line_paint(SK_ColorBLUE));
canvas->resetMatrix();
- print_info(canvas, SkIRect(mapping.paramToLayer(contentBounds).roundOut()),
+ float y = print_info(canvas, SkIRect(mapping.paramToLayer(contentBounds).roundOut()),
devContentRect.roundOut(),
SkIRect(hintedOutputBounds),
SkIRect(unhintedLayerBounds));
+
+ // Draw color key for layer visualization
+ draw_scale_key(canvas, y);
}
SkString name() override { return SkString("FilterBounds"); }
diff --git a/src/core/SkMatrix.cpp b/src/core/SkMatrix.cpp
index 38de466..fbd8c5a 100644
--- a/src/core/SkMatrix.cpp
+++ b/src/core/SkMatrix.cpp
@@ -720,25 +720,27 @@
return SkDoubleToScalar(dcross(a, b, c, d) * scale);
}
-static double sk_inv_determinant(const float mat[9], int isPerspective) {
- double det;
-
+static double sk_determinant(const float mat[9], int isPerspective) {
if (isPerspective) {
- det = mat[SkMatrix::kMScaleX] *
- dcross(mat[SkMatrix::kMScaleY], mat[SkMatrix::kMPersp2],
- mat[SkMatrix::kMTransY], mat[SkMatrix::kMPersp1])
- +
- mat[SkMatrix::kMSkewX] *
- dcross(mat[SkMatrix::kMTransY], mat[SkMatrix::kMPersp0],
- mat[SkMatrix::kMSkewY], mat[SkMatrix::kMPersp2])
- +
- mat[SkMatrix::kMTransX] *
- dcross(mat[SkMatrix::kMSkewY], mat[SkMatrix::kMPersp1],
- mat[SkMatrix::kMScaleY], mat[SkMatrix::kMPersp0]);
+ return mat[SkMatrix::kMScaleX] *
+ dcross(mat[SkMatrix::kMScaleY], mat[SkMatrix::kMPersp2],
+ mat[SkMatrix::kMTransY], mat[SkMatrix::kMPersp1])
+ +
+ mat[SkMatrix::kMSkewX] *
+ dcross(mat[SkMatrix::kMTransY], mat[SkMatrix::kMPersp0],
+ mat[SkMatrix::kMSkewY], mat[SkMatrix::kMPersp2])
+ +
+ mat[SkMatrix::kMTransX] *
+ dcross(mat[SkMatrix::kMSkewY], mat[SkMatrix::kMPersp1],
+ mat[SkMatrix::kMScaleY], mat[SkMatrix::kMPersp0]);
} else {
- det = dcross(mat[SkMatrix::kMScaleX], mat[SkMatrix::kMScaleY],
- mat[SkMatrix::kMSkewX], mat[SkMatrix::kMSkewY]);
+ return dcross(mat[SkMatrix::kMScaleX], mat[SkMatrix::kMScaleY],
+ mat[SkMatrix::kMSkewX], mat[SkMatrix::kMSkewY]);
}
+}
+
+static double sk_inv_determinant(const float mat[9], int isPerspective) {
+ double det = sk_determinant(mat, isPerspective);
// Since the determinant is on the order of the cube of the matrix members,
// compare to the cube of the default nearly-zero constant (although an
@@ -1855,3 +1857,38 @@
return kHigh_SkFilterQuality;
}
+
+SkScalar SkMatrixPriv::DifferentialScale(const SkMatrix& m, const SkPoint& p) {
+ // [m00 m01 m02] [f(u,v)]
+ // Assuming M = [m10 m11 m12], define the projected p'(u,v) = [g(u,v)] where
+ // [m20 m12 m22]
+ // [x] [u]
+ // f(u,v) = x(u,v) / w(u,v), g(u,v) = y(u,v) / w(u,v) and [y] = M*[v]
+ // [w] [1]
+ //
+ // Then the differential scale factor between p = (u,v) and p' is |det J|,
+ // where J is the Jacobian for p': [df/du dg/du]
+ // [df/dv dg/dv]
+ // and df/du = (w*dx/du - x*dw/du)/w^2, dg/du = (w*dy/du - y*dw/du)/w^2
+ // df/dv = (w*dx/dv - x*dw/dv)/w^2, dg/dv = (w*dy/dv - y*dw/dv)/w^2
+ //
+ // From here, |det J| can be rewritten as |det J'/w^3|, where
+ // [x y w ] [x y w ]
+ // J' = [dx/du dy/du dw/du] = [m00 m10 m20]
+ // [dx/dv dy/dv dw/dv] [m01 m11 m21]
+ SkPoint3 xyw;
+ m.mapHomogeneousPoints(&xyw, &p, 1);
+
+ if (xyw.fZ < SK_ScalarNearlyZero) {
+ // Reaching the discontinuity of xy/w and where the point would clip to w >= 0
+ return SK_ScalarInfinity;
+ }
+ SkMatrix jacobian = SkMatrix::MakeAll(xyw.fX, xyw.fY, xyw.fZ,
+ m.getScaleX(), m.getSkewY(), m.getPerspX(),
+ m.getSkewX(), m.getScaleY(), m.getPerspY());
+
+ SkScalar denom = 1.f / xyw.fZ; // 1/w
+ denom = denom * denom * denom; // 1/w^3
+
+ return SkScalarAbs(SkDoubleToScalar(sk_determinant(jacobian.fMat, true)) * denom);
+}
diff --git a/src/core/SkMatrixPriv.h b/src/core/SkMatrixPriv.h
index bec7232..d90f0b7 100644
--- a/src/core/SkMatrixPriv.h
+++ b/src/core/SkMatrixPriv.h
@@ -171,6 +171,15 @@
m.rc(3,3) == 1;
}
+
+ // Returns the differential area scale factor for a local point 'p' that will be transformed
+ // by 'm' (which may have perspective). If 'm' does not have perspective, this scale factor is
+ // constant regardless of 'p'; when it does have perspective, it is specific to that point.
+ //
+ // This can be crudely thought of as "device pixel area" / "local pixel area" at 'p'.
+ //
+ // Returns positive infinity if the transformed homogeneous point has w <= 0.
+ static SkScalar DifferentialScale(const SkMatrix& m, const SkPoint& p);
};
#endif