Skip to content

Commit fd0b158

Browse files
lhkbobSkia Commit-Bot
authored andcommitted
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 <[email protected]> Commit-Queue: Michael Ludwig <[email protected]>
1 parent 712d3a5 commit fd0b158

File tree

3 files changed

+147
-17
lines changed

3 files changed

+147
-17
lines changed

samplecode/SampleFilterBounds.cpp

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
#include "include/core/SkPoint.h"
1616
#include "include/core/SkRect.h"
1717
#include "include/effects/SkDashPathEffect.h"
18+
#include "include/effects/SkGradientShader.h"
1819
#include "include/effects/SkImageFilters.h"
1920

2021
#include "src/core/SkImageFilterTypes.h"
2122
#include "src/core/SkImageFilter_Base.h"
23+
#include "src/core/SkMatrixPriv.h"
2224

2325
#include "tools/ToolUtils.h"
2426

@@ -58,6 +60,17 @@ static float print_info(SkCanvas* canvas,
5860
return y;
5961
}
6062

63+
static void print_label(SkCanvas* canvas, float x, float y, float value) {
64+
SkFont font(nullptr, 12);
65+
SkPaint text;
66+
text.setAntiAlias(true);
67+
68+
SkString label;
69+
label.printf("%.3f", value);
70+
71+
canvas->drawString(label, x, y + kLineHeight / 2.f, font, text);
72+
}
73+
6174
static SkPaint line_paint(SkColor color, bool dashed = false) {
6275
SkPaint paint;
6376
paint.setColor(color);
@@ -84,6 +97,71 @@ static SkPath create_axis_path(const SkRect& rect, float axisSpace) {
8497
return localSpace;
8598
}
8699

100+
static const SkColor4f kScaleGradientColors[] =
101+
{ { 0.05f, 0.0f, 6.f, 1.f }, // Severe downscaling, s < 1/8, log(s) < -3
102+
{ 0.6f, 0.6f, 0.8f, 0.6f }, // Okay downscaling, s < 1/2, log(s) < -1
103+
{ 1.f, 1.f, 1.f, 0.2f }, // No scaling, s = 1, log(s) = 0
104+
{ 0.95f, 0.6f, 0.5f, 0.6f }, // Okay upscaling, s > 2, log(s) > 1
105+
{ 0.8f, 0.1f, 0.f, 1.f } }; // Severe upscaling, s > 8, log(s) > 3
106+
static const SkScalar kLogScaleFactors[] = { -3.f, -1.f, 0.f, 1.f, 3.f };
107+
static const SkScalar kGradientStops[] = { 0.f, 0.33333f, 0.5f, 0.66667f, 1.f };
108+
static const int kStopCount = (int) SK_ARRAY_COUNT(kScaleGradientColors);
109+
110+
static void draw_scale_key(SkCanvas* canvas, float y) {
111+
SkRect key = SkRect::MakeXYWH(15.f, y + 30.f, 15.f, 100.f);
112+
SkPoint pts[] = {{key.centerX(), key.fTop}, {key.centerX(), key.fBottom}};
113+
sk_sp<SkShader> gradient = SkGradientShader::MakeLinear(
114+
pts, kScaleGradientColors, nullptr, kGradientStops, kStopCount, SkTileMode::kClamp,
115+
SkGradientShader::kInterpolateColorsInPremul_Flag, nullptr);
116+
SkPaint keyPaint;
117+
keyPaint.setShader(gradient);
118+
canvas->drawRect(key, keyPaint);
119+
for (int i = 0; i < kStopCount; ++i) {
120+
print_label(canvas, key.fRight + 5.f, key.fTop + kGradientStops[i] * key.height(),
121+
SkScalarPow(2.f, kLogScaleFactors[i]));
122+
}
123+
}
124+
125+
static void draw_scale_factors(SkCanvas* canvas, const skif::Mapping& mapping, const SkRect& rect) {
126+
SkPoint testPoints[5];
127+
testPoints[0] = {rect.centerX(), rect.centerY()};
128+
rect.toQuad(testPoints + 1);
129+
for (int i = 0; i < 5; ++i) {
130+
float scale = SkMatrixPriv::DifferentialScale(
131+
mapping.deviceMatrix(),
132+
SkPoint(mapping.paramToLayer(skif::ParameterSpace<SkPoint>(testPoints[i]))));
133+
SkColor4f color = {0.f, 0.f, 0.f, 1.f};
134+
135+
if (SkScalarIsFinite(scale)) {
136+
float logScale = SkScalarLog2(scale);
137+
for (int j = 0; j <= kStopCount; ++j) {
138+
if (j == kStopCount) {
139+
color = kScaleGradientColors[j - 1];
140+
break;
141+
} else if (kLogScaleFactors[j] >= logScale) {
142+
if (j == 0) {
143+
color = kScaleGradientColors[0];
144+
} else {
145+
SkScalar t = (logScale - kLogScaleFactors[j - 1]) /
146+
(kLogScaleFactors[j] - kLogScaleFactors[j - 1]);
147+
148+
SkColor4f a = kScaleGradientColors[j - 1] * (1.f - t);
149+
SkColor4f b = kScaleGradientColors[j] * t;
150+
color = {a.fR + b.fR, a.fG + b.fG, a.fB + b.fB, a.fA + b.fA};
151+
}
152+
break;
153+
}
154+
}
155+
}
156+
157+
SkPaint p;
158+
p.setAntiAlias(true);
159+
p.setColor4f(color, nullptr);
160+
canvas->drawRect(SkRect::MakeLTRB(testPoints[i].fX - 4.f, testPoints[i].fY - 4.f,
161+
testPoints[i].fX + 4.f, testPoints[i].fY + 4.f), p);
162+
}
163+
}
164+
87165
class FilterBoundsSample : public Sample {
88166
public:
89167
FilterBoundsSample() {}
@@ -114,6 +192,9 @@ class FilterBoundsSample : public Sample {
114192
// Add axis lines, to show perspective distortion
115193
canvas->drawPath(create_axis_path(localContentRect, 10.f), line_paint(SK_ColorGRAY));
116194

195+
// Visualize scale factors at the four corners and center of the local rect
196+
draw_scale_factors(canvas, mapping, localContentRect);
197+
117198
// The device content rect, e.g. the clip bounds if 'localContentRect' were used as a clip
118199
// before the draw or saveLayer, representing what the filter must cover if it affects
119200
// transparent black or doesn't have a local content hint.
@@ -146,10 +227,13 @@ class FilterBoundsSample : public Sample {
146227
canvas->drawRect(SkRect::Make(SkIRect(hintedOutputBounds)), line_paint(SK_ColorBLUE));
147228

148229
canvas->resetMatrix();
149-
print_info(canvas, SkIRect(mapping.paramToLayer(contentBounds).roundOut()),
230+
float y = print_info(canvas, SkIRect(mapping.paramToLayer(contentBounds).roundOut()),
150231
devContentRect.roundOut(),
151232
SkIRect(hintedOutputBounds),
152233
SkIRect(unhintedLayerBounds));
234+
235+
// Draw color key for layer visualization
236+
draw_scale_key(canvas, y);
153237
}
154238

155239
SkString name() override { return SkString("FilterBounds"); }

src/core/SkMatrix.cpp

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -720,25 +720,27 @@ static inline SkScalar dcross_dscale(double a, double b,
720720
return SkDoubleToScalar(dcross(a, b, c, d) * scale);
721721
}
722722

723-
static double sk_inv_determinant(const float mat[9], int isPerspective) {
724-
double det;
725-
723+
static double sk_determinant(const float mat[9], int isPerspective) {
726724
if (isPerspective) {
727-
det = mat[SkMatrix::kMScaleX] *
728-
dcross(mat[SkMatrix::kMScaleY], mat[SkMatrix::kMPersp2],
729-
mat[SkMatrix::kMTransY], mat[SkMatrix::kMPersp1])
730-
+
731-
mat[SkMatrix::kMSkewX] *
732-
dcross(mat[SkMatrix::kMTransY], mat[SkMatrix::kMPersp0],
733-
mat[SkMatrix::kMSkewY], mat[SkMatrix::kMPersp2])
734-
+
735-
mat[SkMatrix::kMTransX] *
736-
dcross(mat[SkMatrix::kMSkewY], mat[SkMatrix::kMPersp1],
737-
mat[SkMatrix::kMScaleY], mat[SkMatrix::kMPersp0]);
725+
return mat[SkMatrix::kMScaleX] *
726+
dcross(mat[SkMatrix::kMScaleY], mat[SkMatrix::kMPersp2],
727+
mat[SkMatrix::kMTransY], mat[SkMatrix::kMPersp1])
728+
+
729+
mat[SkMatrix::kMSkewX] *
730+
dcross(mat[SkMatrix::kMTransY], mat[SkMatrix::kMPersp0],
731+
mat[SkMatrix::kMSkewY], mat[SkMatrix::kMPersp2])
732+
+
733+
mat[SkMatrix::kMTransX] *
734+
dcross(mat[SkMatrix::kMSkewY], mat[SkMatrix::kMPersp1],
735+
mat[SkMatrix::kMScaleY], mat[SkMatrix::kMPersp0]);
738736
} else {
739-
det = dcross(mat[SkMatrix::kMScaleX], mat[SkMatrix::kMScaleY],
740-
mat[SkMatrix::kMSkewX], mat[SkMatrix::kMSkewY]);
737+
return dcross(mat[SkMatrix::kMScaleX], mat[SkMatrix::kMScaleY],
738+
mat[SkMatrix::kMSkewX], mat[SkMatrix::kMSkewY]);
741739
}
740+
}
741+
742+
static double sk_inv_determinant(const float mat[9], int isPerspective) {
743+
double det = sk_determinant(mat, isPerspective);
742744

743745
// Since the determinant is on the order of the cube of the matrix members,
744746
// compare to the cube of the default nearly-zero constant (although an
@@ -1855,3 +1857,38 @@ SkFilterQuality SkMatrixPriv::AdjustHighQualityFilterLevel(const SkMatrix& matri
18551857

18561858
return kHigh_SkFilterQuality;
18571859
}
1860+
1861+
SkScalar SkMatrixPriv::DifferentialScale(const SkMatrix& m, const SkPoint& p) {
1862+
// [m00 m01 m02] [f(u,v)]
1863+
// Assuming M = [m10 m11 m12], define the projected p'(u,v) = [g(u,v)] where
1864+
// [m20 m12 m22]
1865+
// [x] [u]
1866+
// f(u,v) = x(u,v) / w(u,v), g(u,v) = y(u,v) / w(u,v) and [y] = M*[v]
1867+
// [w] [1]
1868+
//
1869+
// Then the differential scale factor between p = (u,v) and p' is |det J|,
1870+
// where J is the Jacobian for p': [df/du dg/du]
1871+
// [df/dv dg/dv]
1872+
// and df/du = (w*dx/du - x*dw/du)/w^2, dg/du = (w*dy/du - y*dw/du)/w^2
1873+
// df/dv = (w*dx/dv - x*dw/dv)/w^2, dg/dv = (w*dy/dv - y*dw/dv)/w^2
1874+
//
1875+
// From here, |det J| can be rewritten as |det J'/w^3|, where
1876+
// [x y w ] [x y w ]
1877+
// J' = [dx/du dy/du dw/du] = [m00 m10 m20]
1878+
// [dx/dv dy/dv dw/dv] [m01 m11 m21]
1879+
SkPoint3 xyw;
1880+
m.mapHomogeneousPoints(&xyw, &p, 1);
1881+
1882+
if (xyw.fZ < SK_ScalarNearlyZero) {
1883+
// Reaching the discontinuity of xy/w and where the point would clip to w >= 0
1884+
return SK_ScalarInfinity;
1885+
}
1886+
SkMatrix jacobian = SkMatrix::MakeAll(xyw.fX, xyw.fY, xyw.fZ,
1887+
m.getScaleX(), m.getSkewY(), m.getPerspX(),
1888+
m.getSkewX(), m.getScaleY(), m.getPerspY());
1889+
1890+
SkScalar denom = 1.f / xyw.fZ; // 1/w
1891+
denom = denom * denom * denom; // 1/w^3
1892+
1893+
return SkScalarAbs(SkDoubleToScalar(sk_determinant(jacobian.fMat, true)) * denom);
1894+
}

src/core/SkMatrixPriv.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,15 @@ class SkMatrixPriv {
171171
m.rc(3,3) == 1;
172172

173173
}
174+
175+
// Returns the differential area scale factor for a local point 'p' that will be transformed
176+
// by 'm' (which may have perspective). If 'm' does not have perspective, this scale factor is
177+
// constant regardless of 'p'; when it does have perspective, it is specific to that point.
178+
//
179+
// This can be crudely thought of as "device pixel area" / "local pixel area" at 'p'.
180+
//
181+
// Returns positive infinity if the transformed homogeneous point has w <= 0.
182+
static SkScalar DifferentialScale(const SkMatrix& m, const SkPoint& p);
174183
};
175184

176185
#endif

0 commit comments

Comments
 (0)