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