1 /*
2 * Copyright 2017 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "include/core/SkCanvas.h"
9 #include "include/core/SkPath.h"
10 #include "include/core/SkVertices.h"
11 #include "include/utils/SkShadowUtils.h"
12 #include "src/core/SkDrawShadowInfo.h"
13 #include "src/core/SkVerticesPriv.h"
14 #include "src/utils/SkShadowTessellator.h"
15 #include "tests/Test.h"
16
17 enum ExpectVerts {
18 kDont_ExpectVerts,
19 kDo_ExpectVerts
20 };
21
check_result(skiatest::Reporter * reporter,sk_sp<SkVertices> verts,ExpectVerts expectVerts)22 void check_result(skiatest::Reporter* reporter, sk_sp<SkVertices> verts, ExpectVerts expectVerts) {
23 const bool expectSuccess = expectVerts == kDo_ExpectVerts;
24 if (expectSuccess != SkToBool(verts)) {
25 ERRORF(reporter, "Expected shadow tessellation to %s but it did not.",
26 expectSuccess ? "succeed" : "fail");
27 }
28 if (SkToBool(verts)) {
29 if (kDont_ExpectVerts == expectVerts && verts->priv().vertexCount()) {
30 ERRORF(reporter, "Expected shadow tessellation to generate no vertices but it did.");
31 } else if (kDo_ExpectVerts == expectVerts && !verts->priv().vertexCount()) {
32 ERRORF(reporter, "Expected shadow tessellation to generate vertices but it didn't.");
33 }
34 }
35 }
36
tessellate_shadow(skiatest::Reporter * reporter,const SkPath & path,const SkMatrix & ctm,const SkPoint3 & heightParams,ExpectVerts expectVerts)37 void tessellate_shadow(skiatest::Reporter* reporter, const SkPath& path, const SkMatrix& ctm,
38 const SkPoint3& heightParams, ExpectVerts expectVerts) {
39
40 auto verts = SkShadowTessellator::MakeAmbient(path, ctm, heightParams, true);
41 check_result(reporter, verts, expectVerts);
42
43 verts = SkShadowTessellator::MakeAmbient(path, ctm, heightParams, false);
44 check_result(reporter, verts, expectVerts);
45
46 verts = SkShadowTessellator::MakeSpot(path, ctm, heightParams, {0, 0, 128}, 128.f, true, false);
47 check_result(reporter, verts, expectVerts);
48
49 verts = SkShadowTessellator::MakeSpot(path, ctm, heightParams, {0, 0, 128}, 128.f, false,
50 false);
51 check_result(reporter, verts, expectVerts);
52
53 verts = SkShadowTessellator::MakeSpot(path, ctm, heightParams, {0, 0, 128}, 128.f, true, true);
54 check_result(reporter, verts, expectVerts);
55
56 verts = SkShadowTessellator::MakeSpot(path, ctm, heightParams, {0, 0, 128}, 128.f, false, true);
57 check_result(reporter, verts, expectVerts);
58 }
59
DEF_TEST(ShadowUtils,reporter)60 DEF_TEST(ShadowUtils, reporter) {
61 SkCanvas canvas(100, 100);
62
63 SkPath path;
64 path.cubicTo(100, 50, 20, 100, 0, 0);
65 tessellate_shadow(reporter, path, canvas.getTotalMatrix(), {0, 0, 4}, kDo_ExpectVerts);
66 // super high path
67 tessellate_shadow(reporter, path, canvas.getTotalMatrix(), {0, 0, 4.0e+37f}, kDo_ExpectVerts);
68
69 // This line segment has no area and no shadow.
70 path.reset();
71 path.lineTo(10.f, 10.f);
72 tessellate_shadow(reporter, path, canvas.getTotalMatrix(), {0, 0, 4}, kDont_ExpectVerts);
73
74 // A series of collinear line segments
75 path.reset();
76 for (int i = 0; i < 10; ++i) {
77 path.lineTo((SkScalar)i, (SkScalar)i);
78 }
79 tessellate_shadow(reporter, path, canvas.getTotalMatrix(), {0, 0, 4}, kDont_ExpectVerts);
80
81 // ugly degenerate path
82 path.reset();
83 path.moveTo(-134217728, 2.22265153e+21f);
84 path.cubicTo(-2.33326106e+21f, 7.36298265e-41f, 3.72237738e-22f, 5.99502692e-36f,
85 1.13631943e+22f, 2.0890786e+33f);
86 path.cubicTo(1.03397626e-25f, 5.99502692e-36f, 9.18354962e-41f, 0, 4.6142745e-37f, -213558848);
87 path.lineTo(-134217728, 2.2226515e+21f);
88 tessellate_shadow(reporter, path, canvas.getTotalMatrix(), {0, 0, 9}, kDont_ExpectVerts);
89
90 // simple concave path (star of David)
91 path.reset();
92 path.moveTo(0.0f, -50.0f);
93 path.lineTo(14.43f, -25.0f);
94 path.lineTo(43.30f, -25.0f);
95 path.lineTo(28.86f, 0.0f);
96 path.lineTo(43.30f, 25.0f);
97 path.lineTo(14.43f, 25.0f);
98 path.lineTo(0.0f, 50.0f);
99 path.lineTo(-14.43f, 25.0f);
100 path.lineTo(-43.30f, 25.0f);
101 path.lineTo(-28.86f, 0.0f);
102 path.lineTo(-43.30f, -25.0f);
103 path.lineTo(-14.43f, -25.0f);
104 // uncomment when transparent concave shadows are working
105 // tessellate_shadow(reporter, path, canvas.getTotalMatrix(), {0, 0, 9}, kDo_ExpectVerts, true);
106
107 // complex concave path (bowtie)
108 path.reset();
109 path.moveTo(-50, -50);
110 path.lineTo(-50, 50);
111 path.lineTo(50, -50);
112 path.lineTo(50, 50);
113 path.lineTo(-50, -50);
114 tessellate_shadow(reporter, path, canvas.getTotalMatrix(), {0, 0, 9}, kDont_ExpectVerts);
115
116 // multiple contour path
117 path.close();
118 path.moveTo(0, 0);
119 path.lineTo(1, 0);
120 path.lineTo(0, 1);
121 tessellate_shadow(reporter, path, canvas.getTotalMatrix(), {0, 0, 9}, kDont_ExpectVerts);
122 }
123
check_xformed_bounds(skiatest::Reporter * reporter,const SkPath & path,const SkMatrix & ctm)124 void check_xformed_bounds(skiatest::Reporter* reporter, const SkPath& path, const SkMatrix& ctm) {
125 SkDrawShadowRec rec = {
126 SkPoint3::Make(0, 0, 4),
127 SkPoint3::Make(100, 0, 600),
128 800.f,
129 0x08000000,
130 0x40000000,
131 0
132 };
133 // point light
134 SkRect bounds;
135 SkDrawShadowMetrics::GetLocalBounds(path, rec, ctm, &bounds);
136 ctm.mapRect(&bounds);
137
138 auto verts = SkShadowTessellator::MakeAmbient(path, ctm, rec.fZPlaneParams, true);
139 if (verts) {
140 REPORTER_ASSERT(reporter, bounds.contains(verts->bounds()));
141 }
142
143 SkPoint mapXY = ctm.mapXY(rec.fLightPos.fX, rec.fLightPos.fY);
144 SkPoint3 devLightPos = SkPoint3::Make(mapXY.fX, mapXY.fY, rec.fLightPos.fZ);
145 verts = SkShadowTessellator::MakeSpot(path, ctm, rec.fZPlaneParams, devLightPos,
146 rec.fLightRadius, false, false);
147 if (verts) {
148 REPORTER_ASSERT(reporter, bounds.contains(verts->bounds()));
149 }
150
151 // directional light
152 rec.fFlags |= SkShadowFlags::kDirectionalLight_ShadowFlag;
153 rec.fLightRadius = 2.0f;
154 SkDrawShadowMetrics::GetLocalBounds(path, rec, ctm, &bounds);
155 ctm.mapRect(&bounds);
156
157 verts = SkShadowTessellator::MakeAmbient(path, ctm, rec.fZPlaneParams, true);
158 if (verts) {
159 REPORTER_ASSERT(reporter, bounds.contains(verts->bounds()));
160 }
161
162 devLightPos = rec.fLightPos;
163 devLightPos.normalize();
164 verts = SkShadowTessellator::MakeSpot(path, ctm, rec.fZPlaneParams, devLightPos,
165 rec.fLightRadius, false, true);
166 if (verts) {
167 REPORTER_ASSERT(reporter, bounds.contains(verts->bounds()));
168 }
169 }
170
check_bounds(skiatest::Reporter * reporter,const SkPath & path)171 void check_bounds(skiatest::Reporter* reporter, const SkPath& path) {
172 const bool fixed_shadows_in_perspective = false; // skbug.com/9698
173
174 SkMatrix ctm;
175 ctm.setTranslate(100, 100);
176 check_xformed_bounds(reporter, path, ctm);
177 ctm.postScale(2, 2);
178 check_xformed_bounds(reporter, path, ctm);
179 ctm.preRotate(45);
180 check_xformed_bounds(reporter, path, ctm);
181 ctm.preSkew(40, -20);
182 check_xformed_bounds(reporter, path, ctm);
183 if (fixed_shadows_in_perspective) {
184 ctm[SkMatrix::kMPersp0] = 0.0001f;
185 ctm[SkMatrix::kMPersp1] = 12.f;
186 check_xformed_bounds(reporter, path, ctm);
187 ctm[SkMatrix::kMPersp0] = 0.0001f;
188 ctm[SkMatrix::kMPersp1] = -12.f;
189 check_xformed_bounds(reporter, path, ctm);
190 ctm[SkMatrix::kMPersp0] = 12.f;
191 ctm[SkMatrix::kMPersp1] = 0.0001f;
192 check_xformed_bounds(reporter, path, ctm);
193 }
194 }
195
DEF_TEST(ShadowBounds,reporter)196 DEF_TEST(ShadowBounds, reporter) {
197 SkPath path;
198 path.addRRect(SkRRect::MakeRectXY(SkRect::MakeLTRB(-50, -20, 40, 30), 4, 4));
199 check_bounds(reporter, path);
200
201 path.reset();
202 path.addOval(SkRect::MakeLTRB(300, 300, 900, 900));
203 check_bounds(reporter, path);
204
205 path.reset();
206 path.cubicTo(100, 50, 20, 100, 0, 0);
207 check_bounds(reporter, path);
208 }
209