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/SkMatrix.h"
9 #include "include/core/SkPath.h"
10 #include "include/core/SkRect.h"
11 #include "include/private/SkShadowFlags.h"
12 #include "src/core/SkDrawShadowInfo.h"
13 #include "src/utils/SkPolyUtils.h"
14
dump(std::string & desc,int depth) const15 void SkDrawShadowRec::dump(std::string& desc, int depth) const {
16 std::string split(depth, '\t');
17 desc += split + "\n SkDrawShadowRec:{ \n";
18 fZPlaneParams.dump(desc, depth + 1);
19 fLightPos.dump(desc, depth + 1);
20 desc += split + "\t fLightRadius: " + std::to_string(fLightRadius) + "\n";
21 desc += split + "\t fAmbientColor: " + std::to_string(fAmbientColor) + "\n";
22 desc += split + "\t fSpotColor: " + std::to_string(fSpotColor) + "\n";
23 desc += split + "\t fFlags: " + std::to_string(fFlags) + "\n";
24 desc += split + "}\n";
25 }
26
27 namespace SkDrawShadowMetrics {
28
compute_z(SkScalar x,SkScalar y,const SkPoint3 & params)29 static SkScalar compute_z(SkScalar x, SkScalar y, const SkPoint3& params) {
30 return x*params.fX + y*params.fY + params.fZ;
31 }
32
GetSpotShadowTransform(const SkPoint3 & lightPos,SkScalar lightRadius,const SkMatrix & ctm,const SkPoint3 & zPlaneParams,const SkRect & pathBounds,bool directional,SkMatrix * shadowTransform,SkScalar * radius)33 bool GetSpotShadowTransform(const SkPoint3& lightPos, SkScalar lightRadius,
34 const SkMatrix& ctm, const SkPoint3& zPlaneParams,
35 const SkRect& pathBounds, bool directional,
36 SkMatrix* shadowTransform, SkScalar* radius) {
37 auto heightFunc = [zPlaneParams] (SkScalar x, SkScalar y) {
38 return zPlaneParams.fX*x + zPlaneParams.fY*y + zPlaneParams.fZ;
39 };
40 SkScalar occluderHeight = heightFunc(pathBounds.centerX(), pathBounds.centerY());
41
42 // TODO: have directional lights support tilt via the zPlaneParams
43 if (!ctm.hasPerspective() || directional) {
44 SkScalar scale;
45 SkVector translate;
46 if (directional) {
47 SkDrawShadowMetrics::GetDirectionalParams(occluderHeight, lightPos.fX, lightPos.fY,
48 lightPos.fZ, lightRadius, radius,
49 &scale, &translate);
50 } else {
51 SkDrawShadowMetrics::GetSpotParams(occluderHeight, lightPos.fX, lightPos.fY,
52 lightPos.fZ, lightRadius, radius,
53 &scale, &translate);
54 }
55 shadowTransform->setScaleTranslate(scale, scale, translate.fX, translate.fY);
56 shadowTransform->preConcat(ctm);
57 } else {
58 if (SkScalarNearlyZero(pathBounds.width()) || SkScalarNearlyZero(pathBounds.height())) {
59 return false;
60 }
61
62 // get rotated quad in 3D
63 SkPoint pts[4];
64 ctm.mapRectToQuad(pts, pathBounds);
65 // No shadows for bowties or other degenerate cases
66 if (!SkIsConvexPolygon(pts, 4)) {
67 return false;
68 }
69 SkPoint3 pts3D[4];
70 SkScalar z = heightFunc(pathBounds.fLeft, pathBounds.fTop);
71 pts3D[0].set(pts[0].fX, pts[0].fY, z);
72 z = heightFunc(pathBounds.fRight, pathBounds.fTop);
73 pts3D[1].set(pts[1].fX, pts[1].fY, z);
74 z = heightFunc(pathBounds.fRight, pathBounds.fBottom);
75 pts3D[2].set(pts[2].fX, pts[2].fY, z);
76 z = heightFunc(pathBounds.fLeft, pathBounds.fBottom);
77 pts3D[3].set(pts[3].fX, pts[3].fY, z);
78
79 // project from light through corners to z=0 plane
80 for (int i = 0; i < 4; ++i) {
81 SkScalar dz = lightPos.fZ - pts3D[i].fZ;
82 // light shouldn't be below or at a corner's z-location
83 if (dz <= SK_ScalarNearlyZero) {
84 return false;
85 }
86 SkScalar zRatio = pts3D[i].fZ / dz;
87 pts3D[i].fX -= (lightPos.fX - pts3D[i].fX)*zRatio;
88 pts3D[i].fY -= (lightPos.fY - pts3D[i].fY)*zRatio;
89 pts3D[i].fZ = SK_Scalar1;
90 }
91
92 // Generate matrix that projects from [-1,1]x[-1,1] square to projected quad
93 SkPoint3 h0, h1, h2;
94 // Compute homogenous crossing point between top and bottom edges (gives new x-axis).
95 h0 = (pts3D[1].cross(pts3D[0])).cross(pts3D[2].cross(pts3D[3]));
96 // Compute homogenous crossing point between left and right edges (gives new y-axis).
97 h1 = (pts3D[0].cross(pts3D[3])).cross(pts3D[1].cross(pts3D[2]));
98 // Compute homogenous crossing point between diagonals (gives new origin).
99 h2 = (pts3D[0].cross(pts3D[2])).cross(pts3D[1].cross(pts3D[3]));
100 // If h2 is a vector (z=0 in 2D homogeneous space), that means that at least
101 // two of the quad corners are coincident and we don't have a realistic projection
102 if (SkScalarNearlyZero(h2.fZ)) {
103 return false;
104 }
105 // In some cases the crossing points are in the wrong direction
106 // to map (-1,-1) to pts3D[0], so we need to correct for that.
107 // Want h0 to be to the right of the left edge.
108 SkVector3 v = pts3D[3] - pts3D[0];
109 SkVector3 w = h0 - pts3D[0];
110 SkScalar perpDot = v.fX*w.fY - v.fY*w.fX;
111 if (perpDot > 0) {
112 h0 = -h0;
113 }
114 // Want h1 to be above the bottom edge.
115 v = pts3D[1] - pts3D[0];
116 perpDot = v.fX*w.fY - v.fY*w.fX;
117 if (perpDot < 0) {
118 h1 = -h1;
119 }
120 shadowTransform->setAll(h0.fX / h2.fZ, h1.fX / h2.fZ, h2.fX / h2.fZ,
121 h0.fY / h2.fZ, h1.fY / h2.fZ, h2.fY / h2.fZ,
122 h0.fZ / h2.fZ, h1.fZ / h2.fZ, 1);
123 // generate matrix that transforms from bounds to [-1,1]x[-1,1] square
124 SkMatrix toHomogeneous;
125 SkScalar xScale = 2/(pathBounds.fRight - pathBounds.fLeft);
126 SkScalar yScale = 2/(pathBounds.fBottom - pathBounds.fTop);
127 toHomogeneous.setAll(xScale, 0, -xScale*pathBounds.fLeft - 1,
128 0, yScale, -yScale*pathBounds.fTop - 1,
129 0, 0, 1);
130 shadowTransform->preConcat(toHomogeneous);
131
132 *radius = SkDrawShadowMetrics::SpotBlurRadius(occluderHeight, lightPos.fZ, lightRadius);
133 }
134
135 return true;
136 }
137
GetLocalBounds(const SkPath & path,const SkDrawShadowRec & rec,const SkMatrix & ctm,SkRect * bounds)138 void GetLocalBounds(const SkPath& path, const SkDrawShadowRec& rec, const SkMatrix& ctm,
139 SkRect* bounds) {
140 SkRect ambientBounds = path.getBounds();
141 SkScalar occluderZ;
142 if (SkScalarNearlyZero(rec.fZPlaneParams.fX) && SkScalarNearlyZero(rec.fZPlaneParams.fY)) {
143 occluderZ = rec.fZPlaneParams.fZ;
144 } else {
145 occluderZ = compute_z(ambientBounds.fLeft, ambientBounds.fTop, rec.fZPlaneParams);
146 occluderZ = std::max(occluderZ, compute_z(ambientBounds.fRight, ambientBounds.fTop,
147 rec.fZPlaneParams));
148 occluderZ = std::max(occluderZ, compute_z(ambientBounds.fLeft, ambientBounds.fBottom,
149 rec.fZPlaneParams));
150 occluderZ = std::max(occluderZ, compute_z(ambientBounds.fRight, ambientBounds.fBottom,
151 rec.fZPlaneParams));
152 }
153 SkScalar ambientBlur;
154 SkScalar spotBlur;
155 SkScalar spotScale;
156 SkPoint spotOffset;
157 if (ctm.hasPerspective()) {
158 // transform ambient and spot bounds into device space
159 ctm.mapRect(&ambientBounds);
160
161 // get ambient blur (in device space)
162 ambientBlur = SkDrawShadowMetrics::AmbientBlurRadius(occluderZ);
163
164 // get spot params (in device space)
165 if (SkToBool(rec.fFlags & SkShadowFlags::kDirectionalLight_ShadowFlag)) {
166 SkDrawShadowMetrics::GetDirectionalParams(occluderZ, rec.fLightPos.fX, rec.fLightPos.fY,
167 rec.fLightPos.fZ, rec.fLightRadius,
168 &spotBlur, &spotScale, &spotOffset);
169 } else {
170 SkPoint devLightPos = SkPoint::Make(rec.fLightPos.fX, rec.fLightPos.fY);
171 ctm.mapPoints(&devLightPos, 1);
172 SkDrawShadowMetrics::GetSpotParams(occluderZ, devLightPos.fX, devLightPos.fY,
173 rec.fLightPos.fZ, rec.fLightRadius,
174 &spotBlur, &spotScale, &spotOffset);
175 }
176 } else {
177 SkScalar devToSrcScale = SkScalarInvert(ctm.getMinScale());
178
179 // get ambient blur (in local space)
180 SkScalar devSpaceAmbientBlur = SkDrawShadowMetrics::AmbientBlurRadius(occluderZ);
181 ambientBlur = devSpaceAmbientBlur*devToSrcScale;
182
183 // get spot params (in local space)
184 if (SkToBool(rec.fFlags & SkShadowFlags::kDirectionalLight_ShadowFlag)) {
185 SkDrawShadowMetrics::GetDirectionalParams(occluderZ, rec.fLightPos.fX, rec.fLightPos.fY,
186 rec.fLightPos.fZ, rec.fLightRadius,
187 &spotBlur, &spotScale, &spotOffset);
188 // light dir is in device space, so need to map spot offset back into local space
189 SkMatrix inverse;
190 if (ctm.invert(&inverse)) {
191 inverse.mapVectors(&spotOffset, 1);
192 }
193 } else {
194 SkDrawShadowMetrics::GetSpotParams(occluderZ, rec.fLightPos.fX, rec.fLightPos.fY,
195 rec.fLightPos.fZ, rec.fLightRadius,
196 &spotBlur, &spotScale, &spotOffset);
197 }
198
199 // convert spot blur to local space
200 spotBlur *= devToSrcScale;
201 }
202
203 // in both cases, adjust ambient and spot bounds
204 SkRect spotBounds = ambientBounds;
205 ambientBounds.outset(ambientBlur, ambientBlur);
206 spotBounds.fLeft *= spotScale;
207 spotBounds.fTop *= spotScale;
208 spotBounds.fRight *= spotScale;
209 spotBounds.fBottom *= spotScale;
210 spotBounds.offset(spotOffset.fX, spotOffset.fY);
211 spotBounds.outset(spotBlur, spotBlur);
212
213 // merge bounds
214 *bounds = ambientBounds;
215 bounds->join(spotBounds);
216 // outset a bit to account for floating point error
217 bounds->outset(1, 1);
218
219 // if perspective, transform back to src space
220 if (ctm.hasPerspective()) {
221 // TODO: create tighter mapping from dev rect back to src rect
222 SkMatrix inverse;
223 if (ctm.invert(&inverse)) {
224 inverse.mapRect(bounds);
225 }
226 }
227 }
228
229
230 } // namespace SkDrawShadowMetrics
231
232