SkDrawShadowInfo.cpp 8.0 KB

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