Home | History | Annotate | Download | only in gm
      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:
     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:
     45     RRect(const SkRect& r) {
     46         fRRect = SkRRect::MakeRectXY(r, 4*kPad, 4*kPad);
     47     }
     48 
     49     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 
     70     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 
     78     void draw(SkCanvas* canvas, const SkPaint& paint) const override {
     79         canvas->drawRRect(fRRect, paint);
     80     }
     81 
     82     void clip(SkCanvas* canvas) const override {
     83         canvas->clipRRect(fRRect);
     84     }
     85 
     86     bool contains(const SkRect& r) const override {
     87         return fRRect.contains(r);
     88     }
     89 
     90     const SkRect& bounds() const override {
     91         return fRRect.getBounds();
     92     }
     93 
     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:
    104     StrokedRRect(const SkRect& r) {
    105         fRRect = SkRRect::MakeRectXY(r, 2*kPad, 2*kPad);
    106         fStrokedBounds = r.makeOutset(kPad, kPad);
    107     }
    108 
    109     bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const override {
    110         return false;
    111     }
    112 
    113     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 
    130     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 
    138     void clip(SkCanvas* canvas) const override {
    139         canvas->clipPath(this->asPath(0.0f));
    140     }
    141 
    142     bool contains(const SkRect& r) const override {
    143         return false;
    144     }
    145 
    146     const SkRect& bounds() const override {
    147         return fStrokedBounds;
    148     }
    149 
    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:
    161     Oval(const SkRect& r) {
    162         fRRect = SkRRect::MakeOval(r);
    163     }
    164 
    165     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 
    176     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 
    185     void draw(SkCanvas* canvas, const SkPaint& paint) const override {
    186         canvas->drawRRect(fRRect, paint);
    187     }
    188 
    189     void clip(SkCanvas* canvas) const override {
    190         canvas->clipRRect(fRRect);
    191     }
    192 
    193     bool contains(const SkRect& r) const override {
    194         return fRRect.contains(r);
    195     }
    196 
    197     const SkRect& bounds() const override {
    198         return fRRect.getBounds();
    199     }
    200 
    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:
    211     Rect(const SkRect& r) : fRect(r) { }
    212 
    213     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 
    224     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 
    233     void draw(SkCanvas* canvas, const SkPaint& paint) const override {
    234         canvas->drawRect(fRect, paint);
    235     }
    236 
    237     void clip(SkCanvas* canvas) const override {
    238         canvas->clipRect(fRect);
    239     }
    240 
    241     bool contains(const SkRect& r) const override {
    242         return fRect.contains(r);
    243     }
    244 
    245     const SkRect& bounds() const override {
    246         return fRect;
    247     }
    248 
    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:
    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 
    279     bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const override {
    280         return false;
    281     }
    282 
    283     SkPath asPath(SkScalar inset) const override { return fPath; }
    284 
    285     void draw(SkCanvas* canvas, const SkPaint& paint) const override {
    286         canvas->drawPath(fPath, paint);
    287     }
    288 
    289     void clip(SkCanvas* canvas) const override {
    290         canvas->clipPath(this->asPath(0.0f));
    291     }
    292 
    293     bool contains(const SkRect& r) const override {
    294         return false;
    295     }
    296 
    297     const SkRect& bounds() const override {
    298         return fPath.getBounds();
    299     }
    300 
    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 
    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:
    343     bool runAsBench() const override { return true; }
    344 
    345     SkString onShortName() override {
    346         return SkString("reveal");
    347     }
    348 
    349     SkISize onISize() override {
    350         return SkISize::Make(kNumCols * kCellSize, kNumRows * kCellSize);
    351     }
    352 
    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 
    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 
    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