SkShadowUtils.cpp revision 0bd699e497819344083df4715928a54a597cd630
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 "SkPath.h"
12#include "SkResourceCache.h"
13#include "SkShadowTessellator.h"
14#include "SkTLazy.h"
15#if SK_SUPPORT_GPU
16#include "GrShape.h"
17#include "effects/GrBlurredEdgeFragmentProcessor.h"
18#endif
19
20/**
21*  Gaussian color filter -- produces a Gaussian ramp based on the color's B value,
22*                           then blends with the color's G value.
23*                           Final result is black with alpha of Gaussian(B)*G.
24*                           The assumption is that the original color's alpha is 1.
25*/
26class SK_API SkGaussianColorFilter : public SkColorFilter {
27public:
28    static sk_sp<SkColorFilter> Make() {
29        return sk_sp<SkColorFilter>(new SkGaussianColorFilter);
30    }
31
32    void filterSpan(const SkPMColor src[], int count, SkPMColor dst[]) const override;
33
34#if SK_SUPPORT_GPU
35    sk_sp<GrFragmentProcessor> asFragmentProcessor(GrContext*, SkColorSpace*) const override;
36#endif
37
38    SK_TO_STRING_OVERRIDE()
39    SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkGaussianColorFilter)
40
41protected:
42    void flatten(SkWriteBuffer&) const override {}
43
44private:
45    SkGaussianColorFilter() : INHERITED() {}
46
47    typedef SkColorFilter INHERITED;
48};
49
50void SkGaussianColorFilter::filterSpan(const SkPMColor src[], int count, SkPMColor dst[]) const {
51    for (int i = 0; i < count; ++i) {
52        SkPMColor c = src[i];
53
54        SkScalar factor = SK_Scalar1 - SkGetPackedB32(c) / 255.f;
55        factor = SkScalarExp(-factor * factor * 4) - 0.018f;
56
57        SkScalar a = factor * SkGetPackedG32(c);
58        dst[i] = SkPackARGB32(a, a, a, a);
59    }
60}
61
62sk_sp<SkFlattenable> SkGaussianColorFilter::CreateProc(SkReadBuffer&) {
63    return Make();
64}
65
66#ifndef SK_IGNORE_TO_STRING
67void SkGaussianColorFilter::toString(SkString* str) const {
68    str->append("SkGaussianColorFilter ");
69}
70#endif
71
72#if SK_SUPPORT_GPU
73
74sk_sp<GrFragmentProcessor> SkGaussianColorFilter::asFragmentProcessor(GrContext*,
75                                                                      SkColorSpace*) const {
76    return GrBlurredEdgeFP::Make(GrBlurredEdgeFP::kGaussian_Mode);
77}
78#endif
79
80///////////////////////////////////////////////////////////////////////////////////////////////////
81
82namespace {
83
84struct AmbientVerticesFactory {
85    SkScalar fRadius;
86    SkColor fUmbraColor;
87    SkColor fPenumbraColor;
88    bool fTransparent;
89
90    bool operator==(const AmbientVerticesFactory& that) const {
91        return fRadius == that.fRadius && fUmbraColor == that.fUmbraColor &&
92               fPenumbraColor == that.fPenumbraColor && fTransparent == that.fTransparent;
93    }
94    bool operator!=(const AmbientVerticesFactory& that) const { return !(*this == that); }
95
96    sk_sp<SkShadowVertices> makeVertices(const SkPath& devPath) const {
97        return SkShadowVertices::MakeAmbient(devPath, fRadius, fUmbraColor, fPenumbraColor,
98                                             fTransparent);
99    }
100};
101
102struct SpotVerticesFactory {
103    SkScalar fRadius;
104    SkColor fUmbraColor;
105    SkColor fPenumbraColor;
106    SkScalar fScale;
107    SkVector fOffset;
108    bool fTransparent;
109
110    bool operator==(const SpotVerticesFactory& that) const {
111        return fRadius == that.fRadius && fUmbraColor == that.fUmbraColor &&
112               fPenumbraColor == that.fPenumbraColor && fTransparent == that.fTransparent &&
113               fScale == that.fScale && fOffset == that.fOffset;
114    }
115    bool operator!=(const SpotVerticesFactory& that) const { return !(*this == that); }
116
117    sk_sp<SkShadowVertices> makeVertices(const SkPath& devPath) const {
118        return SkShadowVertices::MakeSpot(devPath, fScale, fOffset, fRadius, fUmbraColor,
119                                          fPenumbraColor, fTransparent);
120    }
121};
122
123/**
124 * A record of shadow vertices stored in SkResourceCache. Each shape may have one record for a given
125 * FACTORY type.
126 */
127template <typename FACTORY>
128class TessPathRec : public SkResourceCache::Rec {
129public:
130    TessPathRec(const SkResourceCache::Key& key, const SkMatrix& viewMatrix, const FACTORY& factory,
131                sk_sp<SkShadowVertices> vertices)
132            : fVertices(std::move(vertices)), fFactory(factory), fOriginalMatrix(viewMatrix) {
133        fKey.reset(new uint8_t[key.size()]);
134        memcpy(fKey.get(), &key, key.size());
135    }
136
137    const Key& getKey() const override {
138        return *reinterpret_cast<SkResourceCache::Key*>(fKey.get());
139    }
140    size_t bytesUsed() const override { return fVertices->size(); }
141
142    const char* getCategory() const override { return "tessellated shadow mask"; }
143
144    sk_sp<SkShadowVertices> refVertices() const { return fVertices; }
145
146    const FACTORY& factory() const { return fFactory; }
147
148    const SkMatrix& originalViewMatrix() const { return fOriginalMatrix; }
149
150private:
151    std::unique_ptr<uint8_t[]> fKey;
152    sk_sp<SkShadowVertices> fVertices;
153    FACTORY fFactory;
154    SkMatrix fOriginalMatrix;
155};
156
157/**
158 * Used by FindVisitor to determine whether a cache entry can be reused and if so returns the
159 * vertices and translation vector.
160 */
161template <typename FACTORY>
162struct FindContext {
163    FindContext(const SkMatrix* viewMatrix, const FACTORY* factory)
164            : fViewMatrix(viewMatrix), fFactory(factory) {}
165    const SkMatrix* fViewMatrix;
166    SkVector fTranslate = {0, 0};
167    sk_sp<SkShadowVertices> fVertices;
168    const FACTORY* fFactory;
169};
170
171/**
172 * Function called by SkResourceCache when a matching cache key is found. The FACTORY and matrix of
173 * the FindContext are used to determine if the vertices are reusable. If so the vertices and
174 * necessary translation vector are set on the FindContext.
175 */
176template <typename FACTORY>
177bool FindVisitor(const SkResourceCache::Rec& baseRec, void* ctx) {
178    FindContext<FACTORY>* findContext = (FindContext<FACTORY>*)ctx;
179    const TessPathRec<FACTORY>& rec = static_cast<const TessPathRec<FACTORY>&>(baseRec);
180
181    const SkMatrix& viewMatrix = *findContext->fViewMatrix;
182    const SkMatrix& recMatrix = rec.originalViewMatrix();
183    if (findContext->fViewMatrix->hasPerspective() || recMatrix.hasPerspective()) {
184        if (recMatrix != viewMatrix) {
185            return false;
186        }
187    } else if (recMatrix.getScaleX() != viewMatrix.getScaleX() ||
188               recMatrix.getSkewX() != viewMatrix.getSkewX() ||
189               recMatrix.getScaleY() != viewMatrix.getScaleY() ||
190               recMatrix.getSkewY() != viewMatrix.getSkewY()) {
191        return false;
192    }
193    if (*findContext->fFactory != rec.factory()) {
194        return false;
195    }
196    findContext->fTranslate.fX = viewMatrix.getTranslateX() - recMatrix.getTranslateX();
197    findContext->fTranslate.fY = viewMatrix.getTranslateY() - recMatrix.getTranslateY();
198    findContext->fVertices = rec.refVertices();
199    return true;
200}
201
202class ShadowedPath {
203public:
204    ShadowedPath(const SkPath* path, const SkMatrix* viewMatrix)
205            : fOriginalPath(path)
206            , fViewMatrix(viewMatrix)
207#if SK_SUPPORT_GPU
208            , fShapeForKey(*path, GrStyle::SimpleFill())
209#endif
210    {}
211
212    const SkPath& transformedPath() {
213        if (!fTransformedPath.isValid()) {
214            fOriginalPath->transform(*fViewMatrix, fTransformedPath.init());
215        }
216        return *fTransformedPath.get();
217    }
218
219    const SkMatrix& viewMatrix() const { return *fViewMatrix; }
220#if SK_SUPPORT_GPU
221    /** Negative means the vertices should not be cached for this path. */
222    int keyBytes() const { return fShapeForKey.unstyledKeySize() * sizeof(uint32_t); }
223    void writeKey(void* key) const {
224        fShapeForKey.writeUnstyledKey(reinterpret_cast<uint32_t*>(key));
225    }
226#else
227    int keyBytes() const { return -1; }
228    void writeKey(void* key) const { SkFAIL("Should never be called"); }
229#endif
230
231private:
232    const SkPath* fOriginalPath;
233    const SkMatrix* fViewMatrix;
234#if SK_SUPPORT_GPU
235    GrShape fShapeForKey;
236#endif
237    SkTLazy<SkPath> fTransformedPath;
238};
239
240/**
241 * Draws a shadow to 'canvas'. The vertices used to draw the shadow are created by 'factory' unless
242 * they are first found in SkResourceCache.
243 */
244template <typename FACTORY>
245void draw_shadow(const FACTORY& factory, SkCanvas* canvas, ShadowedPath& path, SkColor color) {
246    FindContext<FACTORY> context(&path.viewMatrix(), &factory);
247    static void* kNamespace;
248
249    SkResourceCache::Key* key = nullptr;
250    SkAutoSTArray<32 * 4, uint8_t> keyStorage;
251    int keyDataBytes = path.keyBytes();
252    if (keyDataBytes >= 0) {
253        keyStorage.reset(keyDataBytes + sizeof(SkResourceCache::Key));
254        key = new (keyStorage.begin()) SkResourceCache::Key();
255        path.writeKey((uint32_t*)(keyStorage.begin() + sizeof(*key)));
256        key->init(&kNamespace, 0, keyDataBytes);
257        SkResourceCache::Find(*key, FindVisitor<FACTORY>, &context);
258    }
259
260    sk_sp<SkShadowVertices> vertices;
261    const SkVector* translate;
262    static constexpr SkVector kZeroTranslate = {0, 0};
263    bool foundInCache = SkToBool(context.fVertices);
264    if (foundInCache) {
265        vertices = std::move(context.fVertices);
266        translate = &context.fTranslate;
267    } else {
268        // TODO: handle transforming the path as part of the tessellator
269        vertices = factory.makeVertices(path.transformedPath());
270        translate = &kZeroTranslate;
271    }
272
273    SkPaint paint;
274    // Run the vertex color through a GaussianColorFilter and then modulate the grayscale result of
275    // that against our 'color' param.
276    paint.setColorFilter(SkColorFilter::MakeComposeFilter(
277            SkColorFilter::MakeModeFilter(color, SkBlendMode::kModulate),
278            SkGaussianColorFilter::Make()));
279    if (translate->fX || translate->fY) {
280        canvas->save();
281        canvas->translate(translate->fX, translate->fY);
282    }
283    canvas->drawVertices(SkCanvas::kTriangles_VertexMode, vertices->vertexCount(),
284                         vertices->positions(), nullptr, vertices->colors(), vertices->indices(),
285                         vertices->indexCount(), paint);
286    if (translate->fX || translate->fY) {
287        canvas->restore();
288    }
289    if (!foundInCache && key) {
290        SkResourceCache::Add(
291                new TessPathRec<FACTORY>(*key, path.viewMatrix(), factory, std::move(vertices)));
292    }
293}
294}
295
296static const float kHeightFactor = 1.0f / 128.0f;
297static const float kGeomFactor = 64.0f;
298
299// Draw an offset spot shadow and outlining ambient shadow for the given path.
300void SkShadowUtils::DrawShadow(SkCanvas* canvas, const SkPath& path, SkScalar occluderHeight,
301                               const SkPoint3& devLightPos, SkScalar lightRadius,
302                               SkScalar ambientAlpha, SkScalar spotAlpha, SkColor color,
303                               uint32_t flags) {
304    SkAutoCanvasRestore acr(canvas, true);
305    SkMatrix viewMatrix = canvas->getTotalMatrix();
306    canvas->resetMatrix();
307
308    ShadowedPath shadowedPath(&path, &viewMatrix);
309
310    bool transparent = SkToBool(flags & SkShadowFlags::kTransparentOccluder_ShadowFlag);
311
312    if (ambientAlpha > 0) {
313        ambientAlpha = SkTMin(ambientAlpha, 1.f);
314        AmbientVerticesFactory factory;
315        factory.fRadius = occluderHeight * kHeightFactor * kGeomFactor;
316        SkScalar umbraAlpha = SkScalarInvert((1.0f + SkTMax(occluderHeight*kHeightFactor, 0.0f)));
317        // umbraColor is the interior value, penumbraColor the exterior value.
318        // umbraAlpha is the factor that is linearly interpolated from outside to inside, and
319        // then "blurred" by the GrBlurredEdgeFP. It is then multiplied by fAmbientAlpha to get
320        // the final alpha.
321        factory.fUmbraColor =
322                SkColorSetARGB(255, 0, ambientAlpha * 255.9999f, umbraAlpha * 255.9999f);
323        factory.fPenumbraColor = SkColorSetARGB(255, 0, ambientAlpha * 255.9999f, 0);
324        factory.fTransparent = transparent;
325
326        draw_shadow(factory, canvas, shadowedPath, color);
327    }
328
329    if (spotAlpha > 0) {
330        spotAlpha = SkTMin(spotAlpha, 1.f);
331        SpotVerticesFactory factory;
332        float zRatio = SkTPin(occluderHeight / (devLightPos.fZ - occluderHeight), 0.0f, 0.95f);
333        factory.fRadius = lightRadius * zRatio;
334
335        // Compute the scale and translation for the spot shadow.
336        factory.fScale = devLightPos.fZ / (devLightPos.fZ - occluderHeight);
337
338        SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY());
339        viewMatrix.mapPoints(&center, 1);
340        factory.fOffset = SkVector::Make(zRatio * (center.fX - devLightPos.fX),
341                                         zRatio * (center.fY - devLightPos.fY));
342        factory.fUmbraColor = SkColorSetARGB(255, 0, spotAlpha * 255.9999f, 255);
343        factory.fPenumbraColor = SkColorSetARGB(255, 0, spotAlpha * 255.9999f, 0);
344        factory.fTransparent = transparent;
345
346        draw_shadow(factory, canvas, shadowedPath, color);
347    }
348}
349