| /* |
| * Copyright 2016 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "gm.h" |
| #include "sk_tool_utils.h" |
| #include "SkAnimTimer.h" |
| #include "SkBlurMaskFilter.h" |
| #include "SkRRectsGaussianEdgeMaskFilter.h" |
| #include "SkPath.h" |
| #include "SkPathOps.h" |
| #include "SkRRectPriv.h" |
| #include "SkStroke.h" |
| |
| constexpr int kNumCols = 2; |
| constexpr int kNumRows = 5; |
| constexpr int kCellSize = 128; |
| constexpr SkScalar kPad = 8.0f; |
| constexpr SkScalar kInitialBlurRadius = 8.0f; |
| constexpr SkScalar kPeriod = 8.0f; |
| constexpr int kClipOffset = 32; |
| |
| /////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| class Object { |
| public: |
| virtual ~Object() {} |
| // When it returns true, this call will have placed a device-space _circle, rect or |
| // simple circular_ RRect in "rr" |
| virtual bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const = 0; |
| virtual SkPath asPath(SkScalar inset) const = 0; |
| virtual void draw(SkCanvas* canvas, const SkPaint& paint) const = 0; |
| virtual void clip(SkCanvas* canvas) const = 0; |
| virtual bool contains(const SkRect& r) const = 0; |
| virtual const SkRect& bounds() const = 0; |
| }; |
| |
| typedef Object* (*PFMakeMthd)(const SkRect& r); |
| |
| class RRect : public Object { |
| public: |
| RRect(const SkRect& r) { |
| fRRect = SkRRect::MakeRectXY(r, 4*kPad, 4*kPad); |
| } |
| |
| bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const override { |
| if (!ctm.isSimilarity()) { // the corners have to remain circular |
| return false; |
| } |
| |
| SkScalar scales[2]; |
| if (!ctm.getMinMaxScales(scales)) { |
| return false; |
| } |
| |
| SkASSERT(SkScalarNearlyEqual(scales[0], scales[1])); |
| |
| SkRect devRect; |
| ctm.mapRect(&devRect, fRRect.rect()); |
| |
| SkScalar scaledRad = scales[0] * SkRRectPriv::GetSimpleRadii(fRRect).fX; |
| |
| *rr = SkRRect::MakeRectXY(devRect, scaledRad, scaledRad); |
| return true; |
| } |
| |
| SkPath asPath(SkScalar inset) const override { |
| SkRRect tmp = fRRect; |
| tmp.inset(inset, inset); |
| SkPath p; |
| p.addRRect(tmp); |
| return p; |
| } |
| |
| void draw(SkCanvas* canvas, const SkPaint& paint) const override { |
| canvas->drawRRect(fRRect, paint); |
| } |
| |
| void clip(SkCanvas* canvas) const override { |
| canvas->clipRRect(fRRect); |
| } |
| |
| bool contains(const SkRect& r) const override { |
| return fRRect.contains(r); |
| } |
| |
| const SkRect& bounds() const override { |
| return fRRect.getBounds(); |
| } |
| |
| static Object* Make(const SkRect& r) { |
| return new RRect(r); |
| } |
| |
| private: |
| SkRRect fRRect; |
| }; |
| |
| class StrokedRRect : public Object { |
| public: |
| StrokedRRect(const SkRect& r) { |
| fRRect = SkRRect::MakeRectXY(r, 2*kPad, 2*kPad); |
| fStrokedBounds = r.makeOutset(kPad, kPad); |
| } |
| |
| bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const override { |
| return false; |
| } |
| |
| SkPath asPath(SkScalar inset) const override { |
| SkRRect tmp = fRRect; |
| tmp.inset(inset, inset); |
| |
| // In this case we want the outline of the stroked rrect |
| SkPaint paint; |
| paint.setAntiAlias(true); |
| paint.setStyle(SkPaint::kStroke_Style); |
| paint.setStrokeWidth(kPad); |
| |
| SkPath p, stroked; |
| p.addRRect(tmp); |
| SkStroke stroke(paint); |
| stroke.strokePath(p, &stroked); |
| return stroked; |
| } |
| |
| void draw(SkCanvas* canvas, const SkPaint& paint) const override { |
| SkPaint stroke(paint); |
| stroke.setStyle(SkPaint::kStroke_Style); |
| stroke.setStrokeWidth(kPad); |
| |
| canvas->drawRRect(fRRect, stroke); |
| } |
| |
| void clip(SkCanvas* canvas) const override { |
| canvas->clipPath(this->asPath(0.0f)); |
| } |
| |
| bool contains(const SkRect& r) const override { |
| return false; |
| } |
| |
| const SkRect& bounds() const override { |
| return fStrokedBounds; |
| } |
| |
| static Object* Make(const SkRect& r) { |
| return new StrokedRRect(r); |
| } |
| |
| private: |
| SkRRect fRRect; |
| SkRect fStrokedBounds; |
| }; |
| |
| class Oval : public Object { |
| public: |
| Oval(const SkRect& r) { |
| fRRect = SkRRect::MakeOval(r); |
| } |
| |
| bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const override { |
| if (!ctm.isSimilarity()) { // circles have to remain circles |
| return false; |
| } |
| |
| SkRect devRect; |
| ctm.mapRect(&devRect, fRRect.rect()); |
| *rr = SkRRect::MakeOval(devRect); |
| return true; |
| } |
| |
| SkPath asPath(SkScalar inset) const override { |
| SkRRect tmp = fRRect; |
| tmp.inset(inset, inset); |
| |
| SkPath p; |
| p.addRRect(tmp); |
| return p; |
| } |
| |
| void draw(SkCanvas* canvas, const SkPaint& paint) const override { |
| canvas->drawRRect(fRRect, paint); |
| } |
| |
| void clip(SkCanvas* canvas) const override { |
| canvas->clipRRect(fRRect); |
| } |
| |
| bool contains(const SkRect& r) const override { |
| return fRRect.contains(r); |
| } |
| |
| const SkRect& bounds() const override { |
| return fRRect.getBounds(); |
| } |
| |
| static Object* Make(const SkRect& r) { |
| return new Oval(r); |
| } |
| |
| private: |
| SkRRect fRRect; |
| }; |
| |
| class Rect : public Object { |
| public: |
| Rect(const SkRect& r) : fRect(r) { } |
| |
| bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const override { |
| if (!ctm.rectStaysRect()) { |
| return false; |
| } |
| |
| SkRect devRect; |
| ctm.mapRect(&devRect, fRect); |
| *rr = SkRRect::MakeRect(devRect); |
| return true; |
| } |
| |
| SkPath asPath(SkScalar inset) const override { |
| SkRect tmp = fRect; |
| tmp.inset(inset, inset); |
| |
| SkPath p; |
| p.addRect(tmp); |
| return p; |
| } |
| |
| void draw(SkCanvas* canvas, const SkPaint& paint) const override { |
| canvas->drawRect(fRect, paint); |
| } |
| |
| void clip(SkCanvas* canvas) const override { |
| canvas->clipRect(fRect); |
| } |
| |
| bool contains(const SkRect& r) const override { |
| return fRect.contains(r); |
| } |
| |
| const SkRect& bounds() const override { |
| return fRect; |
| } |
| |
| static Object* Make(const SkRect& r) { |
| return new Rect(r); |
| } |
| |
| private: |
| SkRect fRect; |
| }; |
| |
| class Pentagon : public Object { |
| public: |
| Pentagon(const SkRect& r) { |
| SkPoint points[5] = { |
| { 0.000000f, -1.000000f }, |
| { -0.951056f, -0.309017f }, |
| { -0.587785f, 0.809017f }, |
| { 0.587785f, 0.809017f }, |
| { 0.951057f, -0.309017f }, |
| }; |
| |
| SkScalar height = r.height()/2.0f; |
| SkScalar width = r.width()/2.0f; |
| |
| fPath.moveTo(r.centerX() + points[0].fX * width, r.centerY() + points[0].fY * height); |
| fPath.lineTo(r.centerX() + points[1].fX * width, r.centerY() + points[1].fY * height); |
| fPath.lineTo(r.centerX() + points[2].fX * width, r.centerY() + points[2].fY * height); |
| fPath.lineTo(r.centerX() + points[3].fX * width, r.centerY() + points[3].fY * height); |
| fPath.lineTo(r.centerX() + points[4].fX * width, r.centerY() + points[4].fY * height); |
| fPath.close(); |
| } |
| |
| bool asDevSpaceRRect(const SkMatrix& ctm, SkRRect* rr) const override { |
| return false; |
| } |
| |
| SkPath asPath(SkScalar inset) const override { return fPath; } |
| |
| void draw(SkCanvas* canvas, const SkPaint& paint) const override { |
| canvas->drawPath(fPath, paint); |
| } |
| |
| void clip(SkCanvas* canvas) const override { |
| canvas->clipPath(this->asPath(0.0f)); |
| } |
| |
| bool contains(const SkRect& r) const override { |
| return false; |
| } |
| |
| const SkRect& bounds() const override { |
| return fPath.getBounds(); |
| } |
| |
| static Object* Make(const SkRect& r) { |
| return new Pentagon(r); |
| } |
| |
| private: |
| SkPath fPath; |
| }; |
| |
| /////////////////////////////////////////////////////////////////////////////////////////////////// |
| namespace skiagm { |
| |
| // This GM attempts to mimic Android's reveal animation |
| class RevealGM : public GM { |
| public: |
| enum Mode { |
| kBlurMask_Mode, |
| kRRectsGaussianEdge_Mode, |
| |
| kLast_Mode = kRRectsGaussianEdge_Mode |
| }; |
| static const int kModeCount = kLast_Mode + 1; |
| |
| enum CoverageGeom { |
| kRect_CoverageGeom, |
| kRRect_CoverageGeom, |
| kDRRect_CoverageGeom, |
| kPath_CoverageGeom, |
| |
| kLast_CoverageGeom = kPath_CoverageGeom |
| }; |
| static const int kCoverageGeomCount = kLast_CoverageGeom + 1; |
| |
| RevealGM() |
| : fFraction(0.5f) |
| , fMode(kRRectsGaussianEdge_Mode) |
| , fPause(false) |
| , fBlurRadius(kInitialBlurRadius) |
| , fCoverageGeom(kRect_CoverageGeom) { |
| this->setBGColor(sk_tool_utils::color_to_565(0xFFCCCCCC)); |
| } |
| |
| protected: |
| bool runAsBench() const override { return true; } |
| |
| SkString onShortName() override { |
| return SkString("reveal"); |
| } |
| |
| SkISize onISize() override { |
| return SkISize::Make(kNumCols * kCellSize, kNumRows * kCellSize); |
| } |
| |
| void onDraw(SkCanvas* canvas) override { |
| PFMakeMthd clipMakes[kNumCols] = { Oval::Make, Rect::Make }; |
| PFMakeMthd drawMakes[kNumRows] = { |
| RRect::Make, StrokedRRect::Make, Oval::Make, Rect::Make, Pentagon::Make |
| }; |
| |
| SkPaint strokePaint; |
| strokePaint.setStyle(SkPaint::kStroke_Style); |
| strokePaint.setStrokeWidth(0.0f); |
| |
| for (int y = 0; y < kNumRows; ++y) { |
| for (int x = 0; x < kNumCols; ++x) { |
| SkRect cell = SkRect::MakeXYWH(SkIntToScalar(x*kCellSize), |
| SkIntToScalar(y*kCellSize), |
| SkIntToScalar(kCellSize), |
| SkIntToScalar(kCellSize)); |
| |
| canvas->save(); |
| canvas->clipRect(cell); |
| |
| cell.inset(kPad, kPad); |
| SkPoint clipCenter = SkPoint::Make(cell.centerX() - kClipOffset, |
| cell.centerY() + kClipOffset); |
| SkScalar curSize = kCellSize * fFraction; |
| const SkRect clipRect = SkRect::MakeLTRB(clipCenter.fX - curSize, |
| clipCenter.fY - curSize, |
| clipCenter.fX + curSize, |
| clipCenter.fY + curSize); |
| |
| std::unique_ptr<Object> clipObj((*clipMakes[x])(clipRect)); |
| std::unique_ptr<Object> drawObj((*drawMakes[y])(cell)); |
| |
| // The goal is to replace this clipped draw (which clips the |
| // shadow) with a draw using the geometric clip |
| if (kBlurMask_Mode == fMode) { |
| SkPath clippedPath; |
| |
| SkScalar sigma = fBlurRadius / 4.0f; |
| |
| if (clipObj->contains(drawObj->bounds())) { |
| clippedPath = drawObj->asPath(2.0f*sigma); |
| } else { |
| SkPath drawnPath = drawObj->asPath(2.0f*sigma); |
| SkPath clipPath = clipObj->asPath(2.0f*sigma); |
| |
| SkAssertResult(Op(clipPath, drawnPath, kIntersect_SkPathOp, &clippedPath)); |
| } |
| |
| SkPaint blurPaint; |
| blurPaint.setAntiAlias(true); |
| blurPaint.setMaskFilter(SkBlurMaskFilter::Make(kNormal_SkBlurStyle, sigma)); |
| canvas->drawPath(clippedPath, blurPaint); |
| } else { |
| SkASSERT(kRRectsGaussianEdge_Mode == fMode); |
| |
| SkRect cover = drawObj->bounds(); |
| SkAssertResult(cover.intersect(clipObj->bounds())); |
| |
| SkPaint paint; |
| |
| SkRRect devSpaceClipRR, devSpaceDrawnRR; |
| |
| if (clipObj->asDevSpaceRRect(canvas->getTotalMatrix(), &devSpaceClipRR) && |
| drawObj->asDevSpaceRRect(canvas->getTotalMatrix(), &devSpaceDrawnRR)) { |
| paint.setMaskFilter(SkRRectsGaussianEdgeMaskFilter::Make(devSpaceClipRR, |
| devSpaceDrawnRR, |
| fBlurRadius)); |
| } |
| |
| strokePaint.setColor(SK_ColorBLUE); |
| |
| switch (fCoverageGeom) { |
| case kRect_CoverageGeom: |
| canvas->drawRect(cover, paint); |
| canvas->drawRect(cover, strokePaint); |
| break; |
| case kRRect_CoverageGeom: { |
| const SkRRect rrect = SkRRect::MakeRectXY( |
| cover.makeOutset(10.0f, 10.0f), |
| 10.0f, 10.0f); |
| canvas->drawRRect(rrect, paint); |
| canvas->drawRRect(rrect, strokePaint); |
| break; |
| } |
| case kDRRect_CoverageGeom: { |
| const SkRRect inner = SkRRect::MakeRectXY(cover.makeInset(10.0f, 10.0f), |
| 10.0f, 10.0f); |
| const SkRRect outer = SkRRect::MakeRectXY( |
| cover.makeOutset(10.0f, 10.0f), |
| 10.0f, 10.0f); |
| canvas->drawDRRect(outer, inner, paint); |
| canvas->drawDRRect(outer, inner, strokePaint); |
| break; |
| } |
| case kPath_CoverageGeom: { |
| SkPath path; |
| path.moveTo(cover.fLeft, cover.fTop); |
| path.lineTo(cover.centerX(), cover.centerY()); |
| path.lineTo(cover.fRight, cover.fTop); |
| path.lineTo(cover.fRight, cover.fBottom); |
| path.lineTo(cover.centerX(), cover.centerY()); |
| path.lineTo(cover.fLeft, cover.fBottom); |
| path.close(); |
| canvas->drawPath(path, paint); |
| canvas->drawPath(path, strokePaint); |
| break; |
| } |
| } |
| } |
| |
| // Draw the clip and draw objects for reference |
| strokePaint.setColor(SK_ColorRED); |
| canvas->drawPath(drawObj->asPath(0.0f), strokePaint); |
| strokePaint.setColor(SK_ColorGREEN); |
| canvas->drawPath(clipObj->asPath(0.0f), strokePaint); |
| |
| canvas->restore(); |
| } |
| } |
| } |
| |
| bool onHandleKey(SkUnichar uni) override { |
| switch (uni) { |
| case 'C': |
| fMode = (Mode)((fMode + 1) % kModeCount); |
| return true; |
| case '+': |
| fBlurRadius += 1.0f; |
| return true; |
| case '-': |
| fBlurRadius = SkTMax(1.0f, fBlurRadius - 1.0f); |
| return true; |
| case 'p': |
| fPause = !fPause; |
| return true; |
| case 'G': |
| fCoverageGeom = (CoverageGeom) ((fCoverageGeom+1) % kCoverageGeomCount); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool onAnimate(const SkAnimTimer& timer) override { |
| if (!fPause) { |
| fFraction = timer.pingPong(kPeriod, 0.0f, 0.0f, 1.0f); |
| } |
| return true; |
| } |
| |
| private: |
| SkScalar fFraction; |
| Mode fMode; |
| bool fPause; |
| float fBlurRadius; |
| CoverageGeom fCoverageGeom; |
| |
| typedef GM INHERITED; |
| }; |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| DEF_GM(return new RevealGM;) |
| } |