skia2/gm/dashcircle.cpp
Brian Salomon 62e4f3daf7 Analytic dashing of circles with single on/off intervals and butt caps.
Change-Id: If19ac52cb78af57572a102cec0084f5b6c037680
Reviewed-on: https://skia-review.googlesource.com/121882
Auto-Submit: Brian Salomon <bsalomon@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
2018-04-20 18:45:35 +00:00

228 lines
8.2 KiB
C++

/*
* 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;)