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