| 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 |  | 
|---|
| 16 | static SkScalar compute_z(SkScalar x, SkScalar y, const SkPoint3& params) { | 
|---|
| 17 | return x*params.fX + y*params.fY + params.fZ; | 
|---|
| 18 | } | 
|---|
| 19 |  | 
|---|
| 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 |  | 
|---|
| 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 = 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 |  | 
|---|