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