Flutter Engine
The Flutter Engine
SkShadowUtils.cpp
Go to the documentation of this file.
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
9
18#include "include/core/SkPath.h"
21#include "include/core/SkRect.h"
29#include "src/base/SkRandom.h"
30#include "src/core/SkBlurMask.h"
32#include "src/core/SkDevice.h"
34#include "src/core/SkPathPriv.h"
37
38#if !defined(SK_ENABLE_OPTIMIZE_SIZE)
40#endif
41
42#if defined(SK_GANESH)
45#endif
46
47#include <algorithm>
48#include <cstring>
49#include <functional>
50#include <memory>
51#include <new>
52#include <utility>
53
54using namespace skia_private;
55
56class SkRRect;
57
58///////////////////////////////////////////////////////////////////////////////////////////////////
59
60#if !defined(SK_ENABLE_OPTIMIZE_SIZE)
61namespace {
62
63uint64_t resource_cache_shared_id() {
64 return 0x2020776f64616873llu; // 'shadow '
65}
66
67/** Factory for an ambient shadow mesh with particular shadow properties. */
68struct AmbientVerticesFactory {
69 SkScalar fOccluderHeight = SK_ScalarNaN; // NaN so that isCompatible will fail until init'ed.
70 bool fTransparent;
71 SkVector fOffset;
72
73 bool isCompatible(const AmbientVerticesFactory& that, SkVector* translate) const {
74 if (fOccluderHeight != that.fOccluderHeight || fTransparent != that.fTransparent) {
75 return false;
76 }
77 *translate = that.fOffset;
78 return true;
79 }
80
81 sk_sp<SkVertices> makeVertices(const SkPath& path, const SkMatrix& ctm,
82 SkVector* translate) const {
83 SkPoint3 zParams = SkPoint3::Make(0, 0, fOccluderHeight);
84 // pick a canonical place to generate shadow
85 SkMatrix noTrans(ctm);
86 if (!ctm.hasPerspective()) {
87 noTrans[SkMatrix::kMTransX] = 0;
88 noTrans[SkMatrix::kMTransY] = 0;
89 }
90 *translate = fOffset;
91 return SkShadowTessellator::MakeAmbient(path, noTrans, zParams, fTransparent);
92 }
93};
94
95/** Factory for an spot shadow mesh with particular shadow properties. */
96struct SpotVerticesFactory {
97 enum class OccluderType {
98 // The umbra cannot be dropped out because either the occluder is not opaque,
99 // or the center of the umbra is visible. Uses point light.
100 kPointTransparent,
101 // The umbra can be dropped where it is occluded. Uses point light.
102 kPointOpaquePartialUmbra,
103 // It is known that the entire umbra is occluded. Uses point light.
104 kPointOpaqueNoUmbra,
105 // Uses directional light.
106 kDirectional,
107 // The umbra can't be dropped out. Uses directional light.
108 kDirectionalTransparent,
109 };
110
111 SkVector fOffset;
112 SkPoint fLocalCenter;
113 SkScalar fOccluderHeight = SK_ScalarNaN; // NaN so that isCompatible will fail until init'ed.
114 SkPoint3 fDevLightPos;
115 SkScalar fLightRadius;
116 OccluderType fOccluderType;
117
118 bool isCompatible(const SpotVerticesFactory& that, SkVector* translate) const {
119 if (fOccluderHeight != that.fOccluderHeight || fDevLightPos.fZ != that.fDevLightPos.fZ ||
120 fLightRadius != that.fLightRadius || fOccluderType != that.fOccluderType) {
121 return false;
122 }
123 switch (fOccluderType) {
124 case OccluderType::kPointTransparent:
125 case OccluderType::kPointOpaqueNoUmbra:
126 // 'this' and 'that' will either both have no umbra removed or both have all the
127 // umbra removed.
128 *translate = that.fOffset;
129 return true;
130 case OccluderType::kPointOpaquePartialUmbra:
131 // In this case we partially remove the umbra differently for 'this' and 'that'
132 // if the offsets don't match.
133 if (fOffset == that.fOffset) {
134 translate->set(0, 0);
135 return true;
136 }
137 return false;
138 case OccluderType::kDirectional:
139 case OccluderType::kDirectionalTransparent:
140 *translate = that.fOffset - fOffset;
141 return true;
142 }
143 SK_ABORT("Uninitialized occluder type?");
144 }
145
146 sk_sp<SkVertices> makeVertices(const SkPath& path, const SkMatrix& ctm,
147 SkVector* translate) const {
148 bool transparent = fOccluderType == OccluderType::kPointTransparent ||
149 fOccluderType == OccluderType::kDirectionalTransparent;
150 bool directional = fOccluderType == OccluderType::kDirectional ||
151 fOccluderType == OccluderType::kDirectionalTransparent;
152 SkPoint3 zParams = SkPoint3::Make(0, 0, fOccluderHeight);
153 if (directional) {
154 translate->set(0, 0);
155 return SkShadowTessellator::MakeSpot(path, ctm, zParams, fDevLightPos, fLightRadius,
156 transparent, true);
157 } else if (ctm.hasPerspective() || OccluderType::kPointOpaquePartialUmbra == fOccluderType) {
158 translate->set(0, 0);
159 return SkShadowTessellator::MakeSpot(path, ctm, zParams, fDevLightPos, fLightRadius,
160 transparent, false);
161 } else {
162 // pick a canonical place to generate shadow, with light centered over path
163 SkMatrix noTrans(ctm);
164 noTrans[SkMatrix::kMTransX] = 0;
165 noTrans[SkMatrix::kMTransY] = 0;
166 SkPoint devCenter(fLocalCenter);
167 noTrans.mapPoints(&devCenter, 1);
168 SkPoint3 centerLightPos = SkPoint3::Make(devCenter.fX, devCenter.fY, fDevLightPos.fZ);
169 *translate = fOffset;
170 return SkShadowTessellator::MakeSpot(path, noTrans, zParams,
171 centerLightPos, fLightRadius, transparent, false);
172 }
173 }
174};
175
176/**
177 * This manages a set of tessellations for a given shape in the cache. Because SkResourceCache
178 * records are immutable this is not itself a Rec. When we need to update it we return this on
179 * the FindVisitor and let the cache destroy the Rec. We'll update the tessellations and then add
180 * a new Rec with an adjusted size for any deletions/additions.
181 */
182class CachedTessellations : public SkRefCnt {
183public:
184 size_t size() const { return fAmbientSet.size() + fSpotSet.size(); }
185
186 sk_sp<SkVertices> find(const AmbientVerticesFactory& ambient, const SkMatrix& matrix,
187 SkVector* translate) const {
188 return fAmbientSet.find(ambient, matrix, translate);
189 }
190
191 sk_sp<SkVertices> add(const SkPath& devPath, const AmbientVerticesFactory& ambient,
192 const SkMatrix& matrix, SkVector* translate) {
193 return fAmbientSet.add(devPath, ambient, matrix, translate);
194 }
195
196 sk_sp<SkVertices> find(const SpotVerticesFactory& spot, const SkMatrix& matrix,
197 SkVector* translate) const {
198 return fSpotSet.find(spot, matrix, translate);
199 }
200
201 sk_sp<SkVertices> add(const SkPath& devPath, const SpotVerticesFactory& spot,
202 const SkMatrix& matrix, SkVector* translate) {
203 return fSpotSet.add(devPath, spot, matrix, translate);
204 }
205
206private:
207 template <typename FACTORY, int MAX_ENTRIES>
208 class Set {
209 public:
210 size_t size() const { return fSize; }
211
212 sk_sp<SkVertices> find(const FACTORY& factory, const SkMatrix& matrix,
213 SkVector* translate) const {
214 for (int i = 0; i < MAX_ENTRIES; ++i) {
215 if (fEntries[i].fFactory.isCompatible(factory, translate)) {
216 const SkMatrix& m = fEntries[i].fMatrix;
217 if (matrix.hasPerspective() || m.hasPerspective()) {
218 if (matrix != fEntries[i].fMatrix) {
219 continue;
220 }
221 } else if (matrix.getScaleX() != m.getScaleX() ||
222 matrix.getSkewX() != m.getSkewX() ||
223 matrix.getScaleY() != m.getScaleY() ||
224 matrix.getSkewY() != m.getSkewY()) {
225 continue;
226 }
227 return fEntries[i].fVertices;
228 }
229 }
230 return nullptr;
231 }
232
233 sk_sp<SkVertices> add(const SkPath& path, const FACTORY& factory, const SkMatrix& matrix,
234 SkVector* translate) {
235 sk_sp<SkVertices> vertices = factory.makeVertices(path, matrix, translate);
236 if (!vertices) {
237 return nullptr;
238 }
239 int i;
240 if (fCount < MAX_ENTRIES) {
241 i = fCount++;
242 } else {
243 i = fRandom.nextULessThan(MAX_ENTRIES);
244 fSize -= fEntries[i].fVertices->approximateSize();
245 }
246 fEntries[i].fFactory = factory;
247 fEntries[i].fVertices = vertices;
248 fEntries[i].fMatrix = matrix;
249 fSize += vertices->approximateSize();
250 return vertices;
251 }
252
253 private:
254 struct Entry {
255 FACTORY fFactory;
256 sk_sp<SkVertices> fVertices;
258 };
259 Entry fEntries[MAX_ENTRIES];
260 int fCount = 0;
261 size_t fSize = 0;
262 SkRandom fRandom;
263 };
264
265 Set<AmbientVerticesFactory, 4> fAmbientSet;
266 Set<SpotVerticesFactory, 4> fSpotSet;
267};
268
269/**
270 * A record of shadow vertices stored in SkResourceCache of CachedTessellations for a particular
271 * path. The key represents the path's geometry and not any shadow params.
272 */
273class CachedTessellationsRec : public SkResourceCache::Rec {
274public:
275 CachedTessellationsRec(const SkResourceCache::Key& key,
276 sk_sp<CachedTessellations> tessellations)
277 : fTessellations(std::move(tessellations)) {
278 fKey.reset(new uint8_t[key.size()]);
279 memcpy(fKey.get(), &key, key.size());
280 }
281
282 const Key& getKey() const override {
283 return *reinterpret_cast<SkResourceCache::Key*>(fKey.get());
284 }
285
286 size_t bytesUsed() const override { return fTessellations->size(); }
287
288 const char* getCategory() const override { return "tessellated shadow masks"; }
289
290 sk_sp<CachedTessellations> refTessellations() const { return fTessellations; }
291
292 template <typename FACTORY>
293 sk_sp<SkVertices> find(const FACTORY& factory, const SkMatrix& matrix,
294 SkVector* translate) const {
295 return fTessellations->find(factory, matrix, translate);
296 }
297
298private:
299 std::unique_ptr<uint8_t[]> fKey;
300 sk_sp<CachedTessellations> fTessellations;
301};
302
303/**
304 * Used by FindVisitor to determine whether a cache entry can be reused and if so returns the
305 * vertices and a translation vector. If the CachedTessellations does not contain a suitable
306 * mesh then we inform SkResourceCache to destroy the Rec and we return the CachedTessellations
307 * to the caller. The caller will update it and reinsert it back into the cache.
308 */
309template <typename FACTORY>
310struct FindContext {
311 FindContext(const SkMatrix* viewMatrix, const FACTORY* factory)
312 : fViewMatrix(viewMatrix), fFactory(factory) {}
313 const SkMatrix* const fViewMatrix;
314 // If this is valid after Find is called then we found the vertices and they should be drawn
315 // with fTranslate applied.
316 sk_sp<SkVertices> fVertices;
317 SkVector fTranslate = {0, 0};
318
319 // If this is valid after Find then the caller should add the vertices to the tessellation set
320 // and create a new CachedTessellationsRec and insert it into SkResourceCache.
321 sk_sp<CachedTessellations> fTessellationsOnFailure;
322
323 const FACTORY* fFactory;
324};
325
326/**
327 * Function called by SkResourceCache when a matching cache key is found. The FACTORY and matrix of
328 * the FindContext are used to determine if the vertices are reusable. If so the vertices and
329 * necessary translation vector are set on the FindContext.
330 */
331template <typename FACTORY>
332bool FindVisitor(const SkResourceCache::Rec& baseRec, void* ctx) {
333 FindContext<FACTORY>* findContext = (FindContext<FACTORY>*)ctx;
334 const CachedTessellationsRec& rec = static_cast<const CachedTessellationsRec&>(baseRec);
335 findContext->fVertices =
336 rec.find(*findContext->fFactory, *findContext->fViewMatrix, &findContext->fTranslate);
337 if (findContext->fVertices) {
338 return true;
339 }
340 // We ref the tessellations and let the cache destroy the Rec. Once the tessellations have been
341 // manipulated we will add a new Rec.
342 findContext->fTessellationsOnFailure = rec.refTessellations();
343 return false;
344}
345
346class ShadowedPath {
347public:
348 ShadowedPath(const SkPath* path, const SkMatrix* viewMatrix)
349 : fPath(path)
350 , fViewMatrix(viewMatrix)
351#if defined(SK_GANESH)
352 , fShapeForKey(*path, GrStyle::SimpleFill())
353#endif
354 {}
355
356 const SkPath& path() const { return *fPath; }
357 const SkMatrix& viewMatrix() const { return *fViewMatrix; }
358#if defined(SK_GANESH)
359 /** Negative means the vertices should not be cached for this path. */
360 int keyBytes() const { return fShapeForKey.unstyledKeySize() * sizeof(uint32_t); }
361 void writeKey(void* key) const {
362 fShapeForKey.writeUnstyledKey(reinterpret_cast<uint32_t*>(key));
363 }
364 bool isRRect(SkRRect* rrect) { return fShapeForKey.asRRect(rrect, nullptr); }
365#else
366 int keyBytes() const { return -1; }
367 void writeKey(void* key) const { SK_ABORT("Should never be called"); }
368 bool isRRect(SkRRect* rrect) { return false; }
369#endif
370
371private:
372 const SkPath* fPath;
373 const SkMatrix* fViewMatrix;
374#if defined(SK_GANESH)
375 GrStyledShape fShapeForKey;
376#endif
377};
378
379// This creates a domain of keys in SkResourceCache used by this file.
380static void* kNamespace;
381
382// When the SkPathRef genID changes, invalidate a corresponding GrResource described by key.
383class ShadowInvalidator : public SkIDChangeListener {
384public:
385 ShadowInvalidator(const SkResourceCache::Key& key) {
386 fKey.reset(new uint8_t[key.size()]);
387 memcpy(fKey.get(), &key, key.size());
388 }
389
390private:
391 const SkResourceCache::Key& getKey() const {
392 return *reinterpret_cast<SkResourceCache::Key*>(fKey.get());
393 }
394
395 // always purge
396 static bool FindVisitor(const SkResourceCache::Rec&, void*) {
397 return false;
398 }
399
400 void changed() override {
401 SkResourceCache::Find(this->getKey(), ShadowInvalidator::FindVisitor, nullptr);
402 }
403
404 std::unique_ptr<uint8_t[]> fKey;
405};
406
407/**
408 * Draws a shadow to 'canvas'. The vertices used to draw the shadow are created by 'factory' unless
409 * they are first found in SkResourceCache.
410 */
411template <typename FACTORY>
412bool draw_shadow(const FACTORY& factory,
413 std::function<void(const SkVertices*, SkBlendMode, const SkPaint&,
414 SkScalar tx, SkScalar ty, bool)> drawProc, ShadowedPath& path, SkColor color) {
415 FindContext<FACTORY> context(&path.viewMatrix(), &factory);
416
417 SkResourceCache::Key* key = nullptr;
419 int keyDataBytes = path.keyBytes();
420 if (keyDataBytes >= 0) {
421 keyStorage.reset(keyDataBytes + sizeof(SkResourceCache::Key));
422 key = new (keyStorage.begin()) SkResourceCache::Key();
423 path.writeKey((uint32_t*)(keyStorage.begin() + sizeof(*key)));
424 key->init(&kNamespace, resource_cache_shared_id(), keyDataBytes);
425 SkResourceCache::Find(*key, FindVisitor<FACTORY>, &context);
426 }
427
428 sk_sp<SkVertices> vertices;
429 bool foundInCache = SkToBool(context.fVertices);
430 if (foundInCache) {
431 vertices = std::move(context.fVertices);
432 } else {
433 // TODO: handle transforming the path as part of the tessellator
434 if (key) {
435 // Update or initialize a tessellation set and add it to the cache.
436 sk_sp<CachedTessellations> tessellations;
437 if (context.fTessellationsOnFailure) {
438 tessellations = std::move(context.fTessellationsOnFailure);
439 } else {
440 tessellations.reset(new CachedTessellations());
441 }
442 vertices = tessellations->add(path.path(), factory, path.viewMatrix(),
443 &context.fTranslate);
444 if (!vertices) {
445 return false;
446 }
447 auto rec = new CachedTessellationsRec(*key, std::move(tessellations));
448 SkPathPriv::AddGenIDChangeListener(path.path(), sk_make_sp<ShadowInvalidator>(*key));
450 } else {
451 vertices = factory.makeVertices(path.path(), path.viewMatrix(),
452 &context.fTranslate);
453 if (!vertices) {
454 return false;
455 }
456 }
457 }
458
460 // Run the vertex color through a GaussianColorFilter and then modulate the grayscale result of
461 // that against our 'color' param.
462 paint.setColorFilter(
465
466 drawProc(vertices.get(), SkBlendMode::kModulate, paint,
467 context.fTranslate.fX, context.fTranslate.fY, path.viewMatrix().hasPerspective());
468
469 return true;
470}
471} // namespace
472
473static bool tilted(const SkPoint3& zPlaneParams) {
474 return !SkScalarNearlyZero(zPlaneParams.fX) || !SkScalarNearlyZero(zPlaneParams.fY);
475}
476#endif // SK_ENABLE_OPTIMIZE_SIZE
477
478void SkShadowUtils::ComputeTonalColors(SkColor inAmbientColor, SkColor inSpotColor,
479 SkColor* outAmbientColor, SkColor* outSpotColor) {
480 // For tonal color we only compute color values for the spot shadow.
481 // The ambient shadow is greyscale only.
482
483 // Ambient
484 *outAmbientColor = SkColorSetARGB(SkColorGetA(inAmbientColor), 0, 0, 0);
485
486 // Spot
487 int spotR = SkColorGetR(inSpotColor);
488 int spotG = SkColorGetG(inSpotColor);
489 int spotB = SkColorGetB(inSpotColor);
490 int max = std::max(std::max(spotR, spotG), spotB);
491 int min = std::min(std::min(spotR, spotG), spotB);
492 SkScalar luminance = 0.5f*(max + min)/255.f;
493 SkScalar origA = SkColorGetA(inSpotColor)/255.f;
494
495 // We compute a color alpha value based on the luminance of the color, scaled by an
496 // adjusted alpha value. We want the following properties to match the UX examples
497 // (assuming a = 0.25) and to ensure that we have reasonable results when the color
498 // is black and/or the alpha is 0:
499 // f(0, a) = 0
500 // f(luminance, 0) = 0
501 // f(1, 0.25) = .5
502 // f(0.5, 0.25) = .4
503 // f(1, 1) = 1
504 // The following functions match this as closely as possible.
505 SkScalar alphaAdjust = (2.6f + (-2.66667f + 1.06667f*origA)*origA)*origA;
506 SkScalar colorAlpha = (3.544762f + (-4.891428f + 2.3466f*luminance)*luminance)*luminance;
507 colorAlpha = SkTPin(alphaAdjust*colorAlpha, 0.0f, 1.0f);
508
509 // Similarly, we set the greyscale alpha based on luminance and alpha so that
510 // f(0, a) = a
511 // f(luminance, 0) = 0
512 // f(1, 0.25) = 0.15
513 SkScalar greyscaleAlpha = SkTPin(origA*(1 - 0.4f*luminance), 0.0f, 1.0f);
514
515 // The final color we want to emulate is generated by rendering a color shadow (C_rgb) using an
516 // alpha computed from the color's luminance (C_a), and then a black shadow with alpha (S_a)
517 // which is an adjusted value of 'a'. Assuming SrcOver, a background color of B_rgb, and
518 // ignoring edge falloff, this becomes
519 //
520 // (C_a - S_a*C_a)*C_rgb + (1 - (S_a + C_a - S_a*C_a))*B_rgb
521 //
522 // Assuming premultiplied alpha, this means we scale the color by (C_a - S_a*C_a) and
523 // set the alpha to (S_a + C_a - S_a*C_a).
524 SkScalar colorScale = colorAlpha*(SK_Scalar1 - greyscaleAlpha);
525 SkScalar tonalAlpha = colorScale + greyscaleAlpha;
526 SkScalar unPremulScale = colorScale / tonalAlpha;
527 *outSpotColor = SkColorSetARGB(tonalAlpha*255.999f,
528 unPremulScale*spotR,
529 unPremulScale*spotG,
530 unPremulScale*spotB);
531}
532
533static bool fill_shadow_rec(const SkPath& path, const SkPoint3& zPlaneParams,
534 const SkPoint3& lightPos, SkScalar lightRadius,
535 SkColor ambientColor, SkColor spotColor,
536 uint32_t flags, const SkMatrix& ctm, SkDrawShadowRec* rec) {
537 SkPoint pt = { lightPos.fX, lightPos.fY };
539 // If light position is in device space, need to transform to local space
540 // before applying to SkCanvas.
541 SkMatrix inverse;
542 if (!ctm.invert(&inverse)) {
543 return false;
544 }
545 inverse.mapPoints(&pt, 1);
546 }
547
548 rec->fZPlaneParams = zPlaneParams;
549 rec->fLightPos = { pt.fX, pt.fY, lightPos.fZ };
550 rec->fLightRadius = lightRadius;
551 rec->fAmbientColor = ambientColor;
552 rec->fSpotColor = spotColor;
553 rec->fFlags = flags;
554
555 return true;
556}
557
558// Draw an offset spot shadow and outlining ambient shadow for the given path.
559void SkShadowUtils::DrawShadow(SkCanvas* canvas, const SkPath& path, const SkPoint3& zPlaneParams,
560 const SkPoint3& lightPos, SkScalar lightRadius,
561 SkColor ambientColor, SkColor spotColor,
562 uint32_t flags) {
563 SkDrawShadowRec rec;
564 if (!fill_shadow_rec(path, zPlaneParams, lightPos, lightRadius, ambientColor, spotColor,
565 flags, canvas->getTotalMatrix(), &rec)) {
566 return;
567 }
568
569 canvas->private_draw_shadow_rec(path, rec);
570}
571
573 const SkPoint3& zPlaneParams, const SkPoint3& lightPos,
574 SkScalar lightRadius, uint32_t flags, SkRect* bounds) {
575 SkDrawShadowRec rec;
576 if (!fill_shadow_rec(path, zPlaneParams, lightPos, lightRadius, SK_ColorBLACK, SK_ColorBLACK,
577 flags, ctm, &rec)) {
578 return false;
579 }
580
582
583 return true;
584}
585
586//////////////////////////////////////////////////////////////////////////////////////////////
587
588static bool validate_rec(const SkDrawShadowRec& rec) {
589 return rec.fLightPos.isFinite() && rec.fZPlaneParams.isFinite() &&
591}
592
594 if (!validate_rec(rec)) {
595 return;
596 }
597
598 SkMatrix viewMatrix = this->localToDevice();
600
601#if !defined(SK_ENABLE_OPTIMIZE_SIZE)
602 auto drawVertsProc = [this](const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint,
603 SkScalar tx, SkScalar ty, bool hasPerspective) {
604 if (vertices->priv().vertexCount()) {
605 // For perspective shadows we've already computed the shadow in world space,
606 // and we can't translate it without changing it. Otherwise we concat the
607 // change in translation from the cached version.
609 this,
610 hasPerspective ? SkMatrix::I()
611 : this->localToDevice() * SkMatrix::Translate(tx, ty));
612 // The vertex colors for a tesselated shadow polygon are always either opaque black
613 // or transparent and their real contribution to the final blended color is via
614 // their alpha. We can skip expensive per-vertex color conversion for this.
615 this->drawVertices(vertices, SkBlender::Mode(mode), paint, /*skipColorXform=*/true);
616 }
617 };
618
619 ShadowedPath shadowedPath(&path, &viewMatrix);
620
621 bool tiltZPlane = tilted(rec.fZPlaneParams);
624 !path.isConvex();
625 bool uncached = tiltZPlane || path.isVolatile();
626#endif
628
629 SkPoint3 zPlaneParams = rec.fZPlaneParams;
630 SkPoint3 devLightPos = rec.fLightPos;
631 if (!directional) {
632 viewMatrix.mapPoints((SkPoint*)&devLightPos.fX, 1);
633 }
634 float lightRadius = rec.fLightRadius;
635
636 if (SkColorGetA(rec.fAmbientColor) > 0) {
637 bool success = false;
638#if !defined(SK_ENABLE_OPTIMIZE_SIZE)
639 if (uncached && !useBlur) {
641 zPlaneParams,
642 transparent);
643 if (vertices) {
645 // Run the vertex color through a GaussianColorFilter and then modulate the
646 // grayscale result of that against our 'color' param.
647 paint.setColorFilter(
651 // The vertex colors for a tesselated shadow polygon are always either opaque black
652 // or transparent and their real contribution to the final blended color is via
653 // their alpha. We can skip expensive per-vertex color conversion for this.
654 this->drawVertices(vertices.get(),
656 paint,
657 /*skipColorXform=*/true);
658 success = true;
659 }
660 }
661
662 if (!success && !useBlur) {
663 AmbientVerticesFactory factory;
664 factory.fOccluderHeight = zPlaneParams.fZ;
665 factory.fTransparent = transparent;
666 if (viewMatrix.hasPerspective()) {
667 factory.fOffset.set(0, 0);
668 } else {
669 factory.fOffset.fX = viewMatrix.getTranslateX();
670 factory.fOffset.fY = viewMatrix.getTranslateY();
671 }
672
673 success = draw_shadow(factory, drawVertsProc, shadowedPath, rec.fAmbientColor);
674 }
675#endif // !defined(SK_ENABLE_OPTIMIZE_SIZE)
676
677 // All else has failed, draw with blur
678 if (!success) {
679 // Pretransform the path to avoid transforming the stroke, below.
680 SkPath devSpacePath;
681 path.transform(viewMatrix, &devSpacePath);
682 devSpacePath.setIsVolatile(true);
683
684 // The tesselator outsets by AmbientBlurRadius (or 'r') to get the outer ring of
685 // the tesselation, and sets the alpha on the path to 1/AmbientRecipAlpha (or 'a').
686 //
687 // We want to emulate this with a blur. The full blur width (2*blurRadius or 'f')
688 // can be calculated by interpolating:
689 //
690 // original edge outer edge
691 // | |<---------- r ------>|
692 // |<------|--- f -------------->|
693 // | | |
694 // alpha = 1 alpha = a alpha = 0
695 //
696 // Taking ratios, f/1 = r/a, so f = r/a and blurRadius = f/2.
697 //
698 // We now need to outset the path to place the new edge in the center of the
699 // blur region:
700 //
701 // original new
702 // | |<------|--- r ------>|
703 // |<------|--- f -|------------>|
704 // | |<- o ->|<--- f/2 --->|
705 //
706 // r = o + f/2, so o = r - f/2
707 //
708 // We outset by using the stroker, so the strokeWidth is o/2.
709 //
710 SkScalar devSpaceOutset = SkDrawShadowMetrics::AmbientBlurRadius(zPlaneParams.fZ);
711 SkScalar oneOverA = SkDrawShadowMetrics::AmbientRecipAlpha(zPlaneParams.fZ);
712 SkScalar blurRadius = 0.5f*devSpaceOutset*oneOverA;
713 SkScalar strokeWidth = 0.5f*(devSpaceOutset - blurRadius);
714
715 // Now draw with blur
717 paint.setColor(rec.fAmbientColor);
718 paint.setStrokeWidth(strokeWidth);
720 SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(blurRadius);
721 bool respectCTM = false;
722 paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, sigma, respectCTM));
723 this->drawPath(devSpacePath, paint);
724 }
725 }
726
727 if (SkColorGetA(rec.fSpotColor) > 0) {
728 bool success = false;
729#if !defined(SK_ENABLE_OPTIMIZE_SIZE)
730 if (uncached && !useBlur) {
732 zPlaneParams,
733 devLightPos, lightRadius,
734 transparent,
735 directional);
736 if (vertices) {
738 // Run the vertex color through a GaussianColorFilter and then modulate the
739 // grayscale result of that against our 'color' param.
740 paint.setColorFilter(
744 // The vertex colors for a tesselated shadow polygon are always either opaque black
745 // or transparent and their real contribution to the final blended color is via
746 // their alpha. We can skip expensive per-vertex color conversion for this.
747 this->drawVertices(vertices.get(),
749 paint,
750 /*skipColorXform=*/true);
751 success = true;
752 }
753 }
754
755 if (!success && !useBlur) {
756 SpotVerticesFactory factory;
757 factory.fOccluderHeight = zPlaneParams.fZ;
758 factory.fDevLightPos = devLightPos;
759 factory.fLightRadius = lightRadius;
760
761 SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY());
762 factory.fLocalCenter = center;
763 viewMatrix.mapPoints(&center, 1);
764 SkScalar radius, scale;
766 SkDrawShadowMetrics::GetDirectionalParams(zPlaneParams.fZ, devLightPos.fX,
767 devLightPos.fY, devLightPos.fZ,
768 lightRadius, &radius, &scale,
769 &factory.fOffset);
770 } else {
771 SkDrawShadowMetrics::GetSpotParams(zPlaneParams.fZ, devLightPos.fX - center.fX,
772 devLightPos.fY - center.fY, devLightPos.fZ,
773 lightRadius, &radius, &scale, &factory.fOffset);
774 }
775
776 SkRect devBounds;
777 viewMatrix.mapRect(&devBounds, path.getBounds());
778 if (transparent ||
779 SkTAbs(factory.fOffset.fX) > 0.5f*devBounds.width() ||
780 SkTAbs(factory.fOffset.fY) > 0.5f*devBounds.height()) {
781 // if the translation of the shadow is big enough we're going to end up
782 // filling the entire umbra, we can treat these as all the same
783 if (directional) {
784 factory.fOccluderType =
785 SpotVerticesFactory::OccluderType::kDirectionalTransparent;
786 } else {
787 factory.fOccluderType = SpotVerticesFactory::OccluderType::kPointTransparent;
788 }
789 } else if (directional) {
790 factory.fOccluderType = SpotVerticesFactory::OccluderType::kDirectional;
791 } else if (factory.fOffset.length()*scale + scale < radius) {
792 // if we don't translate more than the blur distance, can assume umbra is covered
793 factory.fOccluderType = SpotVerticesFactory::OccluderType::kPointOpaqueNoUmbra;
794 } else if (path.isConvex()) {
795 factory.fOccluderType = SpotVerticesFactory::OccluderType::kPointOpaquePartialUmbra;
796 } else {
797 factory.fOccluderType = SpotVerticesFactory::OccluderType::kPointTransparent;
798 }
799 // need to add this after we classify the shadow
800 factory.fOffset.fX += viewMatrix.getTranslateX();
801 factory.fOffset.fY += viewMatrix.getTranslateY();
802
804#ifdef DEBUG_SHADOW_CHECKS
805 switch (factory.fOccluderType) {
806 case SpotVerticesFactory::OccluderType::kPointTransparent:
807 color = 0xFFD2B48C; // tan for transparent
808 break;
809 case SpotVerticesFactory::OccluderType::kPointOpaquePartialUmbra:
810 color = 0xFFFFA500; // orange for opaque
811 break;
812 case SpotVerticesFactory::OccluderType::kPointOpaqueNoUmbra:
813 color = 0xFFE5E500; // corn yellow for covered
814 break;
815 case SpotVerticesFactory::OccluderType::kDirectional:
816 case SpotVerticesFactory::OccluderType::kDirectionalTransparent:
817 color = 0xFF550000; // dark red for directional
818 break;
819 }
820#endif
821 success = draw_shadow(factory, drawVertsProc, shadowedPath, color);
822 }
823#endif // !defined(SK_ENABLE_OPTIMIZE_SIZE)
824
825 // All else has failed, draw with blur
826 if (!success) {
827 SkMatrix shadowMatrix;
828 SkScalar radius;
829 if (!SkDrawShadowMetrics::GetSpotShadowTransform(devLightPos, lightRadius,
830 viewMatrix, zPlaneParams,
831 path.getBounds(), directional,
832 &shadowMatrix, &radius)) {
833 return;
834 }
835 SkAutoDeviceTransformRestore adr2(this, shadowMatrix);
836
838 paint.setColor(rec.fSpotColor);
840 bool respectCTM = false;
841 paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, sigma, respectCTM));
842 this->drawPath(path, paint);
843 }
844 }
845}
SkPath fPath
SkMatrix fViewMatrix
static const int strokeWidth
Definition: BlurTest.cpp:60
SkMatrix fMatrix
Definition: FillRRectOp.cpp:74
#define SK_ABORT(message,...)
Definition: SkAssert.h:70
SkBlendMode
Definition: SkBlendMode.h:38
@ kModulate
r = s*d
@ kNormal_SkBlurStyle
fuzzy inside and outside
Definition: SkBlurTypes.h:12
#define SkColorGetR(color)
Definition: SkColor.h:65
#define SkColorGetG(color)
Definition: SkColor.h:69
uint32_t SkColor
Definition: SkColor.h:37
static constexpr SkColor SkColorSetARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b)
Definition: SkColor.h:49
constexpr SkColor SK_ColorBLACK
Definition: SkColor.h:103
#define SkColorGetA(color)
Definition: SkColor.h:61
#define SkColorGetB(color)
Definition: SkColor.h:73
static bool SkIsFinite(T x, Pack... values)
static bool SkScalarNearlyZero(SkScalar x, SkScalar tolerance=SK_ScalarNearlyZero)
Definition: SkScalar.h:101
#define SK_Scalar1
Definition: SkScalar.h:18
#define SK_ScalarNaN
Definition: SkScalar.h:28
static bool tilted(const SkPoint3 &zPlaneParams)
static bool fill_shadow_rec(const SkPath &path, const SkPoint3 &zPlaneParams, const SkPoint3 &lightPos, SkScalar lightRadius, SkColor ambientColor, SkColor spotColor, uint32_t flags, const SkMatrix &ctm, SkDrawShadowRec *rec)
static bool validate_rec(const SkDrawShadowRec &rec)
@ kDirectionalLight_ShadowFlag
Definition: SkShadowUtils.h:31
@ kConcaveBlurOnly_ShadowFlag
Definition: SkShadowUtils.h:33
@ kTransparentOccluder_ShadowFlag
Definition: SkShadowUtils.h:27
static constexpr const T & SkTPin(const T &x, const T &lo, const T &hi)
Definition: SkTPin.h:19
static T SkTAbs(T value)
Definition: SkTemplates.h:43
static constexpr bool SkToBool(const T &x)
Definition: SkTo.h:35
static SkScalar center(float pos0, float pos1)
int find(T *array, int N, T item)
static sk_sp< SkBlender > Mode(SkBlendMode mode)
static SkScalar SK_SPI ConvertRadiusToSigma(SkScalar radius)
Definition: SkBlurMask.cpp:39
void private_draw_shadow_rec(const SkPath &, const SkDrawShadowRec &)
Definition: SkCanvas.cpp:1831
SkMatrix getTotalMatrix() const
Definition: SkCanvas.cpp:1629
static sk_sp< SkColorFilter > MakeGaussian()
sk_sp< SkColorFilter > makeComposed(sk_sp< SkColorFilter > inner) const
static sk_sp< SkColorFilter > Blend(const SkColor4f &c, sk_sp< SkColorSpace >, SkBlendMode mode)
virtual void drawVertices(const SkVertices *, sk_sp< SkBlender >, const SkPaint &, bool skipColorXform=false)=0
virtual void drawShadow(const SkPath &, const SkDrawShadowRec &)
const SkMatrix & localToDevice() const
Definition: SkDevice.h:179
virtual void drawPath(const SkPath &path, const SkPaint &paint, bool pathIsMutable=false)=0
static sk_sp< SkMaskFilter > MakeBlur(SkBlurStyle style, SkScalar sigma, bool respectCTM=true)
static constexpr int kMTransY
vertical translation
Definition: SkMatrix.h:358
static SkMatrix Translate(SkScalar dx, SkScalar dy)
Definition: SkMatrix.h:91
SkScalar getTranslateY() const
Definition: SkMatrix.h:452
void mapPoints(SkPoint dst[], const SkPoint src[], int count) const
Definition: SkMatrix.cpp:770
bool invert(SkMatrix *inverse) const
Definition: SkMatrix.h:1206
static const SkMatrix & I()
Definition: SkMatrix.cpp:1544
static constexpr int kMTransX
horizontal translation
Definition: SkMatrix.h:355
bool hasPerspective() const
Definition: SkMatrix.h:312
bool mapRect(SkRect *dst, const SkRect &src, SkApplyPerspectiveClip pc=SkApplyPerspectiveClip::kYes) const
Definition: SkMatrix.cpp:1141
SkScalar getTranslateX() const
Definition: SkMatrix.h:445
@ kStrokeAndFill_Style
sets to stroke and fill geometry
Definition: SkPaint.h:195
static void AddGenIDChangeListener(const SkPath &path, sk_sp< SkIDChangeListener > listener)
Definition: SkPathPriv.h:106
Definition: SkPath.h:59
SkPath & setIsVolatile(bool isVolatile)
Definition: SkPath.h:370
static void Add(Rec *, void *payload=nullptr)
static bool Find(const Key &key, FindVisitor, void *context)
static void DrawShadow(SkCanvas *canvas, const SkPath &path, const SkPoint3 &zPlaneParams, const SkPoint3 &lightPos, SkScalar lightRadius, SkColor ambientColor, SkColor spotColor, uint32_t flags=SkShadowFlags::kNone_ShadowFlag)
static bool GetLocalBounds(const SkMatrix &ctm, const SkPath &path, const SkPoint3 &zPlaneParams, const SkPoint3 &lightPos, SkScalar lightRadius, uint32_t flags, SkRect *bounds)
static void ComputeTonalColors(SkColor inAmbientColor, SkColor inSpotColor, SkColor *outAmbientColor, SkColor *outSpotColor)
int vertexCount() const
size_t approximateSize() const
Definition: SkVertices.cpp:221
SkVerticesPriv priv()
T * get() const
Definition: SkRefCnt.h:303
void reset(T *ptr=nullptr)
Definition: SkRefCnt.h:310
void reset(int count)
Definition: SkTemplates.h:195
const Paint & paint
Definition: color_source.cc:38
DlColor color
float SkScalar
Definition: extension.cpp:12
FlutterSemanticsFlag flags
if(end==-1)
Dart_NativeFunction function
Definition: fuchsia.cc:51
static float max(float r, float g, float b)
Definition: hsl.cpp:49
static float min(float r, float g, float b)
Definition: hsl.cpp:48
bool GetSpotShadowTransform(const SkPoint3 &lightPos, SkScalar lightRadius, const SkMatrix &ctm, const SkPoint3 &zPlaneParams, const SkRect &pathBounds, bool directional, SkMatrix *shadowTransform, SkScalar *radius)
void GetLocalBounds(const SkPath &path, const SkDrawShadowRec &rec, const SkMatrix &ctm, SkRect *bounds)
SkScalar AmbientRecipAlpha(SkScalar height)
void GetDirectionalParams(SkScalar occluderZ, SkScalar lightX, SkScalar lightY, SkScalar lightZ, SkScalar lightRadius, SkScalar *blurRadius, SkScalar *scale, SkVector *translate)
SkScalar AmbientBlurRadius(SkScalar height)
void GetSpotParams(SkScalar occluderZ, SkScalar lightX, SkScalar lightY, SkScalar lightZ, SkScalar lightRadius, SkScalar *blurRadius, SkScalar *scale, SkVector *translate)
unsigned useCenter Optional< SkMatrix > matrix
Definition: SkRecords.h:258
Optional< SkRect > bounds
Definition: SkRecords.h:189
SkRRect rrect
Definition: SkRecords.h:232
sk_sp< SkVertices > MakeAmbient(const SkPath &path, const SkMatrix &ctm, const SkPoint3 &zPlane, bool transparent)
sk_sp< SkVertices > MakeSpot(const SkPath &path, const SkMatrix &ctm, const SkPoint3 &zPlane, const SkPoint3 &lightPos, SkScalar lightRadius, bool transparent, bool directional)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition: switches.h:57
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive mode
Definition: switches.h:228
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
Definition: ref_ptr.h:256
void draw_shadow(SkCanvas *canvas, const SkPath &path, SkScalar height, SkColor color, SkPoint3 lightPos, SkScalar lightR, bool isAmbient, uint32_t flags)
Definition: shadowutils.cpp:28
const Scalar scale
SkPoint3 fZPlaneParams
SkScalar fX
Definition: SkPoint3.h:16
static SkPoint3 Make(SkScalar x, SkScalar y, SkScalar z)
Definition: SkPoint3.h:18
SkScalar fZ
Definition: SkPoint3.h:16
bool isFinite() const
Definition: SkPoint3.h:116
SkScalar fY
Definition: SkPoint3.h:16
float fX
x-axis value
Definition: SkPoint_impl.h:164
static constexpr SkPoint Make(float x, float y)
Definition: SkPoint_impl.h:173
void set(float x, float y)
Definition: SkPoint_impl.h:200
float fY
y-axis value
Definition: SkPoint_impl.h:165
constexpr float height() const
Definition: SkRect.h:769
constexpr float width() const
Definition: SkRect.h:762
size_t size() const