• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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