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, bool isLimitElevation)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, bool isLimitElevation) {
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, isLimitElevation);
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             if (isLimitElevation) {
88                 zRatio = 0.0f;
89             }
90             pts3D[i].fX -= (lightPos.fX - pts3D[i].fX)*zRatio;
91             pts3D[i].fY -= (lightPos.fY - pts3D[i].fY)*zRatio;
92             pts3D[i].fZ = SK_Scalar1;
93         }
94 
95         // Generate matrix that projects from [-1,1]x[-1,1] square to projected quad
96         SkPoint3 h0, h1, h2;
97         // Compute homogenous crossing point between top and bottom edges (gives new x-axis).
98         h0 = (pts3D[1].cross(pts3D[0])).cross(pts3D[2].cross(pts3D[3]));
99         // Compute homogenous crossing point between left and right edges (gives new y-axis).
100         h1 = (pts3D[0].cross(pts3D[3])).cross(pts3D[1].cross(pts3D[2]));
101         // Compute homogenous crossing point between diagonals (gives new origin).
102         h2 = (pts3D[0].cross(pts3D[2])).cross(pts3D[1].cross(pts3D[3]));
103         // If h2 is a vector (z=0 in 2D homogeneous space), that means that at least
104         // two of the quad corners are coincident and we don't have a realistic projection
105         if (SkScalarNearlyZero(h2.fZ)) {
106             return false;
107         }
108         // In some cases the crossing points are in the wrong direction
109         // to map (-1,-1) to pts3D[0], so we need to correct for that.
110         // Want h0 to be to the right of the left edge.
111         SkVector3 v = pts3D[3] - pts3D[0];
112         SkVector3 w = h0 - pts3D[0];
113         SkScalar perpDot = v.fX*w.fY - v.fY*w.fX;
114         if (perpDot > 0) {
115             h0 = -h0;
116         }
117         // Want h1 to be above the bottom edge.
118         v = pts3D[1] - pts3D[0];
119         perpDot = v.fX*w.fY - v.fY*w.fX;
120         if (perpDot < 0) {
121             h1 = -h1;
122         }
123         shadowTransform->setAll(h0.fX / h2.fZ, h1.fX / h2.fZ, h2.fX / h2.fZ,
124                                h0.fY / h2.fZ, h1.fY / h2.fZ, h2.fY / h2.fZ,
125                                h0.fZ / h2.fZ, h1.fZ / h2.fZ, 1);
126         // generate matrix that transforms from bounds to [-1,1]x[-1,1] square
127         SkMatrix toHomogeneous;
128         SkScalar xScale = 2/(pathBounds.fRight - pathBounds.fLeft);
129         SkScalar yScale = 2/(pathBounds.fBottom - pathBounds.fTop);
130         toHomogeneous.setAll(xScale, 0, -xScale*pathBounds.fLeft - 1,
131                              0, yScale, -yScale*pathBounds.fTop - 1,
132                              0, 0, 1);
133         shadowTransform->preConcat(toHomogeneous);
134 
135         *radius = SkDrawShadowMetrics::SpotBlurRadius(occluderHeight, lightPos.fZ, lightRadius);
136     }
137 
138     return true;
139 }
140 
GetLocalBounds(const SkPath& path, const SkDrawShadowRec& rec, const SkMatrix& ctm, SkRect* bounds)141 void GetLocalBounds(const SkPath& path, const SkDrawShadowRec& rec, const SkMatrix& ctm,
142                     SkRect* bounds) {
143     SkRect ambientBounds = path.getBounds();
144     SkScalar occluderZ;
145     if (SkScalarNearlyZero(rec.fZPlaneParams.fX) && SkScalarNearlyZero(rec.fZPlaneParams.fY)) {
146         occluderZ = rec.fZPlaneParams.fZ;
147     } else {
148         occluderZ = compute_z(ambientBounds.fLeft, ambientBounds.fTop, rec.fZPlaneParams);
149         occluderZ = std::max(occluderZ, compute_z(ambientBounds.fRight, ambientBounds.fTop,
150                                                 rec.fZPlaneParams));
151         occluderZ = std::max(occluderZ, compute_z(ambientBounds.fLeft, ambientBounds.fBottom,
152                                                 rec.fZPlaneParams));
153         occluderZ = std::max(occluderZ, compute_z(ambientBounds.fRight, ambientBounds.fBottom,
154                                                 rec.fZPlaneParams));
155     }
156     SkScalar ambientBlur;
157     SkScalar spotBlur;
158     SkScalar spotScale;
159     SkPoint spotOffset;
160     if (ctm.hasPerspective()) {
161         // transform ambient and spot bounds into device space
162         ctm.mapRect(&ambientBounds);
163 
164         // get ambient blur (in device space)
165         ambientBlur = SkDrawShadowMetrics::AmbientBlurRadius(occluderZ);
166 
167         // get spot params (in device space)
168         if (SkToBool(rec.fFlags & SkShadowFlags::kDirectionalLight_ShadowFlag)) {
169             SkDrawShadowMetrics::GetDirectionalParams(occluderZ, rec.fLightPos.fX, rec.fLightPos.fY,
170                                                       rec.fLightPos.fZ, rec.fLightRadius,
171                                                       &spotBlur, &spotScale, &spotOffset);
172         } else {
173             SkPoint devLightPos = SkPoint::Make(rec.fLightPos.fX, rec.fLightPos.fY);
174             ctm.mapPoints(&devLightPos, 1);
175             SkDrawShadowMetrics::GetSpotParams(occluderZ, devLightPos.fX, devLightPos.fY,
176                                                rec.fLightPos.fZ, rec.fLightRadius,
177                                                &spotBlur, &spotScale, &spotOffset, rec.isLimitElevation);
178         }
179     } else {
180         SkScalar devToSrcScale = SkScalarInvert(ctm.getMinScale());
181 
182         // get ambient blur (in local space)
183         SkScalar devSpaceAmbientBlur = SkDrawShadowMetrics::AmbientBlurRadius(occluderZ);
184         ambientBlur = devSpaceAmbientBlur*devToSrcScale;
185 
186         // get spot params (in local space)
187         if (SkToBool(rec.fFlags & SkShadowFlags::kDirectionalLight_ShadowFlag)) {
188             SkDrawShadowMetrics::GetDirectionalParams(occluderZ, rec.fLightPos.fX, rec.fLightPos.fY,
189                                                       rec.fLightPos.fZ, rec.fLightRadius,
190                                                       &spotBlur, &spotScale, &spotOffset);
191             // light dir is in device space, so need to map spot offset back into local space
192             SkMatrix inverse;
193             if (ctm.invert(&inverse)) {
194                 inverse.mapVectors(&spotOffset, 1);
195             }
196         } else {
197             SkDrawShadowMetrics::GetSpotParams(occluderZ, rec.fLightPos.fX, rec.fLightPos.fY,
198                                                rec.fLightPos.fZ, rec.fLightRadius,
199                                                &spotBlur, &spotScale, &spotOffset, rec.isLimitElevation);
200         }
201 
202         // convert spot blur to local space
203         spotBlur *= devToSrcScale;
204     }
205 
206     // in both cases, adjust ambient and spot bounds
207     SkRect spotBounds = ambientBounds;
208     ambientBounds.outset(ambientBlur, ambientBlur);
209     spotBounds.fLeft *= spotScale;
210     spotBounds.fTop *= spotScale;
211     spotBounds.fRight *= spotScale;
212     spotBounds.fBottom *= spotScale;
213     spotBounds.offset(spotOffset.fX, spotOffset.fY);
214     spotBounds.outset(spotBlur, spotBlur);
215 
216     // merge bounds
217     *bounds = ambientBounds;
218     bounds->join(spotBounds);
219     // outset a bit to account for floating point error
220     bounds->outset(1, 1);
221 
222     // if perspective, transform back to src space
223     if (ctm.hasPerspective()) {
224         // TODO: create tighter mapping from dev rect back to src rect
225         SkMatrix inverse;
226         if (ctm.invert(&inverse)) {
227             inverse.mapRect(bounds);
228         }
229     }
230 }
231 
232 
233 }  // namespace SkDrawShadowMetrics
234 
235