blob: 32f1c6f165f2c049b723ad4a61112be56f37e1f9 [file] [log] [blame]
Jim Van Verth43475ad2017-01-13 14:37:37 -05001/*
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"
Jim Van Verthefe3ded2017-01-30 13:11:45 -050010#include "SkColorFilter.h"
11#include "SkPath.h"
Brian Salomon5e689522017-02-01 12:07:17 -050012#include "SkResourceCache.h"
Jim Van Verthefe3ded2017-01-30 13:11:45 -050013#include "SkShadowTessellator.h"
Brian Salomon5e689522017-02-01 12:07:17 -050014#include "SkTLazy.h"
15#if SK_SUPPORT_GPU
16#include "GrShape.h"
17#include "effects/GrBlurredEdgeFragmentProcessor.h"
18#endif
Jim Van Verthefe3ded2017-01-30 13:11:45 -050019
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
Brian Salomon0bd699e2017-02-01 12:23:25 -050057 SkScalar a = factor * SkGetPackedG32(c);
58 dst[i] = SkPackARGB32(a, a, a, a);
Jim Van Verthefe3ded2017-01-30 13:11:45 -050059 }
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
Jim Van Verthefe3ded2017-01-30 13:11:45 -050073
74sk_sp<GrFragmentProcessor> SkGaussianColorFilter::asFragmentProcessor(GrContext*,
75 SkColorSpace*) const {
76 return GrBlurredEdgeFP::Make(GrBlurredEdgeFP::kGaussian_Mode);
77}
78#endif
79
80///////////////////////////////////////////////////////////////////////////////////////////////////
Brian Salomon5e689522017-02-01 12:07:17 -050081
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;
Brian Salomon0bd699e2017-02-01 12:23:25 -0500274 // 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()));
Brian Salomon5e689522017-02-01 12:07:17 -0500279 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
Jim Van Verthefe3ded2017-01-30 13:11:45 -0500296static const float kHeightFactor = 1.0f / 128.0f;
297static const float kGeomFactor = 64.0f;
Jim Van Verth43475ad2017-01-13 14:37:37 -0500298
299// Draw an offset spot shadow and outlining ambient shadow for the given path.
300void SkShadowUtils::DrawShadow(SkCanvas* canvas, const SkPath& path, SkScalar occluderHeight,
Brian Salomon0bd699e2017-02-01 12:23:25 -0500301 const SkPoint3& devLightPos, SkScalar lightRadius,
Jim Van Verth43475ad2017-01-13 14:37:37 -0500302 SkScalar ambientAlpha, SkScalar spotAlpha, SkColor color,
303 uint32_t flags) {
Brian Salomon0bd699e2017-02-01 12:23:25 -0500304 SkAutoCanvasRestore acr(canvas, true);
Brian Salomon5e689522017-02-01 12:07:17 -0500305 SkMatrix viewMatrix = canvas->getTotalMatrix();
Jim Van Verthefe3ded2017-01-30 13:11:45 -0500306 canvas->resetMatrix();
307
Brian Salomon5e689522017-02-01 12:07:17 -0500308 ShadowedPath shadowedPath(&path, &viewMatrix);
309
Brian Salomon958fbc42017-01-30 17:01:28 -0500310 bool transparent = SkToBool(flags & SkShadowFlags::kTransparentOccluder_ShadowFlag);
311
Jim Van Verthefe3ded2017-01-30 13:11:45 -0500312 if (ambientAlpha > 0) {
Brian Salomon0bd699e2017-02-01 12:23:25 -0500313 ambientAlpha = SkTMin(ambientAlpha, 1.f);
Brian Salomon5e689522017-02-01 12:07:17 -0500314 AmbientVerticesFactory factory;
315 factory.fRadius = occluderHeight * kHeightFactor * kGeomFactor;
Jim Van Verthefe3ded2017-01-30 13:11:45 -0500316 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.
Brian Salomon5e689522017-02-01 12:07:17 -0500321 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;
Jim Van Verthefe3ded2017-01-30 13:11:45 -0500325
Brian Salomon5e689522017-02-01 12:07:17 -0500326 draw_shadow(factory, canvas, shadowedPath, color);
Jim Van Verthefe3ded2017-01-30 13:11:45 -0500327 }
328
329 if (spotAlpha > 0) {
Brian Salomon0bd699e2017-02-01 12:23:25 -0500330 spotAlpha = SkTMin(spotAlpha, 1.f);
Brian Salomon5e689522017-02-01 12:07:17 -0500331 SpotVerticesFactory factory;
Brian Salomon0bd699e2017-02-01 12:23:25 -0500332 float zRatio = SkTPin(occluderHeight / (devLightPos.fZ - occluderHeight), 0.0f, 0.95f);
Brian Salomon5e689522017-02-01 12:07:17 -0500333 factory.fRadius = lightRadius * zRatio;
Jim Van Verthefe3ded2017-01-30 13:11:45 -0500334
335 // Compute the scale and translation for the spot shadow.
Brian Salomon0bd699e2017-02-01 12:23:25 -0500336 factory.fScale = devLightPos.fZ / (devLightPos.fZ - occluderHeight);
Jim Van Verthefe3ded2017-01-30 13:11:45 -0500337
338 SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY());
Brian Salomon0bd699e2017-02-01 12:23:25 -0500339 viewMatrix.mapPoints(&center, 1);
340 factory.fOffset = SkVector::Make(zRatio * (center.fX - devLightPos.fX),
341 zRatio * (center.fY - devLightPos.fY));
Brian Salomon5e689522017-02-01 12:07:17 -0500342 factory.fUmbraColor = SkColorSetARGB(255, 0, spotAlpha * 255.9999f, 255);
343 factory.fPenumbraColor = SkColorSetARGB(255, 0, spotAlpha * 255.9999f, 0);
344 factory.fTransparent = transparent;
Jim Van Verthefe3ded2017-01-30 13:11:45 -0500345
Brian Salomon5e689522017-02-01 12:07:17 -0500346 draw_shadow(factory, canvas, shadowedPath, color);
Jim Van Verthefe3ded2017-01-30 13:11:45 -0500347 }
Jim Van Verth43475ad2017-01-13 14:37:37 -0500348}