/* * 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 "SkAnimTimer.h" #include "SkPath.h" #include "SkDashPathEffect.h" int dash1[] = { 1, 1 }; int dash2[] = { 1, 3 }; int dash3[] = { 1, 1, 3, 3 }; int dash4[] = { 1, 3, 2, 4 }; struct DashExample { int* pattern; int length; } dashExamples[] = { { dash1, SK_ARRAY_COUNT(dash1) }, { dash2, SK_ARRAY_COUNT(dash2) }, { dash3, SK_ARRAY_COUNT(dash3) }, { dash4, SK_ARRAY_COUNT(dash4) } }; class DashCircleGM : public skiagm::GM { public: DashCircleGM() : fRotation(0) { } protected: SkString onShortName() override { return SkString("dashcircle"); } SkISize onISize() override { return SkISize::Make(900, 1200); } void onDraw(SkCanvas* canvas) override { SkPaint refPaint; refPaint.setAntiAlias(true); refPaint.setColor(0xFFbf3f7f); refPaint.setStyle(SkPaint::kStroke_Style); refPaint.setStrokeWidth(1); const SkScalar radius = 125; SkRect oval = SkRect::MakeLTRB(-radius - 20, -radius - 20, radius + 20, radius + 20); SkPath circle; circle.addCircle(0, 0, radius); SkScalar circumference = radius * SK_ScalarPI * 2; int wedges[] = { 6, 12, 36 }; canvas->translate(radius+20, radius+20); for (int wedge : wedges) { SkScalar arcLength = 360.f / wedge; canvas->save(); for (const DashExample& dashExample : dashExamples) { SkPath refPath; int dashUnits = 0; for (int index = 0; index < dashExample.length; ++index) { dashUnits += dashExample.pattern[index]; } SkScalar unitLength = arcLength / dashUnits; SkScalar angle = 0; for (int index = 0; index < wedge; ++index) { for (int i2 = 0; i2 < dashExample.length; i2 += 2) { SkScalar span = dashExample.pattern[i2] * unitLength; refPath.moveTo(0, 0); refPath.arcTo(oval, angle, span, false); refPath.close(); angle += span + (dashExample.pattern[i2 + 1]) * unitLength; } } canvas->save(); canvas->rotate(fRotation); canvas->drawPath(refPath, refPaint); canvas->restore(); SkPaint p; p.setAntiAlias(true); p.setStyle(SkPaint::kStroke_Style); p.setStrokeWidth(10); SkScalar intervals[4]; int intervalCount = dashExample.length; SkScalar dashLength = circumference / wedge / dashUnits; for (int index = 0; index < dashExample.length; ++index) { intervals[index] = dashExample.pattern[index] * dashLength; } p.setPathEffect(SkDashPathEffect::Make(intervals, intervalCount, 0)); canvas->save(); canvas->rotate(fRotation); canvas->drawPath(circle, p); canvas->restore(); canvas->translate(0, radius * 2 + 50); } canvas->restore(); canvas->translate(radius * 2 + 50, 0); } } bool onAnimate(const SkAnimTimer& timer) override { constexpr SkScalar kDesiredDurationSecs = 100.0f; fRotation = timer.scaled(360.0f/kDesiredDurationSecs, 360.0f); return true; } private: SkScalar fRotation; typedef GM INHERITED; }; DEF_GM(return new DashCircleGM; ) class DashCircle2GM : public skiagm::GM { public: DashCircle2GM() {} protected: SkString onShortName() override { return SkString("dashcircle2"); } SkISize onISize() override { return SkISize::Make(635, 900); } void onDraw(SkCanvas* canvas) override { // These intervals are defined relative to tau. static constexpr SkScalar kIntervals[][2]{ {0.333f, 0.333f}, {0.015f, 0.015f}, {0.01f , 0.09f }, {0.097f, 0.003f}, {0.02f , 0.04f }, {0.1f , 0.2f }, {0.25f , 0.25f }, {0.6f , 0.7f }, // adds to > 1 {1.2f , 0.8f }, // on is > 1 {0.1f , 1.1f }, // off is > 1*/ }; static constexpr int kN = SK_ARRAY_COUNT(kIntervals); static constexpr SkScalar kRadius = 20.f; static constexpr SkScalar kStrokeWidth = 15.f; static constexpr SkScalar kPad = 5.f; static constexpr SkRect kCircle = {-kRadius, -kRadius, kRadius, kRadius}; static constexpr SkScalar kThinRadius = kRadius * 1.5; static constexpr SkRect kThinCircle = {-kThinRadius, -kThinRadius, kThinRadius, kThinRadius}; static constexpr SkScalar kThinStrokeWidth = 0.4f; sk_sp<SkPathEffect> deffects[SK_ARRAY_COUNT(kIntervals)]; sk_sp<SkPathEffect> thinDEffects[SK_ARRAY_COUNT(kIntervals)]; for (int i = 0; i < kN; ++i) { static constexpr SkScalar kTau = 2 * SK_ScalarPI; static constexpr SkScalar kCircumference = kRadius * kTau; SkScalar scaledIntervals[2] = {kCircumference * kIntervals[i][0], kCircumference * kIntervals[i][1]}; deffects[i] = SkDashPathEffect::Make( scaledIntervals, 2, kCircumference * fPhaseDegrees * kTau / 360.f); static constexpr SkScalar kThinCircumference = kThinRadius * kTau; scaledIntervals[0] = kThinCircumference * kIntervals[i][0]; scaledIntervals[1] = kThinCircumference * kIntervals[i][1]; thinDEffects[i] = SkDashPathEffect::Make( scaledIntervals, 2, kThinCircumference * fPhaseDegrees * kTau / 360.f); } SkMatrix rotate; rotate.setRotate(25.f); static const SkMatrix kMatrices[]{ SkMatrix::I(), SkMatrix::MakeScale(1.2f), SkMatrix::MakeAll(1, 0, 0, 0, -1, 0, 0, 0, 1), // y flipper SkMatrix::MakeAll(-1, 0, 0, 0, 1, 0, 0, 0, 1), // x flipper SkMatrix::MakeScale(0.7f), rotate, SkMatrix::Concat( SkMatrix::Concat(SkMatrix::MakeAll(-1, 0, 0, 0, 1, 0, 0, 0, 1), rotate), rotate) }; SkPaint paint; paint.setAntiAlias(true); paint.setStrokeWidth(kStrokeWidth); paint.setStyle(SkPaint::kStroke_Style); // Compute the union of bounds of all of our test cases. SkRect bounds = SkRect::MakeEmpty(); static const SkRect kBounds = kThinCircle.makeOutset(kThinStrokeWidth / 2.f, kThinStrokeWidth / 2.f); for (const auto& m : kMatrices) { SkRect devBounds; m.mapRect(&devBounds, kBounds); bounds.join(devBounds); } canvas->save(); canvas->translate(-bounds.fLeft + kPad, -bounds.fTop + kPad); for (size_t i = 0; i < SK_ARRAY_COUNT(deffects); ++i) { canvas->save(); for (const auto& m : kMatrices) { canvas->save(); canvas->concat(m); paint.setPathEffect(deffects[i]); paint.setStrokeWidth(kStrokeWidth); canvas->drawOval(kCircle, paint); paint.setPathEffect(thinDEffects[i]); paint.setStrokeWidth(kThinStrokeWidth); canvas->drawOval(kThinCircle, paint); canvas->restore(); canvas->translate(bounds.width() + kPad, 0); } canvas->restore(); canvas->translate(0, bounds.height() + kPad); } canvas->restore(); } protected: bool onAnimate(const SkAnimTimer& timer) override { fPhaseDegrees = timer.secs(); return true; } // Init with a non-zero phase for when run as a non-animating GM. SkScalar fPhaseDegrees = 12.f; }; DEF_GM(return new DashCircle2GM;)