diff --git a/gm/megalooper.cpp b/gm/megalooper.cpp new file mode 100644 index 0000000000..8f5932caf9 --- /dev/null +++ b/gm/megalooper.cpp @@ -0,0 +1,250 @@ +/* + * 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 "SkCanvas.h" +#include "SkBlurMaskFilter.h" +#include "SkLayerDrawLooper.h" +#include "SkColorFilter.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 const int kWidth = 800; + static const int kHeight = 800; + static const int kHalfOuterClipSize = 100; + static const int kHalfSquareSize = 50; + static const 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, SkRegion::kIntersect_Op); + canvas->clipRect(innerClip, SkRegion::kDifference_Op); + + SkPaint paint; + paint.setAntiAlias(true); + paint.setMaskFilter(createBlur())->unref(); + + 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(); + } + + SkMaskFilter* createBlur() { + static const SkScalar kBlurRadius = 25.0f; + + return SkBlurMaskFilter::Create(kBlurRadius, + SkBlurMaskFilter::kNormal_BlurStyle, + SkBlurMaskFilter::kHighQuality_BlurFlag); + } + + // 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, SkRegion::kIntersect_Op); + canvas->clipRect(rect, SkRegion::kDifference_Op); + + // 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 + SkLayerDrawLooper* create1Looper(SkScalar xOff, SkScalar yOff, SkColor color) { + SkLayerDrawLooper* looper = new SkLayerDrawLooper; + SkLayerDrawLooper::LayerInfo info; + + info.fFlagsMask = 0; + info.fPaintBits = SkLayerDrawLooper::kColorFilter_Bit | + SkLayerDrawLooper::kMaskFilter_Bit; + info.fColorMode = SkXfermode::kSrc_Mode; + info.fOffset.set(xOff, yOff); + info.fPostTranslate = false; + + SkPaint* paint = looper->addLayer(info); + + paint->setMaskFilter(this->createBlur())->unref(); + + SkColorFilter* cf = SkColorFilter::CreateModeFilter(color, SkXfermode::kSrcIn_Mode); + paint->setColorFilter(cf)->unref(); + + return looper; + } + + 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, SkRegion::kIntersect_Op); + canvas->clipRect(rect, SkRegion::kDifference_Op); + + rect.offset(SkIntToScalar(kOffsetToOutsideClip+kHalfSquareSize), 0); + canvas->drawRect(rect, paint); + canvas->restore(); + } + + // Create a 4-tier draw looper + SkLayerDrawLooper* create4Looper(SkScalar xOff, SkScalar yOff) { + SkLayerDrawLooper* looper = new SkLayerDrawLooper; + SkLayerDrawLooper::LayerInfo info; + + info.fFlagsMask = 0; + info.fPaintBits = SkLayerDrawLooper::kColorFilter_Bit | + SkLayerDrawLooper::kMaskFilter_Bit; + info.fColorMode = SkXfermode::kSrc_Mode; + info.fPostTranslate = false; + + SkPaint* paint; + + for (int i = 3; i >= 0; --i) { + info.fOffset.set(xOff+gBlurOffsets[i].fX, yOff+gBlurOffsets[i].fY); + paint = looper->addLayer(info); + + paint->setMaskFilter(this->createBlur())->unref(); + + SkColorFilter* cf = SkColorFilter::CreateModeFilter(gColors[i], SkXfermode::kSrcIn_Mode); + paint->setColorFilter(cf)->unref(); + } + + return looper; + } + + 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); ) diff --git a/gyp/gmslides.gypi b/gyp/gmslides.gypi index 1de9b28fd3..49302451b0 100644 --- a/gyp/gmslides.gypi +++ b/gyp/gmslides.gypi @@ -62,6 +62,7 @@ '../gm/lcdtext.cpp', '../gm/linepaths.cpp', '../gm/matrixconvolution.cpp', + '../gm/megalooper.cpp', '../gm/mixedxfermodes.cpp', '../gm/modecolorfilters.cpp', '../gm/morphology.cpp',