SampleAndroidShadows.cpp revision fdb1bdf1aa4e7f14befcb3acc3f586e7734190ea
1 2/* 3 * Copyright 2016 Google Inc. 4 * 5 * Use of this source code is governed by a BSD-style license that can be 6 * found in the LICENSE file. 7 */ 8#include "SampleCode.h" 9#include "SkBlurMask.h" 10#include "SkBlurMaskFilter.h" 11#include "SkCanvas.h" 12#include "SkGaussianEdgeShader.h" 13#include "SkPath.h" 14#include "SkPoint3.h" 15#include "SkShadowUtils.h" 16#include "SkUtils.h" 17#include "SkView.h" 18#include "sk_tool_utils.h" 19 20#define USE_SHADOW_UTILS 21 22//////////////////////////////////////////////////////////////////////////// 23 24class ShadowsView : public SampleView { 25 SkPath fRectPath; 26 SkPath fRRPath; 27 SkPath fCirclePath; 28 SkPath fFunkyRRPath; 29 SkPath fCubicPath; 30 SkPoint3 fLightPos; 31 32 bool fShowAmbient; 33 bool fShowSpot; 34 bool fUseAlt; 35 bool fShowObject; 36 bool fIgnoreShadowAlpha; 37 38public: 39 ShadowsView() 40 : fShowAmbient(true) 41 , fShowSpot(true) 42 , fUseAlt(true) 43 , fShowObject(true) 44 , fIgnoreShadowAlpha(false) {} 45 46protected: 47 void onOnceBeforeDraw() override { 48 fCirclePath.addCircle(0, 0, 50); 49 fRectPath.addRect(SkRect::MakeXYWH(-100, -50, 200, 100)); 50 fRRPath.addRRect(SkRRect::MakeRectXY(SkRect::MakeXYWH(-100, -50, 200, 100), 4, 4)); 51 fFunkyRRPath.addRoundRect(SkRect::MakeXYWH(-50, -50, SK_Scalar1 * 100, SK_Scalar1 * 100), 52 40 * SK_Scalar1, 20 * SK_Scalar1, 53 SkPath::kCW_Direction); 54 fCubicPath.cubicTo(100 * SK_Scalar1, 50 * SK_Scalar1, 55 20 * SK_Scalar1, 100 * SK_Scalar1, 56 0 * SK_Scalar1, 0 * SK_Scalar1); 57 58 fLightPos = SkPoint3::Make(-700, -700, 2800); 59 } 60 61 // overrides from SkEventSink 62 bool onQuery(SkEvent* evt) override { 63 if (SampleCode::TitleQ(*evt)) { 64 SampleCode::TitleR(evt, "AndroidShadows"); 65 return true; 66 } 67 68 SkUnichar uni; 69 if (SampleCode::CharQ(*evt, &uni)) { 70 switch (uni) { 71 case 'B': 72 fShowAmbient = !fShowAmbient; 73 break; 74 case 'S': 75 fShowSpot = !fShowSpot; 76 break; 77 case 'T': 78 fUseAlt = !fUseAlt; 79 break; 80 case 'O': 81 fShowObject = !fShowObject; 82 break; 83 case '>': 84 fLightPos.fZ += 10; 85 break; 86 case '<': 87 fLightPos.fZ -= 10; 88 break; 89 case '?': 90 fIgnoreShadowAlpha = !fIgnoreShadowAlpha; 91 break; 92 default: 93 break; 94 } 95 this->inval(nullptr); 96 } 97 return this->INHERITED::onQuery(evt); 98 } 99 100 void drawBG(SkCanvas* canvas) { 101 canvas->drawColor(0xFFDDDDDD); 102 } 103 104 static void GetOcclRect(const SkPath& path, SkRect* occlRect) { 105 SkRect pathRect; 106 SkRRect pathRRect; 107 if (path.isOval(&pathRect)) { 108 *occlRect = sk_tool_utils::compute_central_occluder(SkRRect::MakeOval(pathRect)); 109 } else if (path.isRRect(&pathRRect)) { 110 *occlRect = sk_tool_utils::compute_central_occluder(pathRRect); 111 } else if (path.isRect(occlRect)) { 112 // the inverse transform for the spot shadow occluder doesn't always get us 113 // back to exactly the same position, so deducting a little slop 114 occlRect->inset(1, 1); 115 } else { 116 *occlRect = SkRect::MakeEmpty(); 117 } 118 } 119 120 void drawAmbientShadow(SkCanvas* canvas, const SkPath& path, SkScalar zValue, 121 SkScalar ambientAlpha) { 122 123 if (ambientAlpha <= 0) { 124 return; 125 } 126 127 const SkScalar kHeightFactor = 1.f / 128.f; 128 const SkScalar kGeomFactor = 64; 129 130 SkScalar umbraAlpha = 1 / (1 + SkMaxScalar(zValue*kHeightFactor, 0)); 131 SkScalar radius = zValue*kHeightFactor*kGeomFactor; 132 133 // occlude blur 134 SkRect occlRect; 135 GetOcclRect(path, &occlRect); 136 sk_sp<SkMaskFilter> mf = SkBlurMaskFilter::Make(kNormal_SkBlurStyle, 137 SkBlurMask::ConvertRadiusToSigma(radius), 138 occlRect, 139 SkBlurMaskFilter::kNone_BlurFlag); 140 141 SkPaint paint; 142 paint.setAntiAlias(true); 143 paint.setMaskFilter(std::move(mf)); 144 paint.setColor(SkColorSetARGB(fIgnoreShadowAlpha 145 ? 255 146 : (unsigned char)(ambientAlpha*umbraAlpha*255.999f), 0, 0, 0)); 147 canvas->drawPath(path, paint); 148 149 // draw occlusion rect 150#if DRAW_OCCL_RECT 151 SkPaint stroke; 152 stroke.setStyle(SkPaint::kStroke_Style); 153 stroke.setColor(SK_ColorBLUE); 154 canvas->drawRect(occlRect, stroke); 155#endif 156 } 157 158 void drawAmbientShadowAlt(SkCanvas* canvas, const SkPath& path, SkScalar zValue, 159 SkScalar ambientAlpha) { 160 161 if (ambientAlpha <= 0) { 162 return; 163 } 164 165 const SkScalar kHeightFactor = 1.f / 128.f; 166 const SkScalar kGeomFactor = 64; 167 168 SkScalar umbraAlpha = 1 / (1 + SkMaxScalar(zValue*kHeightFactor, 0)); 169 SkScalar radius = zValue*kHeightFactor*kGeomFactor; 170 // distance to outer of edge of geometry from original shape edge 171 SkScalar offset = radius*umbraAlpha; 172 173 SkRect pathRect; 174 SkRRect pathRRect; 175 SkScalar scaleFactors[2]; 176 if (!canvas->getTotalMatrix().getMinMaxScales(scaleFactors)) { 177 return; 178 } 179 if (scaleFactors[0] != scaleFactors[1] || radius*scaleFactors[0] >= 64 || 180 !((path.isOval(&pathRect) && pathRect.width() == pathRect.height()) || 181 (path.isRRect(&pathRRect) && pathRRect.allCornersCircular()) || 182 path.isRect(&pathRect))) { 183 this->drawAmbientShadow(canvas, path, zValue, ambientAlpha); 184 return; 185 } 186 187 // For all of these, we inset the offset rect by half the radius to get our stroke shape. 188 SkScalar strokeOutset = offset - SK_ScalarHalf*radius; 189 // Make sure we'll have a radius of at least 0.5 after xform 190 if (strokeOutset*scaleFactors[0] < 0.5f) { 191 strokeOutset = 0.5f / scaleFactors[0]; 192 } 193 if (path.isOval(nullptr)) { 194 pathRect.outset(strokeOutset, strokeOutset); 195 pathRRect = SkRRect::MakeOval(pathRect); 196 } else if (path.isRect(nullptr)) { 197 pathRect.outset(strokeOutset, strokeOutset); 198 pathRRect = SkRRect::MakeRectXY(pathRect, strokeOutset, strokeOutset); 199 } else { 200 pathRRect.outset(strokeOutset, strokeOutset); 201 } 202 203 SkPaint paint; 204 paint.setAntiAlias(true); 205 paint.setStyle(SkPaint::kStroke_Style); 206 // we outset the stroke a little to cover up AA on the interior edge 207 SkScalar pad = 0.5f; 208 paint.setStrokeWidth(radius + 2*pad); 209 // handle scale of radius and pad due to CTM 210 radius *= scaleFactors[0]; 211 pad *= scaleFactors[0]; 212 SkASSERT(radius < 16384); 213 SkASSERT(pad < 64); 214 // Convert radius to 14.2 fixed point and place in the R & G components. 215 // Convert pad to 6.2 fixed point and place in the B component. 216 uint16_t iRadius = (uint16_t)(radius*4.0f); 217 unsigned char alpha = (unsigned char)(ambientAlpha*255.999f); 218 paint.setColor(SkColorSetARGB(fIgnoreShadowAlpha ? 255 : alpha, 219 iRadius >> 8, iRadius & 0xff, 220 (unsigned char)(4.0f*pad))); 221 222 paint.setShader(SkGaussianEdgeShader::Make()); 223 canvas->drawRRect(pathRRect, paint); 224 } 225 226 void drawSpotShadow(SkCanvas* canvas, const SkPath& path, SkScalar zValue, 227 SkPoint3 lightPos, SkScalar lightWidth, SkScalar spotAlpha) { 228 if (spotAlpha <= 0) { 229 return; 230 } 231 232 SkScalar zRatio = zValue / (lightPos.fZ - zValue); 233 if (zRatio < 0.0f) { 234 zRatio = 0.0f; 235 } else if (zRatio > 0.95f) { 236 zRatio = 0.95f; 237 } 238 SkScalar blurRadius = lightWidth*zRatio; 239 240 // compute the transformation params 241 SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY()); 242 SkMatrix ctmInverse; 243 if (!canvas->getTotalMatrix().invert(&ctmInverse)) { 244 return; 245 } 246 SkPoint lightPos2D = SkPoint::Make(lightPos.fX, lightPos.fY); 247 ctmInverse.mapPoints(&lightPos2D, 1); 248 SkPoint offset = SkPoint::Make(zRatio*(center.fX - lightPos2D.fX), 249 zRatio*(center.fY - lightPos2D.fY)); 250 SkScalar scale = lightPos.fZ / (lightPos.fZ - zValue); 251 252 SkAutoCanvasRestore acr(canvas, true); 253 254 sk_sp<SkMaskFilter> mf = SkBlurMaskFilter::Make(kNormal_SkBlurStyle, 255 SkBlurMask::ConvertRadiusToSigma(blurRadius), 256 SkBlurMaskFilter::kNone_BlurFlag); 257 258 SkPaint paint; 259 paint.setAntiAlias(true); 260 paint.setMaskFilter(std::move(mf)); 261 paint.setColor(SkColorSetARGB(fIgnoreShadowAlpha 262 ? 255 263 : (unsigned char)(spotAlpha*255.999f), 0, 0, 0)); 264 265 // apply transformation to shadow 266 canvas->scale(scale, scale); 267 canvas->translate(offset.fX, offset.fY); 268 canvas->drawPath(path, paint); 269 } 270 271 void drawSpotShadowAlt(SkCanvas* canvas, const SkPath& path, SkScalar zValue, 272 SkPoint3 lightPos, SkScalar lightWidth, SkScalar spotAlpha) { 273 if (spotAlpha <= 0) { 274 return; 275 } 276 277 SkScalar zRatio = zValue / (lightPos.fZ - zValue); 278 if (zRatio < 0.0f) { 279 zRatio = 0.0f; 280 } else if (zRatio > 0.95f) { 281 zRatio = 0.95f; 282 } 283 SkScalar radius = 2.0f*lightWidth*zRatio; 284 285 SkRect pathRect; 286 SkRRect pathRRect; 287 SkScalar scaleFactors[2]; 288 if (!canvas->getTotalMatrix().getMinMaxScales(scaleFactors)) { 289 return; 290 } 291 if (scaleFactors[0] != scaleFactors[1] || radius*scaleFactors[0] >= 16384 || 292 !((path.isOval(&pathRect) && pathRect.width() == pathRect.height()) || 293 (path.isRRect(&pathRRect) && pathRRect.allCornersCircular()) || 294 path.isRect(&pathRect))) { 295 this->drawSpotShadow(canvas, path, zValue, lightPos, lightWidth, spotAlpha); 296 return; 297 } 298 299 // For all of these, we need to ensure we have a rrect with radius >= 0.5f in device space 300 const SkScalar minRadius = SK_ScalarHalf/scaleFactors[0]; 301 if (path.isOval(nullptr)) { 302 pathRRect = SkRRect::MakeOval(pathRect); 303 } else if (path.isRect(nullptr)) { 304 pathRRect = SkRRect::MakeRectXY(pathRect, minRadius, minRadius); 305 } else { 306 if (pathRRect.getSimpleRadii().fX < minRadius) { 307 pathRRect.setRectXY(pathRRect.rect(), minRadius, minRadius); 308 } 309 } 310 311 // compute the scale and translation for the shadow 312 SkScalar scale = lightPos.fZ / (lightPos.fZ - zValue); 313 SkRRect shadowRRect; 314 pathRRect.transform(SkMatrix::MakeScale(scale, scale), &shadowRRect); 315 SkPoint center = SkPoint::Make(shadowRRect.rect().centerX(), shadowRRect.rect().centerY()); 316 SkMatrix ctmInverse; 317 if (!canvas->getTotalMatrix().invert(&ctmInverse)) { 318 return; 319 } 320 SkPoint lightPos2D = SkPoint::Make(lightPos.fX, lightPos.fY); 321 ctmInverse.mapPoints(&lightPos2D, 1); 322 SkPoint offset = SkPoint::Make(zRatio*(center.fX - lightPos2D.fX), 323 zRatio*(center.fY - lightPos2D.fY)); 324 SkAutoCanvasRestore acr(canvas, true); 325 326 SkPaint paint; 327 paint.setAntiAlias(true); 328 // We want to extend the stroked area in so that it meets up with the caster 329 // geometry. The stroked geometry will, by definition already be inset half the 330 // stroke width but we also have to account for the scaling. 331 // We also add 1/2 to cover up AA on the interior edge. 332 SkScalar scaleOffset = (scale - 1.0f) * SkTMax(SkTMax(SkTAbs(pathRect.fLeft), 333 SkTAbs(pathRect.fRight)), 334 SkTMax(SkTAbs(pathRect.fTop), 335 SkTAbs(pathRect.fBottom))); 336 SkScalar insetAmount = offset.length() - (0.5f * radius) + scaleOffset + 0.5f; 337 338 // compute area 339 SkScalar strokeWidth = radius + insetAmount; 340 SkScalar strokedArea = 2.0f*strokeWidth*(shadowRRect.width() + shadowRRect.height()); 341 SkScalar filledArea = (shadowRRect.height() + radius)*(shadowRRect.width() + radius); 342 // If the area of the stroked geometry is larger than the fill geometry, or 343 // if our pad is too big to convert to 6.2 fixed point, just fill it. 344 if (strokedArea > filledArea) { 345 paint.setStyle(SkPaint::kStrokeAndFill_Style); 346 paint.setStrokeWidth(radius); 347 } else { 348 // Since we can't have unequal strokes, inset the shadow rect so the inner 349 // and outer edges of the stroke will land where we want. 350 SkRect insetRect = shadowRRect.rect().makeInset(insetAmount/2.0f, insetAmount/2.0f); 351 SkScalar insetRad = SkTMax(shadowRRect.getSimpleRadii().fX - insetAmount/2.0f, 352 minRadius); 353 354 shadowRRect = SkRRect::MakeRectXY(insetRect, insetRad, insetRad); 355 paint.setStyle(SkPaint::kStroke_Style); 356 paint.setStrokeWidth(strokeWidth); 357 } 358 paint.setShader(SkGaussianEdgeShader::Make()); 359 // handle scale of radius due to CTM 360 radius *= scaleFactors[0]; 361 // don't need to scale pad as it was computed from the transformed offset 362 SkASSERT(radius < 16384); 363 SkScalar pad = 0; 364 SkASSERT(pad < 64); 365 // Convert radius to 14.2 fixed point and place in the R & G components. 366 // Convert pad to 6.2 fixed point and place in the B component. 367 uint16_t iRadius = (uint16_t)(radius*4.0f); 368 unsigned char alpha = (unsigned char)(spotAlpha*255.999f); 369 paint.setColor(SkColorSetARGB(fIgnoreShadowAlpha ? 255 : alpha, 370 iRadius >> 8, iRadius & 0xff, 371 (unsigned char)(4.0f*pad))); 372 373 // apply transformation to shadow 374 canvas->translate(offset.fX, offset.fY); 375 canvas->drawRRect(shadowRRect, paint); 376 } 377 378 void drawShadowedPath(SkCanvas* canvas, const SkPath& path, SkScalar zValue, 379 const SkPaint& paint, SkScalar ambientAlpha, 380 const SkPoint3& lightPos, SkScalar lightWidth, SkScalar spotAlpha) { 381#ifdef USE_SHADOW_UTILS 382 if (fUseAlt) { 383 if (fShowAmbient) { 384 this->drawAmbientShadowAlt(canvas, path, zValue, ambientAlpha); 385 } 386 if (fShowSpot) { 387 this->drawSpotShadowAlt(canvas, path, zValue, lightPos, lightWidth, spotAlpha); 388 } 389 } else { 390 if (!fShowAmbient) { 391 ambientAlpha = 0; 392 } 393 if (!fShowSpot) { 394 spotAlpha = 0; 395 } 396 SkShadowUtils::DrawShadow(canvas, path, zValue, lightPos, lightWidth, 397 ambientAlpha, spotAlpha, SK_ColorBLACK); 398 } 399#else 400 if (fShowAmbient) { 401 if (fUseAlt) { 402 this->drawAmbientShadowAlt(canvas, path, zValue, ambientAlpha); 403 } else { 404 this->drawAmbientShadow(canvas, path, zValue, ambientAlpha); 405 } 406 } 407 if (fShowSpot) { 408 if (fUseAlt) { 409 this->drawSpotShadowAlt(canvas, path, zValue, lightPos, lightWidth, spotAlpha); 410 } else { 411 this->drawSpotShadow(canvas, path, zValue, lightPos, lightWidth, spotAlpha); 412 } 413 } 414#endif 415 416 if (fShowObject) { 417 canvas->drawPath(path, paint); 418 } else { 419 SkPaint strokePaint; 420 421 strokePaint.setColor(paint.getColor()); 422 strokePaint.setStyle(SkPaint::kStroke_Style); 423 424 canvas->drawPath(path, strokePaint); 425 } 426 } 427 428 void onDrawContent(SkCanvas* canvas) override { 429 this->drawBG(canvas); 430 const SkScalar kLightWidth = 2800; 431 const SkScalar kAmbientAlpha = 0.25f; 432 const SkScalar kSpotAlpha = 0.25f; 433 434 SkPaint paint; 435 paint.setAntiAlias(true); 436 437 SkPoint3 lightPos = fLightPos; 438 439 paint.setColor(SK_ColorWHITE); 440 canvas->translate(200, 90); 441 lightPos.fX += 200; 442 lightPos.fY += 90; 443 this->drawShadowedPath(canvas, fRRPath, 2, paint, kAmbientAlpha, 444 lightPos, kLightWidth, kSpotAlpha); 445 446 paint.setColor(SK_ColorRED); 447 canvas->translate(250, 0); 448 lightPos.fX += 250; 449 this->drawShadowedPath(canvas, fRectPath, 4, paint, kAmbientAlpha, 450 lightPos, kLightWidth, kSpotAlpha); 451 452 paint.setColor(SK_ColorBLUE); 453 canvas->translate(-250, 110); 454 lightPos.fX -= 250; 455 lightPos.fY += 110; 456 this->drawShadowedPath(canvas, fCirclePath, 8, paint, 0, 457 lightPos, kLightWidth, 0.5f); 458 459 paint.setColor(SK_ColorGREEN); 460 canvas->translate(250, 0); 461 lightPos.fX += 250; 462 this->drawShadowedPath(canvas, fRRPath, 64, paint, kAmbientAlpha, 463 lightPos, kLightWidth, kSpotAlpha); 464 465 paint.setColor(SK_ColorYELLOW); 466 canvas->translate(-250, 110); 467 lightPos.fX -= 250; 468 lightPos.fY += 110; 469 this->drawShadowedPath(canvas, fFunkyRRPath, 8, paint, kAmbientAlpha, 470 lightPos, kLightWidth, kSpotAlpha); 471 472 paint.setColor(SK_ColorCYAN); 473 canvas->translate(250, 0); 474 lightPos.fX += 250; 475 this->drawShadowedPath(canvas, fCubicPath, 16, paint, kAmbientAlpha, 476 lightPos, kLightWidth, kSpotAlpha); 477 } 478 479protected: 480 SkView::Click* onFindClickHandler(SkScalar x, SkScalar y, unsigned modi) override { 481 return new SkView::Click(this); 482 } 483 484 bool onClick(Click *click) override { 485 SkScalar x = click->fCurr.fX; 486 SkScalar y = click->fCurr.fY; 487 488 SkScalar dx = x - click->fPrev.fX; 489 SkScalar dy = y - click->fPrev.fY; 490 491 if (dx != 0 || dy != 0) { 492 fLightPos.fX += dx; 493 fLightPos.fY += dy; 494 this->inval(nullptr); 495 } 496 497 return true; 498 } 499 500private: 501 typedef SkView INHERITED; 502}; 503 504////////////////////////////////////////////////////////////////////////////// 505 506static SkView* MyFactory() { return new ShadowsView; } 507static SkViewRegister reg(MyFactory); 508