SkShadowUtils.cpp revision 0dda9cb881900241c1c2193ddf3bede72cda898b
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 if (!vertices) { 271 return; 272 } 273 translate = &kZeroTranslate; 274 } 275 276 SkPaint paint; 277 // Run the vertex color through a GaussianColorFilter and then modulate the grayscale result of 278 // that against our 'color' param. 279 paint.setColorFilter(SkColorFilter::MakeComposeFilter( 280 SkColorFilter::MakeModeFilter(color, SkBlendMode::kModulate), 281 SkGaussianColorFilter::Make())); 282 if (translate->fX || translate->fY) { 283 canvas->save(); 284 canvas->translate(translate->fX, translate->fY); 285 } 286 canvas->drawVertices(SkCanvas::kTriangles_VertexMode, vertices->vertexCount(), 287 vertices->positions(), nullptr, vertices->colors(), vertices->indices(), 288 vertices->indexCount(), paint); 289 if (translate->fX || translate->fY) { 290 canvas->restore(); 291 } 292 if (!foundInCache && key) { 293 SkResourceCache::Add( 294 new TessPathRec<FACTORY>(*key, path.viewMatrix(), factory, std::move(vertices))); 295 } 296} 297} 298 299static const float kHeightFactor = 1.0f / 128.0f; 300static const float kGeomFactor = 64.0f; 301 302// Draw an offset spot shadow and outlining ambient shadow for the given path. 303void SkShadowUtils::DrawShadow(SkCanvas* canvas, const SkPath& path, SkScalar occluderHeight, 304 const SkPoint3& devLightPos, SkScalar lightRadius, 305 SkScalar ambientAlpha, SkScalar spotAlpha, SkColor color, 306 uint32_t flags) { 307 SkAutoCanvasRestore acr(canvas, true); 308 SkMatrix viewMatrix = canvas->getTotalMatrix(); 309 canvas->resetMatrix(); 310 311 ShadowedPath shadowedPath(&path, &viewMatrix); 312 313 bool transparent = SkToBool(flags & SkShadowFlags::kTransparentOccluder_ShadowFlag); 314 315 if (ambientAlpha > 0) { 316 ambientAlpha = SkTMin(ambientAlpha, 1.f); 317 AmbientVerticesFactory factory; 318 factory.fRadius = occluderHeight * kHeightFactor * kGeomFactor; 319 SkScalar umbraAlpha = SkScalarInvert((1.0f + SkTMax(occluderHeight*kHeightFactor, 0.0f))); 320 // umbraColor is the interior value, penumbraColor the exterior value. 321 // umbraAlpha is the factor that is linearly interpolated from outside to inside, and 322 // then "blurred" by the GrBlurredEdgeFP. It is then multiplied by fAmbientAlpha to get 323 // the final alpha. 324 factory.fUmbraColor = 325 SkColorSetARGB(255, 0, ambientAlpha * 255.9999f, umbraAlpha * 255.9999f); 326 factory.fPenumbraColor = SkColorSetARGB(255, 0, ambientAlpha * 255.9999f, 0); 327 factory.fTransparent = transparent; 328 329 draw_shadow(factory, canvas, shadowedPath, color); 330 } 331 332 if (spotAlpha > 0) { 333 spotAlpha = SkTMin(spotAlpha, 1.f); 334 SpotVerticesFactory factory; 335 float zRatio = SkTPin(occluderHeight / (devLightPos.fZ - occluderHeight), 0.0f, 0.95f); 336 factory.fRadius = lightRadius * zRatio; 337 338 // Compute the scale and translation for the spot shadow. 339 factory.fScale = devLightPos.fZ / (devLightPos.fZ - occluderHeight); 340 341 SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY()); 342 viewMatrix.mapPoints(¢er, 1); 343 factory.fOffset = SkVector::Make(zRatio * (center.fX - devLightPos.fX), 344 zRatio * (center.fY - devLightPos.fY)); 345 factory.fUmbraColor = SkColorSetARGB(255, 0, spotAlpha * 255.9999f, 255); 346 factory.fPenumbraColor = SkColorSetARGB(255, 0, spotAlpha * 255.9999f, 0); 347 factory.fTransparent = transparent; 348 349 draw_shadow(factory, canvas, shadowedPath, color); 350 } 351} 352