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