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
14namespace SkDrawShadowMetrics {
15
16static SkScalar compute_z(SkScalar x, SkScalar y, const SkPoint3& params) {
17 return x*params.fX + y*params.fY + params.fZ;
18}
19
20bool 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
116void 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 = std::max(occluderZ, compute_z(ambientBounds.fRight, ambientBounds.fTop,
125 rec.fZPlaneParams));
126 occluderZ = std::max(occluderZ, compute_z(ambientBounds.fLeft, ambientBounds.fBottom,
127 rec.fZPlaneParams));
128 occluderZ = std::max(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