| /* | 
 |  * Copyright 2013 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 "SkBlurMask.h" | 
 | #include "SkCanvas.h" | 
 | #include "SkColorFilter.h" | 
 | #include "SkLayerDrawLooper.h" | 
 | #include "SkMaskFilter.h" | 
 |  | 
 | // This GM tests 3 different ways of drawing four shadows around a square: | 
 | //      just using 4 blurred rects | 
 | //      using 4 1-level draw loopers | 
 | //      using 1 4-level draw looper | 
 | // They all produce exactly the same pixels | 
 | class MegaLooperGM : public skiagm::GM { | 
 | public: | 
 |     // The types define "<# of loopers> x <# of stages per looper>" | 
 |     enum Type { | 
 |         k0x0_Type,  // draw without loopers at all | 
 |         k4x1_Type,  // a looper for each shadow | 
 |         k1x4_Type,  // all four shadows in one looper | 
 |     }; | 
 |  | 
 |     MegaLooperGM(Type type) : fType(type) {} | 
 |  | 
 | protected: | 
 |     virtual SkString onShortName() { | 
 |         switch (fType) { | 
 |         case k0x0_Type: | 
 |             return SkString("megalooper_0x0"); | 
 |             break; | 
 |         case k4x1_Type: | 
 |             return SkString("megalooper_4x1"); | 
 |             break; | 
 |         case k1x4_Type:     // fall through | 
 |         default: | 
 |             return SkString("megalooper_1x4"); | 
 |             break; | 
 |         } | 
 |     } | 
 |  | 
 |     virtual SkISize onISize() { | 
 |         return SkISize::Make(kWidth, kHeight); | 
 |     } | 
 |  | 
 |     virtual void onDraw(SkCanvas* canvas) { | 
 |         for (int y = 100; y < kHeight; y += 200) { | 
 |             for (int x = 100; x < kWidth; x += 200) { | 
 |                 switch (fType) { | 
 |                     case k0x0_Type: | 
 |                         draw0x0(canvas, SkIntToScalar(x), SkIntToScalar(y)); | 
 |                         break; | 
 |                     case k4x1_Type: | 
 |                         draw4x1(canvas, SkIntToScalar(x), SkIntToScalar(y)); | 
 |                         break; | 
 |                     case k1x4_Type:     // fall through | 
 |                     default: | 
 |                         draw1x4(canvas, SkIntToScalar(x), SkIntToScalar(y)); | 
 |                         break; | 
 |                 } | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 | private: | 
 |     static constexpr int kWidth = 800; | 
 |     static constexpr int kHeight = 800; | 
 |     static constexpr int kHalfOuterClipSize = 100; | 
 |     static constexpr int kHalfSquareSize = 50; | 
 |     static constexpr int kOffsetToOutsideClip = kHalfSquareSize + kHalfOuterClipSize + 1; | 
 |  | 
 |     static const SkPoint gBlurOffsets[4]; | 
 |     static const SkColor gColors[4]; | 
 |     Type fType; | 
 |  | 
 |     // Just draw a blurred rect at each of the four corners of a square (centered at x,y). | 
 |     // Use two clips to define a rectori where we want pixels to appear. | 
 |     void draw0x0(SkCanvas* canvas, SkScalar x, SkScalar y) { | 
 |         SkRect innerClip = { -kHalfSquareSize, -kHalfSquareSize, kHalfSquareSize, kHalfSquareSize }; | 
 |         innerClip.offset(x, y); | 
 |  | 
 |         SkRect outerClip = { | 
 |             -kHalfOuterClipSize-kHalfSquareSize, -kHalfOuterClipSize-kHalfSquareSize, | 
 |              kHalfOuterClipSize+kHalfSquareSize,  kHalfOuterClipSize+kHalfSquareSize | 
 |         }; | 
 |         outerClip.offset(x, y); | 
 |  | 
 |         canvas->save(); | 
 |         canvas->clipRect(outerClip, kIntersect_SkClipOp); | 
 |         canvas->clipRect(innerClip, kDifference_SkClipOp); | 
 |  | 
 |         SkPaint paint; | 
 |         paint.setAntiAlias(true); | 
 |         paint.setMaskFilter(MakeBlur()); | 
 |  | 
 |         for (int i = 0; i < 4; ++i) { | 
 |             paint.setColor(gColors[i]); | 
 |  | 
 |             SkRect rect = { -kHalfSquareSize, -kHalfSquareSize, kHalfSquareSize, kHalfSquareSize }; | 
 |             rect.offset(gBlurOffsets[i]); | 
 |             rect.offset(x, y); | 
 |             canvas->drawRect(rect, paint); | 
 |         } | 
 |  | 
 |         canvas->restore(); | 
 |     } | 
 |  | 
 |     static sk_sp<SkMaskFilter> MakeBlur() { | 
 |         const SkScalar kBlurSigma = SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(25)); | 
 |  | 
 |         return SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, kBlurSigma); | 
 |     } | 
 |  | 
 |     // This draws 4 blurred shadows around a single square (centered at x, y). | 
 |     // Each blur is offset +/- half the square's side in x & y from the original | 
 |     // (so each blurred rect is centered at one of the corners of the original). | 
 |     // For each blur a large outer clip is centered around the blurred rect | 
 |     // while a difference clip stays at the location of the original rect. | 
 |     // Each blurred rect is drawn with a draw looper where the original (non- | 
 |     // blurred rect) is offset to reside outside of the large outer clip (so | 
 |     // it never appears) but the offset in the draw looper is used to translate | 
 |     // the blurred version back into the clip. | 
 |     void draw4x1(SkCanvas* canvas, SkScalar x, SkScalar y) { | 
 |  | 
 |         for (int i = 0; i < 4; ++i) { | 
 |             SkPaint loopPaint; | 
 |  | 
 |             loopPaint.setLooper(create1Looper(-kOffsetToOutsideClip, 0, gColors[i])); | 
 |             loopPaint.setAntiAlias(true); | 
 |  | 
 |             SkRect outerClip = { | 
 |                 -kHalfOuterClipSize, -kHalfOuterClipSize, | 
 |                 kHalfOuterClipSize, kHalfOuterClipSize | 
 |             }; | 
 |             outerClip.offset(x, y); | 
 |             // center it on the blurred rect | 
 |             outerClip.offset(gBlurOffsets[i]); | 
 |  | 
 |             SkRect rect = { -kHalfSquareSize, -kHalfSquareSize, kHalfSquareSize, kHalfSquareSize }; | 
 |             rect.offset(x, y); | 
 |  | 
 |             canvas->save(); | 
 |                 canvas->clipRect(outerClip, kIntersect_SkClipOp); | 
 |                 canvas->clipRect(rect, kDifference_SkClipOp); | 
 |  | 
 |                 // move the rect to where we want the blur to appear | 
 |                 rect.offset(gBlurOffsets[i]); | 
 |                 // then move it outside the clip (the blur stage of the draw | 
 |                 // looper will undo this translation) | 
 |                 rect.offset(SkIntToScalar(kOffsetToOutsideClip), 0); | 
 |  | 
 |                 canvas->drawRect(rect, loopPaint); | 
 |             canvas->restore(); | 
 |         } | 
 |     } | 
 |  | 
 |     // Create a 1-tier drawlooper | 
 |     sk_sp<SkDrawLooper> create1Looper(SkScalar xOff, SkScalar yOff, SkColor color) { | 
 |         SkLayerDrawLooper::Builder looperBuilder; | 
 |         SkLayerDrawLooper::LayerInfo info; | 
 |  | 
 |         info.fPaintBits = SkLayerDrawLooper::kColorFilter_Bit | | 
 |                           SkLayerDrawLooper::kMaskFilter_Bit; | 
 |         info.fColorMode = SkBlendMode::kSrc; | 
 |         info.fOffset.set(xOff, yOff); | 
 |         info.fPostTranslate = false; | 
 |  | 
 |         SkPaint* paint = looperBuilder.addLayer(info); | 
 |  | 
 |         paint->setMaskFilter(MakeBlur()); | 
 |  | 
 |         paint->setColorFilter(SkColorFilter::MakeModeFilter(color, SkBlendMode::kSrcIn)); | 
 |  | 
 |         return looperBuilder.detach(); | 
 |     } | 
 |  | 
 |     void draw1x4(SkCanvas* canvas, SkScalar x, SkScalar y) { | 
 |         SkRect rect = { -kHalfSquareSize, -kHalfSquareSize, kHalfSquareSize, kHalfSquareSize }; | 
 |         rect.offset(x, y); | 
 |  | 
 |         SkRect outerClip = { | 
 |             -kHalfOuterClipSize-kHalfSquareSize, -kHalfOuterClipSize-kHalfSquareSize, | 
 |              kHalfOuterClipSize+kHalfSquareSize,  kHalfOuterClipSize+kHalfSquareSize | 
 |         }; | 
 |         outerClip.offset(x, y); | 
 |  | 
 |         SkPaint paint; | 
 |         paint.setAntiAlias(true); | 
 |         paint.setLooper(create4Looper(-kOffsetToOutsideClip-kHalfSquareSize, 0)); | 
 |  | 
 |         canvas->save(); | 
 |             canvas->clipRect(outerClip, kIntersect_SkClipOp); | 
 |             canvas->clipRect(rect, kDifference_SkClipOp); | 
 |  | 
 |             rect.offset(SkIntToScalar(kOffsetToOutsideClip+kHalfSquareSize), 0); | 
 |             canvas->drawRect(rect, paint); | 
 |         canvas->restore(); | 
 |     } | 
 |  | 
 |     // Create a 4-tier draw looper | 
 |     sk_sp<SkDrawLooper> create4Looper(SkScalar xOff, SkScalar yOff) { | 
 |         SkLayerDrawLooper::Builder looperBuilder; | 
 |         SkLayerDrawLooper::LayerInfo info; | 
 |  | 
 |         info.fPaintBits = SkLayerDrawLooper::kColorFilter_Bit | | 
 |                           SkLayerDrawLooper::kMaskFilter_Bit; | 
 |         info.fColorMode = SkBlendMode::kSrc; | 
 |         info.fPostTranslate = false; | 
 |  | 
 |         SkPaint* paint; | 
 |  | 
 |         for (int i = 3; i >= 0; --i) { | 
 |             info.fOffset.set(xOff+gBlurOffsets[i].fX, yOff+gBlurOffsets[i].fY); | 
 |             paint = looperBuilder.addLayer(info); | 
 |  | 
 |             paint->setMaskFilter(MakeBlur()); | 
 |  | 
 |             paint->setColorFilter(SkColorFilter::MakeModeFilter(gColors[i], SkBlendMode::kSrcIn)); | 
 |         } | 
 |  | 
 |         return looperBuilder.detach(); | 
 |     } | 
 |  | 
 |     typedef GM INHERITED; | 
 | }; | 
 |  | 
 | const SkPoint MegaLooperGM::gBlurOffsets[4] = { | 
 |     {  kHalfSquareSize,  kHalfSquareSize }, | 
 |     { -kHalfSquareSize,  kHalfSquareSize }, | 
 |     {  kHalfSquareSize, -kHalfSquareSize }, | 
 |     { -kHalfSquareSize, -kHalfSquareSize } | 
 | }; | 
 |  | 
 | const SkColor MegaLooperGM::gColors[4] = { | 
 |     SK_ColorGREEN, SK_ColorYELLOW, SK_ColorBLUE, SK_ColorRED | 
 | }; | 
 |  | 
 | DEF_GM( return new MegaLooperGM(MegaLooperGM::k0x0_Type); ) | 
 | DEF_GM( return new MegaLooperGM(MegaLooperGM::k4x1_Type); ) | 
 | DEF_GM( return new MegaLooperGM(MegaLooperGM::k1x4_Type); ) |