diff --git a/gm/dashcubics.cpp b/gm/dashcubics.cpp index 1d80d75b4a..bfff3f3658 100644 --- a/gm/dashcubics.cpp +++ b/gm/dashcubics.cpp @@ -6,10 +6,13 @@ */ #include "gm.h" +#include "SkAnimTimer.h" #include "SkCanvas.h" +#include "SkDashPathEffect.h" #include "SkPath.h" #include "SkParsePath.h" -#include "SkDashPathEffect.h" +#include "SkTArray.h" +#include "SkTrimPathEffect.h" /* * Inspired by http://code.google.com/p/chromium/issues/detail?id=112145 @@ -59,41 +62,104 @@ DEF_SIMPLE_GM(dashcubics, canvas, 865, 750) { } } -#include "SkTrimPathEffect.h" class TrimGM : public skiagm::GM { public: - TrimGM() {} + TrimGM() { + SkAssertResult(SkParsePath::FromSVGString( + "M 0,100 C 10, 50 190, 50 200,100" + "M 200,100 C 210,150 390,150 400,100" + "M 400,100 C 390, 50 210, 50 200,100" + "M 200,100 C 190,150 10,150 0,100", + &fPaths.push_back())); + + SkAssertResult(SkParsePath::FromSVGString( + "M 0, 75 L 200, 75" + "M 200, 91 L 200, 91" + "M 200,108 L 200,108" + "M 200,125 L 400,125", + &fPaths.push_back())); + + SkAssertResult(SkParsePath::FromSVGString( + "M 0,100 L 50, 50" + "M 50, 50 L 150,150" + "M 150,150 L 250, 50" + "M 250, 50 L 350,150" + "M 350,150 L 400,100", + &fPaths.push_back())); + + } protected: SkString onShortName() override { return SkString("trimpatheffect"); } - SkISize onISize() override { return SkISize::Make(1240, 390); } + SkISize onISize() override { + return SkISize::Make(1400, 1000); + } void onDraw(SkCanvas* canvas) override { - SkPaint paint; - paint.setPathEffect(SkTrimPathEffect::Make(0.25 + fOffset, 0.75)); - paint.setStyle(SkPaint::kStroke_Style); - paint.setAntiAlias(true); - paint.setStrokeWidth(10); + static constexpr SkSize kCellSize = { 440, 150 }; + static constexpr SkScalar kOffsets[][2] = { + { -0.33f, -0.66f }, + { 0 , 1 }, + { 0 , 0.25f}, + { 0.25f, 0.75f}, + { 0.75f, 1 }, + { 1 , 0.75f}, + }; - SkPath path; - path.moveTo(50, 300); - path.cubicTo(100, 50, 150, 550, 200, 300); + SkPaint hairlinePaint; + hairlinePaint.setAntiAlias(true); + hairlinePaint.setStyle(SkPaint::kStroke_Style); + hairlinePaint.setStrokeCap(SkPaint::kRound_Cap); + hairlinePaint.setStrokeWidth(2); + SkPaint normalPaint = hairlinePaint; + normalPaint.setStrokeWidth(10); + normalPaint.setColor(0x8000ff00); + SkPaint invertedPaint = normalPaint; + invertedPaint.setColor(0x80ff0000); - paint.setColor(0xFF888888); - canvas->drawPath(path, paint); - paint.setPathEffect(nullptr); - paint.setStrokeWidth(0); - paint.setColor(0xFF000000); - canvas->drawPath(path, paint); + for (const auto& offset : kOffsets) { + auto start = offset[0] + fOffset, + stop = offset[1] + fOffset; + + auto normalMode = SkTrimPathEffect::Mode::kNormal, + invertedMode = SkTrimPathEffect::Mode::kInverted; + if (fOffset) { + start -= SkScalarFloorToScalar(start); + stop -= SkScalarFloorToScalar(stop); + if (start > stop) { + SkTSwap(start, stop); + SkTSwap(normalMode, invertedMode); + } + } + + normalPaint.setPathEffect(SkTrimPathEffect::Make(start, stop, normalMode)); + invertedPaint.setPathEffect(SkTrimPathEffect::Make(start, stop, invertedMode)); + + { + SkAutoCanvasRestore acr(canvas, true); + for (const auto& path : fPaths) { + canvas->drawPath(path, normalPaint); + canvas->drawPath(path, invertedPaint); + canvas->drawPath(path, hairlinePaint); + canvas->translate(kCellSize.width(), 0); + } + } + + canvas->translate(0, kCellSize.height()); + } } - bool onAnimate(const SkAnimTimer&) override { - // fOffset += 1; + bool onAnimate(const SkAnimTimer& t) override { + fOffset = t.msec() / 2000.0f; + fOffset -= floorf(fOffset); return true; } + private: - SkScalar fOffset = 0; + SkTArray fPaths; + SkScalar fOffset = 0; + typedef skiagm::GM INHERITED; }; DEF_GM(return new TrimGM;) diff --git a/include/effects/SkTrimPathEffect.h b/include/effects/SkTrimPathEffect.h index d40c9e2bbf..e96b94f83e 100644 --- a/include/effects/SkTrimPathEffect.h +++ b/include/effects/SkTrimPathEffect.h @@ -12,6 +12,11 @@ class SK_API SkTrimPathEffect { public: + enum class Mode { + kNormal, // return the subset path [start,stop] + kInverted, // return the complement/subset paths [0,start] + [stop,1] + }; + /** * Take start and stop "t" values (values between 0...1), and return a path that is that * subset of the original path. @@ -26,11 +31,11 @@ public: * startT and stopT must be 0..1 inclusive. If they are outside of that interval, they will * be pinned to the nearest legal value. If either is NaN, null will be returned. * - * Note: if startT < stopT, this will return one (logical) segment (even if it is spread - * across multiple contours). If startT > stopT, then this will return 2 logical + * Note: for Mode::kNormal, this will return one (logical) segment (even if it is spread + * across multiple contours). For Mode::kInverted, this will return 2 logical * segments: 0...stopT and startT...1 */ - static sk_sp Make(SkScalar startT, SkScalar stopT); + static sk_sp Make(SkScalar startT, SkScalar stopT, Mode = Mode::kNormal); }; #endif diff --git a/src/effects/SkTrimPE.h b/src/effects/SkTrimPE.h index 2cd39c4793..fde3292a67 100644 --- a/src/effects/SkTrimPE.h +++ b/src/effects/SkTrimPE.h @@ -10,9 +10,11 @@ #include "SkPathEffect.h" +#include "SkTrimPathEffect.h" + class SkTrimPE : public SkPathEffect { public: - SkTrimPE(SkScalar startT, SkScalar stopT); + SkTrimPE(SkScalar startT, SkScalar stopT, SkTrimPathEffect::Mode); bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*) const override; @@ -23,8 +25,9 @@ protected: void flatten(SkWriteBuffer&) const override; private: - const SkScalar fStartT; - const SkScalar fStopT; + const SkScalar fStartT, + fStopT; + const SkTrimPathEffect::Mode fMode; typedef SkPathEffect INHERITED; }; diff --git a/src/effects/SkTrimPathEffect.cpp b/src/effects/SkTrimPathEffect.cpp index beaa36e17d..01e4a1c579 100644 --- a/src/effects/SkTrimPathEffect.cpp +++ b/src/effects/SkTrimPathEffect.cpp @@ -11,35 +11,90 @@ #include "SkReadBuffer.h" #include "SkWriteBuffer.h" -SkTrimPE::SkTrimPE(SkScalar startT, SkScalar stopT) : fStartT(startT), fStopT(stopT) { - SkASSERT(startT >= 0 && startT <= 1); - SkASSERT(stopT >= 0 && stopT <= 1); - SkASSERT(startT != stopT); -} +namespace { + +class Segmentator : public SkNoncopyable { +public: + Segmentator(const SkPath& src, SkPath* dst) + : fMeasure(src, false) + , fDst(dst) {} + + void add(SkScalar start, SkScalar stop) { + SkASSERT(start < stop); + + // TODO: we appear to skip zero-length contours. + do { + const auto nextOffset = fCurrentSegmentOffset + fMeasure.getLength(); + + if (start < nextOffset) { + fMeasure.getSegment(start - fCurrentSegmentOffset, + stop - fCurrentSegmentOffset, + fDst, true); + + if (stop < nextOffset) + break; + } + + fCurrentSegmentOffset = nextOffset; + } while (fMeasure.nextContour()); + } + +private: + SkPathMeasure fMeasure; + SkPath* fDst; + + SkScalar fCurrentSegmentOffset = 0; + + using INHERITED = SkNoncopyable; +}; + +} // namespace + +SkTrimPE::SkTrimPE(SkScalar startT, SkScalar stopT, SkTrimPathEffect::Mode mode) + : fStartT(startT), fStopT(stopT), fMode(mode) {} bool SkTrimPE::filterPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec, const SkRect* cullRect) const { - SkPathMeasure meas(src, false); - SkScalar length = meas.getLength(); - - if (fStartT < fStopT) { - meas.getSegment(fStartT * length, fStopT * length, dst, true); - } else { - meas.getSegment(0, fStopT * length, dst, true); - meas.getSegment(fStartT * length, length, dst, true); + if (fStartT >= fStopT) { + SkASSERT(fMode == SkTrimPathEffect::Mode::kNormal); + return true; } + + // First pass: compute the total len. + SkScalar len = 0; + SkPathMeasure meas(src, false); + do { + len += meas.getLength(); + } while (meas.nextContour()); + + const auto arcStart = len * fStartT, + arcStop = len * fStopT; + + // Second pass: actually add segments. + Segmentator segmentator(src, dst); + if (fMode == SkTrimPathEffect::Mode::kNormal) { + if (arcStart < arcStop) segmentator.add(arcStart, arcStop); + } else { + if (0 < arcStart) segmentator.add(0, arcStart); + if (arcStop < len) segmentator.add(arcStop, len); + } + return true; } void SkTrimPE::flatten(SkWriteBuffer& buffer) const { buffer.writeScalar(fStartT); buffer.writeScalar(fStopT); + buffer.writeUInt(static_cast(fMode)); } sk_sp SkTrimPE::CreateProc(SkReadBuffer& buffer) { - SkScalar start = buffer.readScalar(); - SkScalar stop = buffer.readScalar(); - return SkTrimPathEffect::Make(start, stop); + const auto start = buffer.readScalar(), + stop = buffer.readScalar(); + const auto mode = buffer.readUInt(); + + return SkTrimPathEffect::Make(start, stop, + (mode & 1) ? SkTrimPathEffect::Mode::kInverted : SkTrimPathEffect::Mode::kNormal); } #ifndef SK_IGNORE_TO_STRING @@ -50,14 +105,21 @@ void SkTrimPE::toString(SkString* str) const { ////////////////////////////////////////////////////////////////////////////////////////////////// -sk_sp SkTrimPathEffect::Make(SkScalar startT, SkScalar stopT) { +sk_sp SkTrimPathEffect::Make(SkScalar startT, SkScalar stopT, Mode mode) { if (!SkScalarsAreFinite(startT, stopT)) { return nullptr; } - startT = SkTPin(startT, 0.f, 1.f); - stopT = SkTPin(stopT, 0.f, 1.f); - if (startT == stopT) { + + if (startT <= 0 && stopT >= 1 && mode == Mode::kNormal) { return nullptr; } - return sk_sp(new SkTrimPE(startT, stopT)); + + startT = SkTPin(startT, 0.f, 1.f); + stopT = SkTPin(stopT, 0.f, 1.f); + + if (startT >= stopT && mode == Mode::kInverted) { + return nullptr; + } + + return sk_sp(new SkTrimPE(startT, stopT, mode)); }