1 /* 2 * Copyright 2016 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 "gm.h" 9 #include "sk_tool_utils.h" 10 #include "SkAnimTimer.h" 11 #include "SkBlurMaskFilter.h" 12 #include "SkRRectsGaussianEdgeMaskFilter.h" 13 #include "SkPath.h" 14 #include "SkPathOps.h" 15 #include "SkRRect.h" 16 #include "SkStroke.h" 17 18 constexpr int kNumCols = 2; 19 constexpr int kNumRows = 5; 20 constexpr int kCellSize = 128; 21 constexpr SkScalar kPad = 8.0f; 22 constexpr SkScalar kInitialBlurRadius = 8.0f; 23 constexpr SkScalar kPeriod = 8.0f; 24 constexpr int kClipOffset = 32; 25 26 /////////////////////////////////////////////////////////////////////////////////////////////////// 27 28 class Object { 29 public: ~Object()30 virtual ~Object() {} 31 // When it returns true, this call will have placed a device-space _circle, rect or 32 // simple circular_ RRect in "rr" 33 virtual bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const = 0; 34 virtual SkPath asPath(SkScalar inset) const = 0; 35 virtual void draw(SkCanvas* canvas, const SkPaint& paint) const = 0; 36 virtual void clip(SkCanvas* canvas) const = 0; 37 virtual bool contains(const SkRect& r) const = 0; 38 virtual const SkRect& bounds() const = 0; 39 }; 40 41 typedef Object* (*PFMakeMthd)(const SkRect& r); 42 43 class RRect : public Object { 44 public: RRect(const SkRect & r)45 RRect(const SkRect& r) { 46 fRRect = SkRRect::MakeRectXY(r, 4*kPad, 4*kPad); 47 } 48 asDevSpaceRRect(const SkMatrix & ctm,SkRRect * rr) const49 bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const override { 50 if (!ctm.isSimilarity()) { // the corners have to remain circular 51 return false; 52 } 53 54 SkScalar scales[2]; 55 if (!ctm.getMinMaxScales(scales)) { 56 return false; 57 } 58 59 SkASSERT(SkScalarNearlyEqual(scales[0], scales[1])); 60 61 SkRect devRect; 62 ctm.mapRect(&devRect, fRRect.rect()); 63 64 SkScalar scaledRad = scales[0] * fRRect.getSimpleRadii().fX; 65 66 *rr = SkRRect::MakeRectXY(devRect, scaledRad, scaledRad); 67 return true; 68 } 69 asPath(SkScalar inset) const70 SkPath asPath(SkScalar inset) const override { 71 SkRRect tmp = fRRect; 72 tmp.inset(inset, inset); 73 SkPath p; 74 p.addRRect(tmp); 75 return p; 76 } 77 draw(SkCanvas * canvas,const SkPaint & paint) const78 void draw(SkCanvas* canvas, const SkPaint& paint) const override { 79 canvas->drawRRect(fRRect, paint); 80 } 81 clip(SkCanvas * canvas) const82 void clip(SkCanvas* canvas) const override { 83 canvas->clipRRect(fRRect); 84 } 85 contains(const SkRect & r) const86 bool contains(const SkRect& r) const override { 87 return fRRect.contains(r); 88 } 89 bounds() const90 const SkRect& bounds() const override { 91 return fRRect.getBounds(); 92 } 93 Make(const SkRect & r)94 static Object* Make(const SkRect& r) { 95 return new RRect(r); 96 } 97 98 private: 99 SkRRect fRRect; 100 }; 101 102 class StrokedRRect : public Object { 103 public: StrokedRRect(const SkRect & r)104 StrokedRRect(const SkRect& r) { 105 fRRect = SkRRect::MakeRectXY(r, 2*kPad, 2*kPad); 106 fStrokedBounds = r.makeOutset(kPad, kPad); 107 } 108 asDevSpaceRRect(const SkMatrix & ctm,SkRRect * rr) const109 bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const override { 110 return false; 111 } 112 asPath(SkScalar inset) const113 SkPath asPath(SkScalar inset) const override { 114 SkRRect tmp = fRRect; 115 tmp.inset(inset, inset); 116 117 // In this case we want the outline of the stroked rrect 118 SkPaint paint; 119 paint.setAntiAlias(true); 120 paint.setStyle(SkPaint::kStroke_Style); 121 paint.setStrokeWidth(kPad); 122 123 SkPath p, stroked; 124 p.addRRect(tmp); 125 SkStroke stroke(paint); 126 stroke.strokePath(p, &stroked); 127 return stroked; 128 } 129 draw(SkCanvas * canvas,const SkPaint & paint) const130 void draw(SkCanvas* canvas, const SkPaint& paint) const override { 131 SkPaint stroke(paint); 132 stroke.setStyle(SkPaint::kStroke_Style); 133 stroke.setStrokeWidth(kPad); 134 135 canvas->drawRRect(fRRect, stroke); 136 } 137 clip(SkCanvas * canvas) const138 void clip(SkCanvas* canvas) const override { 139 canvas->clipPath(this->asPath(0.0f)); 140 } 141 contains(const SkRect & r) const142 bool contains(const SkRect& r) const override { 143 return false; 144 } 145 bounds() const146 const SkRect& bounds() const override { 147 return fStrokedBounds; 148 } 149 Make(const SkRect & r)150 static Object* Make(const SkRect& r) { 151 return new StrokedRRect(r); 152 } 153 154 private: 155 SkRRect fRRect; 156 SkRect fStrokedBounds; 157 }; 158 159 class Oval : public Object { 160 public: Oval(const SkRect & r)161 Oval(const SkRect& r) { 162 fRRect = SkRRect::MakeOval(r); 163 } 164 asDevSpaceRRect(const SkMatrix & ctm,SkRRect * rr) const165 bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const override { 166 if (!ctm.isSimilarity()) { // circles have to remain circles 167 return false; 168 } 169 170 SkRect devRect; 171 ctm.mapRect(&devRect, fRRect.rect()); 172 *rr = SkRRect::MakeOval(devRect); 173 return true; 174 } 175 asPath(SkScalar inset) const176 SkPath asPath(SkScalar inset) const override { 177 SkRRect tmp = fRRect; 178 tmp.inset(inset, inset); 179 180 SkPath p; 181 p.addRRect(tmp); 182 return p; 183 } 184 draw(SkCanvas * canvas,const SkPaint & paint) const185 void draw(SkCanvas* canvas, const SkPaint& paint) const override { 186 canvas->drawRRect(fRRect, paint); 187 } 188 clip(SkCanvas * canvas) const189 void clip(SkCanvas* canvas) const override { 190 canvas->clipRRect(fRRect); 191 } 192 contains(const SkRect & r) const193 bool contains(const SkRect& r) const override { 194 return fRRect.contains(r); 195 } 196 bounds() const197 const SkRect& bounds() const override { 198 return fRRect.getBounds(); 199 } 200 Make(const SkRect & r)201 static Object* Make(const SkRect& r) { 202 return new Oval(r); 203 } 204 205 private: 206 SkRRect fRRect; 207 }; 208 209 class Rect : public Object { 210 public: Rect(const SkRect & r)211 Rect(const SkRect& r) : fRect(r) { } 212 asDevSpaceRRect(const SkMatrix & ctm,SkRRect * rr) const213 bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const override { 214 if (!ctm.rectStaysRect()) { 215 return false; 216 } 217 218 SkRect devRect; 219 ctm.mapRect(&devRect, fRect); 220 *rr = SkRRect::MakeRect(devRect); 221 return true; 222 } 223 asPath(SkScalar inset) const224 SkPath asPath(SkScalar inset) const override { 225 SkRect tmp = fRect; 226 tmp.inset(inset, inset); 227 228 SkPath p; 229 p.addRect(tmp); 230 return p; 231 } 232 draw(SkCanvas * canvas,const SkPaint & paint) const233 void draw(SkCanvas* canvas, const SkPaint& paint) const override { 234 canvas->drawRect(fRect, paint); 235 } 236 clip(SkCanvas * canvas) const237 void clip(SkCanvas* canvas) const override { 238 canvas->clipRect(fRect); 239 } 240 contains(const SkRect & r) const241 bool contains(const SkRect& r) const override { 242 return fRect.contains(r); 243 } 244 bounds() const245 const SkRect& bounds() const override { 246 return fRect; 247 } 248 Make(const SkRect & r)249 static Object* Make(const SkRect& r) { 250 return new Rect(r); 251 } 252 253 private: 254 SkRect fRect; 255 }; 256 257 class Pentagon : public Object { 258 public: Pentagon(const SkRect & r)259 Pentagon(const SkRect& r) { 260 SkPoint points[5] = { 261 { 0.000000f, -1.000000f }, 262 { -0.951056f, -0.309017f }, 263 { -0.587785f, 0.809017f }, 264 { 0.587785f, 0.809017f }, 265 { 0.951057f, -0.309017f }, 266 }; 267 268 SkScalar height = r.height()/2.0f; 269 SkScalar width = r.width()/2.0f; 270 271 fPath.moveTo(r.centerX() + points[0].fX * width, r.centerY() + points[0].fY * height); 272 fPath.lineTo(r.centerX() + points[1].fX * width, r.centerY() + points[1].fY * height); 273 fPath.lineTo(r.centerX() + points[2].fX * width, r.centerY() + points[2].fY * height); 274 fPath.lineTo(r.centerX() + points[3].fX * width, r.centerY() + points[3].fY * height); 275 fPath.lineTo(r.centerX() + points[4].fX * width, r.centerY() + points[4].fY * height); 276 fPath.close(); 277 } 278 asDevSpaceRRect(const SkMatrix & ctm,SkRRect * rr) const279 bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const override { 280 return false; 281 } 282 asPath(SkScalar inset) const283 SkPath asPath(SkScalar inset) const override { return fPath; } 284 draw(SkCanvas * canvas,const SkPaint & paint) const285 void draw(SkCanvas* canvas, const SkPaint& paint) const override { 286 canvas->drawPath(fPath, paint); 287 } 288 clip(SkCanvas * canvas) const289 void clip(SkCanvas* canvas) const override { 290 canvas->clipPath(this->asPath(0.0f)); 291 } 292 contains(const SkRect & r) const293 bool contains(const SkRect& r) const override { 294 return false; 295 } 296 bounds() const297 const SkRect& bounds() const override { 298 return fPath.getBounds(); 299 } 300 Make(const SkRect & r)301 static Object* Make(const SkRect& r) { 302 return new Pentagon(r); 303 } 304 305 private: 306 SkPath fPath; 307 }; 308 309 /////////////////////////////////////////////////////////////////////////////////////////////////// 310 namespace skiagm { 311 312 // This GM attempts to mimic Android's reveal animation 313 class RevealGM : public GM { 314 public: 315 enum Mode { 316 kBlurMask_Mode, 317 kRRectsGaussianEdge_Mode, 318 319 kLast_Mode = kRRectsGaussianEdge_Mode 320 }; 321 static const int kModeCount = kLast_Mode + 1; 322 323 enum CoverageGeom { 324 kRect_CoverageGeom, 325 kRRect_CoverageGeom, 326 kDRRect_CoverageGeom, 327 kPath_CoverageGeom, 328 329 kLast_CoverageGeom = kPath_CoverageGeom 330 }; 331 static const int kCoverageGeomCount = kLast_CoverageGeom + 1; 332 RevealGM()333 RevealGM() 334 : fFraction(0.5f) 335 , fMode(kRRectsGaussianEdge_Mode) 336 , fPause(false) 337 , fBlurRadius(kInitialBlurRadius) 338 , fCoverageGeom(kRect_CoverageGeom) { 339 this->setBGColor(sk_tool_utils::color_to_565(0xFFCCCCCC)); 340 } 341 342 protected: runAsBench() const343 bool runAsBench() const override { return true; } 344 onShortName()345 SkString onShortName() override { 346 return SkString("reveal"); 347 } 348 onISize()349 SkISize onISize() override { 350 return SkISize::Make(kNumCols * kCellSize, kNumRows * kCellSize); 351 } 352 onDraw(SkCanvas * canvas)353 void onDraw(SkCanvas* canvas) override { 354 PFMakeMthd clipMakes[kNumCols] = { Oval::Make, Rect::Make }; 355 PFMakeMthd drawMakes[kNumRows] = { 356 RRect::Make, StrokedRRect::Make, Oval::Make, Rect::Make, Pentagon::Make 357 }; 358 359 SkPaint strokePaint; 360 strokePaint.setStyle(SkPaint::kStroke_Style); 361 strokePaint.setStrokeWidth(0.0f); 362 363 for (int y = 0; y < kNumRows; ++y) { 364 for (int x = 0; x < kNumCols; ++x) { 365 SkRect cell = SkRect::MakeXYWH(SkIntToScalar(x*kCellSize), 366 SkIntToScalar(y*kCellSize), 367 SkIntToScalar(kCellSize), 368 SkIntToScalar(kCellSize)); 369 370 canvas->save(); 371 canvas->clipRect(cell); 372 373 cell.inset(kPad, kPad); 374 SkPoint clipCenter = SkPoint::Make(cell.centerX() - kClipOffset, 375 cell.centerY() + kClipOffset); 376 SkScalar curSize = kCellSize * fFraction; 377 const SkRect clipRect = SkRect::MakeLTRB(clipCenter.fX - curSize, 378 clipCenter.fY - curSize, 379 clipCenter.fX + curSize, 380 clipCenter.fY + curSize); 381 382 std::unique_ptr<Object> clipObj((*clipMakes[x])(clipRect)); 383 std::unique_ptr<Object> drawObj((*drawMakes[y])(cell)); 384 385 // The goal is to replace this clipped draw (which clips the 386 // shadow) with a draw using the geometric clip 387 if (kBlurMask_Mode == fMode) { 388 SkPath clippedPath; 389 390 SkScalar sigma = fBlurRadius / 4.0f; 391 392 if (clipObj->contains(drawObj->bounds())) { 393 clippedPath = drawObj->asPath(2.0f*sigma); 394 } else { 395 SkPath drawnPath = drawObj->asPath(2.0f*sigma); 396 SkPath clipPath = clipObj->asPath(2.0f*sigma); 397 398 SkAssertResult(Op(clipPath, drawnPath, kIntersect_SkPathOp, &clippedPath)); 399 } 400 401 SkPaint blurPaint; 402 blurPaint.setAntiAlias(true); 403 blurPaint.setMaskFilter(SkBlurMaskFilter::Make(kNormal_SkBlurStyle, sigma)); 404 canvas->drawPath(clippedPath, blurPaint); 405 } else { 406 SkASSERT(kRRectsGaussianEdge_Mode == fMode); 407 408 SkRect cover = drawObj->bounds(); 409 SkAssertResult(cover.intersect(clipObj->bounds())); 410 411 SkPaint paint; 412 413 SkRRect devSpaceClipRR, devSpaceDrawnRR; 414 415 if (clipObj->asDevSpaceRRect(canvas->getTotalMatrix(), &devSpaceClipRR) && 416 drawObj->asDevSpaceRRect(canvas->getTotalMatrix(), &devSpaceDrawnRR)) { 417 paint.setMaskFilter(SkRRectsGaussianEdgeMaskFilter::Make(devSpaceClipRR, 418 devSpaceDrawnRR, 419 fBlurRadius)); 420 } 421 422 strokePaint.setColor(SK_ColorBLUE); 423 424 switch (fCoverageGeom) { 425 case kRect_CoverageGeom: 426 canvas->drawRect(cover, paint); 427 canvas->drawRect(cover, strokePaint); 428 break; 429 case kRRect_CoverageGeom: { 430 const SkRRect rrect = SkRRect::MakeRectXY( 431 cover.makeOutset(10.0f, 10.0f), 432 10.0f, 10.0f); 433 canvas->drawRRect(rrect, paint); 434 canvas->drawRRect(rrect, strokePaint); 435 break; 436 } 437 case kDRRect_CoverageGeom: { 438 const SkRRect inner = SkRRect::MakeRectXY(cover.makeInset(10.0f, 10.0f), 439 10.0f, 10.0f); 440 const SkRRect outer = SkRRect::MakeRectXY( 441 cover.makeOutset(10.0f, 10.0f), 442 10.0f, 10.0f); 443 canvas->drawDRRect(outer, inner, paint); 444 canvas->drawDRRect(outer, inner, strokePaint); 445 break; 446 } 447 case kPath_CoverageGeom: { 448 SkPath path; 449 path.moveTo(cover.fLeft, cover.fTop); 450 path.lineTo(cover.centerX(), cover.centerY()); 451 path.lineTo(cover.fRight, cover.fTop); 452 path.lineTo(cover.fRight, cover.fBottom); 453 path.lineTo(cover.centerX(), cover.centerY()); 454 path.lineTo(cover.fLeft, cover.fBottom); 455 path.close(); 456 canvas->drawPath(path, paint); 457 canvas->drawPath(path, strokePaint); 458 break; 459 } 460 } 461 } 462 463 // Draw the clip and draw objects for reference 464 strokePaint.setColor(SK_ColorRED); 465 canvas->drawPath(drawObj->asPath(0.0f), strokePaint); 466 strokePaint.setColor(SK_ColorGREEN); 467 canvas->drawPath(clipObj->asPath(0.0f), strokePaint); 468 469 canvas->restore(); 470 } 471 } 472 } 473 onHandleKey(SkUnichar uni)474 bool onHandleKey(SkUnichar uni) override { 475 switch (uni) { 476 case 'C': 477 fMode = (Mode)((fMode + 1) % kModeCount); 478 return true; 479 case '+': 480 fBlurRadius += 1.0f; 481 return true; 482 case '-': 483 fBlurRadius = SkTMax(1.0f, fBlurRadius - 1.0f); 484 return true; 485 case 'p': 486 fPause = !fPause; 487 return true; 488 case 'G': 489 fCoverageGeom = (CoverageGeom) ((fCoverageGeom+1) % kCoverageGeomCount); 490 return true; 491 } 492 493 return false; 494 } 495 onAnimate(const SkAnimTimer & timer)496 bool onAnimate(const SkAnimTimer& timer) override { 497 if (!fPause) { 498 fFraction = timer.pingPong(kPeriod, 0.0f, 0.0f, 1.0f); 499 } 500 return true; 501 } 502 503 private: 504 SkScalar fFraction; 505 Mode fMode; 506 bool fPause; 507 float fBlurRadius; 508 CoverageGeom fCoverageGeom; 509 510 typedef GM INHERITED; 511 }; 512 513 ////////////////////////////////////////////////////////////////////////////// 514 515 DEF_GM(return new RevealGM;) 516 } 517