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 "SkShadowUtils.h" 9 #include "SkCanvas.h" 10 #include "SkColorFilter.h" 11 #include "SkColorPriv.h" 12 #include "SkDevice.h" 13 #include "SkDrawShadowRec.h" 14 #include "SkPath.h" 15 #include "SkPM4f.h" 16 #include "SkRandom.h" 17 #include "SkRasterPipeline.h" 18 #include "SkResourceCache.h" 19 #include "SkShadowTessellator.h" 20 #include "SkString.h" 21 #include "SkTLazy.h" 22 #include "SkVertices.h" 23 #if SK_SUPPORT_GPU 24 #include "GrShape.h" 25 #include "effects/GrBlurredEdgeFragmentProcessor.h" 26 #endif 27 28 /** 29 * Gaussian color filter -- produces a Gaussian ramp based on the color's B value, 30 * then blends with the color's G value. 31 * Final result is black with alpha of Gaussian(B)*G. 32 * The assumption is that the original color's alpha is 1. 33 */ 34 class SK_API SkGaussianColorFilter : public SkColorFilter { 35 public: 36 static sk_sp<SkColorFilter> Make() { 37 return sk_sp<SkColorFilter>(new SkGaussianColorFilter); 38 } 39 40 #if SK_SUPPORT_GPU 41 sk_sp<GrFragmentProcessor> asFragmentProcessor(GrContext*, SkColorSpace*) const override; 42 #endif 43 44 SK_TO_STRING_OVERRIDE() 45 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkGaussianColorFilter) 46 47 protected: 48 void flatten(SkWriteBuffer&) const override {} 49 void onAppendStages(SkRasterPipeline* pipeline, SkColorSpace* dstCS, SkArenaAlloc* alloc, 50 bool shaderIsOpaque) const override { 51 pipeline->append(SkRasterPipeline::gauss_a_to_rgba); 52 } 53 private: 54 SkGaussianColorFilter() : INHERITED() {} 55 56 typedef SkColorFilter INHERITED; 57 }; 58 59 sk_sp<SkFlattenable> SkGaussianColorFilter::CreateProc(SkReadBuffer&) { 60 return Make(); 61 } 62 63 #ifndef SK_IGNORE_TO_STRING 64 void SkGaussianColorFilter::toString(SkString* str) const { 65 str->append("SkGaussianColorFilter "); 66 } 67 #endif 68 69 #if SK_SUPPORT_GPU 70 71 sk_sp<GrFragmentProcessor> SkGaussianColorFilter::asFragmentProcessor(GrContext*, 72 SkColorSpace*) const { 73 return GrBlurredEdgeFragmentProcessor::Make(GrBlurredEdgeFragmentProcessor::kGaussian_Mode); 74 } 75 #endif 76 77 /////////////////////////////////////////////////////////////////////////////////////////////////// 78 79 namespace { 80 81 uint64_t resource_cache_shared_id() { 82 return 0x2020776f64616873llu; // 'shadow ' 83 } 84 85 /** Factory for an ambient shadow mesh with particular shadow properties. */ 86 struct AmbientVerticesFactory { 87 SkScalar fOccluderHeight = SK_ScalarNaN; // NaN so that isCompatible will fail until init'ed. 88 bool fTransparent; 89 SkVector fOffset; 90 91 bool isCompatible(const AmbientVerticesFactory& that, SkVector* translate) const { 92 if (fOccluderHeight != that.fOccluderHeight || fTransparent != that.fTransparent) { 93 return false; 94 } 95 *translate = that.fOffset; 96 return true; 97 } 98 99 sk_sp<SkVertices> makeVertices(const SkPath& path, const SkMatrix& ctm, 100 SkVector* translate) const { 101 SkPoint3 zParams = SkPoint3::Make(0, 0, fOccluderHeight); 102 // pick a canonical place to generate shadow 103 SkMatrix noTrans(ctm); 104 if (!ctm.hasPerspective()) { 105 noTrans[SkMatrix::kMTransX] = 0; 106 noTrans[SkMatrix::kMTransY] = 0; 107 } 108 *translate = fOffset; 109 return SkShadowTessellator::MakeAmbient(path, noTrans, zParams, fTransparent); 110 } 111 }; 112 113 /** Factory for an spot shadow mesh with particular shadow properties. */ 114 struct SpotVerticesFactory { 115 enum class OccluderType { 116 // The umbra cannot be dropped out because either the occluder is not opaque, 117 // or the center of the umbra is visible. 118 kTransparent, 119 // The umbra can be dropped where it is occluded. 120 kOpaquePartialUmbra, 121 // It is known that the entire umbra is occluded. 122 kOpaqueNoUmbra 123 }; 124 125 SkVector fOffset; 126 SkPoint fLocalCenter; 127 SkScalar fOccluderHeight = SK_ScalarNaN; // NaN so that isCompatible will fail until init'ed. 128 SkPoint3 fDevLightPos; 129 SkScalar fLightRadius; 130 OccluderType fOccluderType; 131 132 bool isCompatible(const SpotVerticesFactory& that, SkVector* translate) const { 133 if (fOccluderHeight != that.fOccluderHeight || fDevLightPos.fZ != that.fDevLightPos.fZ || 134 fLightRadius != that.fLightRadius || fOccluderType != that.fOccluderType) { 135 return false; 136 } 137 switch (fOccluderType) { 138 case OccluderType::kTransparent: 139 case OccluderType::kOpaqueNoUmbra: 140 // 'this' and 'that' will either both have no umbra removed or both have all the 141 // umbra removed. 142 *translate = that.fOffset; 143 return true; 144 case OccluderType::kOpaquePartialUmbra: 145 // In this case we partially remove the umbra differently for 'this' and 'that' 146 // if the offsets don't match. 147 if (fOffset == that.fOffset) { 148 translate->set(0, 0); 149 return true; 150 } 151 return false; 152 } 153 SkFAIL("Uninitialized occluder type?"); 154 return false; 155 } 156 157 sk_sp<SkVertices> makeVertices(const SkPath& path, const SkMatrix& ctm, 158 SkVector* translate) const { 159 bool transparent = OccluderType::kTransparent == fOccluderType; 160 SkPoint3 zParams = SkPoint3::Make(0, 0, fOccluderHeight); 161 if (ctm.hasPerspective() || OccluderType::kOpaquePartialUmbra == fOccluderType) { 162 translate->set(0, 0); 163 return SkShadowTessellator::MakeSpot(path, ctm, zParams, 164 fDevLightPos, fLightRadius, transparent); 165 } else { 166 // pick a canonical place to generate shadow, with light centered over path 167 SkMatrix noTrans(ctm); 168 noTrans[SkMatrix::kMTransX] = 0; 169 noTrans[SkMatrix::kMTransY] = 0; 170 SkPoint devCenter(fLocalCenter); 171 noTrans.mapPoints(&devCenter, 1); 172 SkPoint3 centerLightPos = SkPoint3::Make(devCenter.fX, devCenter.fY, fDevLightPos.fZ); 173 *translate = fOffset; 174 return SkShadowTessellator::MakeSpot(path, noTrans, zParams, 175 centerLightPos, fLightRadius, transparent); 176 } 177 } 178 }; 179 180 /** 181 * This manages a set of tessellations for a given shape in the cache. Because SkResourceCache 182 * records are immutable this is not itself a Rec. When we need to update it we return this on 183 * the FindVisitor and let the cache destroy the Rec. We'll update the tessellations and then add 184 * a new Rec with an adjusted size for any deletions/additions. 185 */ 186 class CachedTessellations : public SkRefCnt { 187 public: 188 size_t size() const { return fAmbientSet.size() + fSpotSet.size(); } 189 190 sk_sp<SkVertices> find(const AmbientVerticesFactory& ambient, const SkMatrix& matrix, 191 SkVector* translate) const { 192 return fAmbientSet.find(ambient, matrix, translate); 193 } 194 195 sk_sp<SkVertices> add(const SkPath& devPath, const AmbientVerticesFactory& ambient, 196 const SkMatrix& matrix, SkVector* translate) { 197 return fAmbientSet.add(devPath, ambient, matrix, translate); 198 } 199 200 sk_sp<SkVertices> find(const SpotVerticesFactory& spot, const SkMatrix& matrix, 201 SkVector* translate) const { 202 return fSpotSet.find(spot, matrix, translate); 203 } 204 205 sk_sp<SkVertices> add(const SkPath& devPath, const SpotVerticesFactory& spot, 206 const SkMatrix& matrix, SkVector* translate) { 207 return fSpotSet.add(devPath, spot, matrix, translate); 208 } 209 210 private: 211 template <typename FACTORY, int MAX_ENTRIES> 212 class Set { 213 public: 214 size_t size() const { return fSize; } 215 216 sk_sp<SkVertices> find(const FACTORY& factory, const SkMatrix& matrix, 217 SkVector* translate) const { 218 for (int i = 0; i < MAX_ENTRIES; ++i) { 219 if (fEntries[i].fFactory.isCompatible(factory, translate)) { 220 const SkMatrix& m = fEntries[i].fMatrix; 221 if (matrix.hasPerspective() || m.hasPerspective()) { 222 if (matrix != fEntries[i].fMatrix) { 223 continue; 224 } 225 } else if (matrix.getScaleX() != m.getScaleX() || 226 matrix.getSkewX() != m.getSkewX() || 227 matrix.getScaleY() != m.getScaleY() || 228 matrix.getSkewY() != m.getSkewY()) { 229 continue; 230 } 231 return fEntries[i].fVertices; 232 } 233 } 234 return nullptr; 235 } 236 237 sk_sp<SkVertices> add(const SkPath& path, const FACTORY& factory, const SkMatrix& matrix, 238 SkVector* translate) { 239 sk_sp<SkVertices> vertices = factory.makeVertices(path, matrix, translate); 240 if (!vertices) { 241 return nullptr; 242 } 243 int i; 244 if (fCount < MAX_ENTRIES) { 245 i = fCount++; 246 } else { 247 i = fRandom.nextULessThan(MAX_ENTRIES); 248 fSize -= fEntries[i].fVertices->approximateSize(); 249 } 250 fEntries[i].fFactory = factory; 251 fEntries[i].fVertices = vertices; 252 fEntries[i].fMatrix = matrix; 253 fSize += vertices->approximateSize(); 254 return vertices; 255 } 256 257 private: 258 struct Entry { 259 FACTORY fFactory; 260 sk_sp<SkVertices> fVertices; 261 SkMatrix fMatrix; 262 }; 263 Entry fEntries[MAX_ENTRIES]; 264 int fCount = 0; 265 size_t fSize = 0; 266 SkRandom fRandom; 267 }; 268 269 Set<AmbientVerticesFactory, 4> fAmbientSet; 270 Set<SpotVerticesFactory, 4> fSpotSet; 271 }; 272 273 /** 274 * A record of shadow vertices stored in SkResourceCache of CachedTessellations for a particular 275 * path. The key represents the path's geometry and not any shadow params. 276 */ 277 class CachedTessellationsRec : public SkResourceCache::Rec { 278 public: 279 CachedTessellationsRec(const SkResourceCache::Key& key, 280 sk_sp<CachedTessellations> tessellations) 281 : fTessellations(std::move(tessellations)) { 282 fKey.reset(new uint8_t[key.size()]); 283 memcpy(fKey.get(), &key, key.size()); 284 } 285 286 const Key& getKey() const override { 287 return *reinterpret_cast<SkResourceCache::Key*>(fKey.get()); 288 } 289 290 size_t bytesUsed() const override { return fTessellations->size(); } 291 292 const char* getCategory() const override { return "tessellated shadow masks"; } 293 294 sk_sp<CachedTessellations> refTessellations() const { return fTessellations; } 295 296 template <typename FACTORY> 297 sk_sp<SkVertices> find(const FACTORY& factory, const SkMatrix& matrix, 298 SkVector* translate) const { 299 return fTessellations->find(factory, matrix, translate); 300 } 301 302 private: 303 std::unique_ptr<uint8_t[]> fKey; 304 sk_sp<CachedTessellations> fTessellations; 305 }; 306 307 /** 308 * Used by FindVisitor to determine whether a cache entry can be reused and if so returns the 309 * vertices and a translation vector. If the CachedTessellations does not contain a suitable 310 * mesh then we inform SkResourceCache to destroy the Rec and we return the CachedTessellations 311 * to the caller. The caller will update it and reinsert it back into the cache. 312 */ 313 template <typename FACTORY> 314 struct FindContext { 315 FindContext(const SkMatrix* viewMatrix, const FACTORY* factory) 316 : fViewMatrix(viewMatrix), fFactory(factory) {} 317 const SkMatrix* const fViewMatrix; 318 // If this is valid after Find is called then we found the vertices and they should be drawn 319 // with fTranslate applied. 320 sk_sp<SkVertices> fVertices; 321 SkVector fTranslate = {0, 0}; 322 323 // If this is valid after Find then the caller should add the vertices to the tessellation set 324 // and create a new CachedTessellationsRec and insert it into SkResourceCache. 325 sk_sp<CachedTessellations> fTessellationsOnFailure; 326 327 const FACTORY* fFactory; 328 }; 329 330 /** 331 * Function called by SkResourceCache when a matching cache key is found. The FACTORY and matrix of 332 * the FindContext are used to determine if the vertices are reusable. If so the vertices and 333 * necessary translation vector are set on the FindContext. 334 */ 335 template <typename FACTORY> 336 bool FindVisitor(const SkResourceCache::Rec& baseRec, void* ctx) { 337 FindContext<FACTORY>* findContext = (FindContext<FACTORY>*)ctx; 338 const CachedTessellationsRec& rec = static_cast<const CachedTessellationsRec&>(baseRec); 339 findContext->fVertices = 340 rec.find(*findContext->fFactory, *findContext->fViewMatrix, &findContext->fTranslate); 341 if (findContext->fVertices) { 342 return true; 343 } 344 // We ref the tessellations and let the cache destroy the Rec. Once the tessellations have been 345 // manipulated we will add a new Rec. 346 findContext->fTessellationsOnFailure = rec.refTessellations(); 347 return false; 348 } 349 350 class ShadowedPath { 351 public: 352 ShadowedPath(const SkPath* path, const SkMatrix* viewMatrix) 353 : fPath(path) 354 , fViewMatrix(viewMatrix) 355 #if SK_SUPPORT_GPU 356 , fShapeForKey(*path, GrStyle::SimpleFill()) 357 #endif 358 {} 359 360 const SkPath& path() const { return *fPath; } 361 const SkMatrix& viewMatrix() const { return *fViewMatrix; } 362 #if SK_SUPPORT_GPU 363 /** Negative means the vertices should not be cached for this path. */ 364 int keyBytes() const { return fShapeForKey.unstyledKeySize() * sizeof(uint32_t); } 365 void writeKey(void* key) const { 366 fShapeForKey.writeUnstyledKey(reinterpret_cast<uint32_t*>(key)); 367 } 368 bool isRRect(SkRRect* rrect) { return fShapeForKey.asRRect(rrect, nullptr, nullptr, nullptr); } 369 #else 370 int keyBytes() const { return -1; } 371 void writeKey(void* key) const { SkFAIL("Should never be called"); } 372 bool isRRect(SkRRect* rrect) { return false; } 373 #endif 374 375 private: 376 const SkPath* fPath; 377 const SkMatrix* fViewMatrix; 378 #if SK_SUPPORT_GPU 379 GrShape fShapeForKey; 380 #endif 381 }; 382 383 // This creates a domain of keys in SkResourceCache used by this file. 384 static void* kNamespace; 385 386 /** 387 * Draws a shadow to 'canvas'. The vertices used to draw the shadow are created by 'factory' unless 388 * they are first found in SkResourceCache. 389 */ 390 template <typename FACTORY> 391 void draw_shadow(const FACTORY& factory, 392 std::function<void(const SkVertices*, SkBlendMode, const SkPaint&, 393 SkScalar tx, SkScalar ty)> drawProc, ShadowedPath& path, SkColor color) { 394 FindContext<FACTORY> context(&path.viewMatrix(), &factory); 395 396 SkResourceCache::Key* key = nullptr; 397 SkAutoSTArray<32 * 4, uint8_t> keyStorage; 398 int keyDataBytes = path.keyBytes(); 399 if (keyDataBytes >= 0) { 400 keyStorage.reset(keyDataBytes + sizeof(SkResourceCache::Key)); 401 key = new (keyStorage.begin()) SkResourceCache::Key(); 402 path.writeKey((uint32_t*)(keyStorage.begin() + sizeof(*key))); 403 key->init(&kNamespace, resource_cache_shared_id(), keyDataBytes); 404 SkResourceCache::Find(*key, FindVisitor<FACTORY>, &context); 405 } 406 407 sk_sp<SkVertices> vertices; 408 bool foundInCache = SkToBool(context.fVertices); 409 if (foundInCache) { 410 vertices = std::move(context.fVertices); 411 } else { 412 // TODO: handle transforming the path as part of the tessellator 413 if (key) { 414 // Update or initialize a tessellation set and add it to the cache. 415 sk_sp<CachedTessellations> tessellations; 416 if (context.fTessellationsOnFailure) { 417 tessellations = std::move(context.fTessellationsOnFailure); 418 } else { 419 tessellations.reset(new CachedTessellations()); 420 } 421 vertices = tessellations->add(path.path(), factory, path.viewMatrix(), 422 &context.fTranslate); 423 if (!vertices) { 424 return; 425 } 426 auto rec = new CachedTessellationsRec(*key, std::move(tessellations)); 427 SkResourceCache::Add(rec); 428 } else { 429 vertices = factory.makeVertices(path.path(), path.viewMatrix(), 430 &context.fTranslate); 431 if (!vertices) { 432 return; 433 } 434 } 435 } 436 437 SkPaint paint; 438 // Run the vertex color through a GaussianColorFilter and then modulate the grayscale result of 439 // that against our 'color' param. 440 paint.setColorFilter(SkColorFilter::MakeComposeFilter( 441 SkColorFilter::MakeModeFilter(color, SkBlendMode::kModulate), 442 SkGaussianColorFilter::Make())); 443 444 drawProc(vertices.get(), SkBlendMode::kModulate, paint, 445 context.fTranslate.fX, context.fTranslate.fY); 446 } 447 } 448 449 static bool tilted(const SkPoint3& zPlaneParams) { 450 return !SkScalarNearlyZero(zPlaneParams.fX) || !SkScalarNearlyZero(zPlaneParams.fY); 451 } 452 453 static SkPoint3 map(const SkMatrix& m, const SkPoint3& pt) { 454 SkPoint3 result; 455 m.mapXY(pt.fX, pt.fY, (SkPoint*)&result.fX); 456 result.fZ = pt.fZ; 457 return result; 458 } 459 460 static SkColor compute_render_color(SkColor color, float alpha, bool useTonalColor) { 461 if (useTonalColor) { 462 SkScalar colorScale; 463 SkScalar tonalAlpha; 464 SkColor4f color4f = SkColor4f::FromColor(color); 465 SkShadowUtils::ComputeTonalColorParams(color4f.fR, 466 color4f.fG, 467 color4f.fB, 468 alpha, 469 &colorScale, &tonalAlpha); 470 // After pre-multiplying, we want the alpha to be scaled by tonalAlpha, and 471 // the color scaled by colorScale. This scale factor gives that. 472 SkScalar unPremulScale = colorScale / tonalAlpha; 473 474 return SkColorSetARGB(tonalAlpha*255.999f, unPremulScale*SkColorGetR(color), 475 unPremulScale*SkColorGetG(color), unPremulScale*SkColorGetB(color)); 476 } 477 478 return SkColorSetARGB(alpha*SkColorGetA(color), SkColorGetR(color), 479 SkColorGetG(color), SkColorGetB(color)); 480 } 481 482 // Draw an offset spot shadow and outlining ambient shadow for the given path. 483 void SkShadowUtils::DrawShadow(SkCanvas* canvas, const SkPath& path, const SkPoint3& zPlaneParams, 484 const SkPoint3& devLightPos, SkScalar lightRadius, 485 SkScalar ambientAlpha, SkScalar spotAlpha, SkColor color, 486 uint32_t flags) { 487 SkMatrix inverse; 488 if (!canvas->getTotalMatrix().invert(&inverse)) { 489 return; 490 } 491 SkPoint pt = inverse.mapXY(devLightPos.fX, devLightPos.fY); 492 493 SkDrawShadowRec rec; 494 rec.fZPlaneParams = zPlaneParams; 495 rec.fLightPos = { pt.fX, pt.fY, devLightPos.fZ }; 496 rec.fLightRadius = lightRadius; 497 rec.fAmbientAlpha = SkScalarToFloat(ambientAlpha); 498 rec.fSpotAlpha = SkScalarToFloat(spotAlpha); 499 rec.fColor = color; 500 rec.fFlags = flags; 501 502 canvas->private_draw_shadow_rec(path, rec); 503 } 504 505 void SkBaseDevice::drawShadow(const SkPath& path, const SkDrawShadowRec& rec) { 506 auto drawVertsProc = [this](const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint, 507 SkScalar tx, SkScalar ty) { 508 SkAutoDeviceCTMRestore adr(this, SkMatrix::Concat(this->ctm(), 509 SkMatrix::MakeTrans(tx, ty))); 510 this->drawVertices(vertices, mode, paint); 511 }; 512 513 SkMatrix viewMatrix = this->ctm(); 514 SkAutoDeviceCTMRestore adr(this, SkMatrix::I()); 515 516 ShadowedPath shadowedPath(&path, &viewMatrix); 517 518 bool tiltZPlane = tilted(rec.fZPlaneParams); 519 bool transparent = SkToBool(rec.fFlags & SkShadowFlags::kTransparentOccluder_ShadowFlag); 520 bool uncached = tiltZPlane || path.isVolatile(); 521 bool useTonalColor = SkToBool(rec.fFlags & kTonalColor_ShadowFlag); 522 523 SkColor color = rec.fColor; 524 SkPoint3 zPlaneParams = rec.fZPlaneParams; 525 SkPoint3 devLightPos = map(viewMatrix, rec.fLightPos); 526 float lightRadius = rec.fLightRadius; 527 528 float ambientAlpha = rec.fAmbientAlpha; 529 if (ambientAlpha > 0) { 530 ambientAlpha = SkTMin(ambientAlpha, 1.f); 531 SkColor renderColor; 532 if (useTonalColor) { 533 renderColor = compute_render_color(SK_ColorBLACK, ambientAlpha, false); 534 } else { 535 renderColor = compute_render_color(color, ambientAlpha, false); 536 } 537 if (uncached) { 538 sk_sp<SkVertices> vertices = SkShadowTessellator::MakeAmbient(path, viewMatrix, 539 zPlaneParams, 540 transparent); 541 if (vertices) { 542 SkPaint paint; 543 // Run the vertex color through a GaussianColorFilter and then modulate the 544 // grayscale result of that against our 'color' param. 545 paint.setColorFilter(SkColorFilter::MakeComposeFilter( 546 SkColorFilter::MakeModeFilter(renderColor, SkBlendMode::kModulate), 547 SkGaussianColorFilter::Make())); 548 this->drawVertices(vertices.get(), SkBlendMode::kModulate, paint); 549 } 550 } else { 551 AmbientVerticesFactory factory; 552 factory.fOccluderHeight = zPlaneParams.fZ; 553 factory.fTransparent = transparent; 554 if (viewMatrix.hasPerspective()) { 555 factory.fOffset.set(0, 0); 556 } else { 557 factory.fOffset.fX = viewMatrix.getTranslateX(); 558 factory.fOffset.fY = viewMatrix.getTranslateY(); 559 } 560 561 draw_shadow(factory, drawVertsProc, shadowedPath, renderColor); 562 } 563 } 564 565 float spotAlpha = rec.fSpotAlpha; 566 if (spotAlpha > 0) { 567 spotAlpha = SkTMin(spotAlpha, 1.f); 568 SkColor renderColor = compute_render_color(color, spotAlpha, useTonalColor); 569 if (uncached) { 570 sk_sp<SkVertices> vertices = SkShadowTessellator::MakeSpot(path, viewMatrix, 571 zPlaneParams, 572 devLightPos, lightRadius, 573 transparent); 574 if (vertices) { 575 SkPaint paint; 576 // Run the vertex color through a GaussianColorFilter and then modulate the 577 // grayscale result of that against our 'color' param. 578 paint.setColorFilter(SkColorFilter::MakeComposeFilter( 579 SkColorFilter::MakeModeFilter(renderColor, SkBlendMode::kModulate), 580 SkGaussianColorFilter::Make())); 581 this->drawVertices(vertices.get(), SkBlendMode::kModulate, paint); 582 } 583 } else { 584 SpotVerticesFactory factory; 585 SkScalar occluderHeight = zPlaneParams.fZ; 586 float zRatio = SkTPin(occluderHeight / (devLightPos.fZ - occluderHeight), 0.0f, 0.95f); 587 SkScalar radius = lightRadius * zRatio; 588 589 // Compute the scale and translation for the spot shadow. 590 SkScalar scale = devLightPos.fZ / (devLightPos.fZ - occluderHeight); 591 SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY()); 592 factory.fLocalCenter = center; 593 viewMatrix.mapPoints(¢er, 1); 594 factory.fOffset = SkVector::Make(zRatio * (center.fX - devLightPos.fX), 595 zRatio * (center.fY - devLightPos.fY)); 596 factory.fOccluderHeight = occluderHeight; 597 factory.fDevLightPos = devLightPos; 598 factory.fLightRadius = lightRadius; 599 SkRect devBounds; 600 viewMatrix.mapRect(&devBounds, path.getBounds()); 601 if (transparent || 602 SkTAbs(factory.fOffset.fX) > 0.5f*devBounds.width() || 603 SkTAbs(factory.fOffset.fY) > 0.5f*devBounds.height()) { 604 // if the translation of the shadow is big enough we're going to end up 605 // filling the entire umbra, so we can treat these as all the same 606 factory.fOccluderType = SpotVerticesFactory::OccluderType::kTransparent; 607 } else if (factory.fOffset.length()*scale + scale < radius) { 608 // if we don't translate more than the blur distance, can assume umbra is covered 609 factory.fOccluderType = SpotVerticesFactory::OccluderType::kOpaqueNoUmbra; 610 } else { 611 factory.fOccluderType = SpotVerticesFactory::OccluderType::kOpaquePartialUmbra; 612 } 613 // need to add this after we classify the shadow 614 factory.fOffset.fX += viewMatrix.getTranslateX(); 615 factory.fOffset.fY += viewMatrix.getTranslateY(); 616 #ifdef DEBUG_SHADOW_CHECKS 617 switch (factory.fOccluderType) { 618 case SpotVerticesFactory::OccluderType::kTransparent: 619 color = 0xFFD2B48C; // tan for transparent 620 break; 621 case SpotVerticesFactory::OccluderType::kOpaquePartialUmbra: 622 color = 0xFFFFA500; // orange for opaque 623 break; 624 case SpotVerticesFactory::OccluderType::kOpaqueNoUmbra: 625 color = 0xFFE5E500; // corn yellow for covered 626 break; 627 } 628 #endif 629 draw_shadow(factory, drawVertsProc, shadowedPath, renderColor); 630 } 631 } 632 } 633