Extend SkTrimPathEffect semantics
Add support for multiple contours, and an explicit "inverted" mode. Bug: skia: Change-Id: Iafadbbe9d4692f2467a4ef8585f7fcd9cee9566a Reviewed-on: https://skia-review.googlesource.com/113270 Reviewed-by: Mike Reed <reed@google.com> Commit-Queue: Florin Malita <fmalita@chromium.org>
This commit is contained in:
parent
8e03f6930f
commit
827af667bb
@ -6,10 +6,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "gm.h"
|
#include "gm.h"
|
||||||
|
#include "SkAnimTimer.h"
|
||||||
#include "SkCanvas.h"
|
#include "SkCanvas.h"
|
||||||
|
#include "SkDashPathEffect.h"
|
||||||
#include "SkPath.h"
|
#include "SkPath.h"
|
||||||
#include "SkParsePath.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
|
* 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 {
|
class TrimGM : public skiagm::GM {
|
||||||
public:
|
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:
|
protected:
|
||||||
SkString onShortName() override { return SkString("trimpatheffect"); }
|
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 {
|
void onDraw(SkCanvas* canvas) override {
|
||||||
SkPaint paint;
|
static constexpr SkSize kCellSize = { 440, 150 };
|
||||||
paint.setPathEffect(SkTrimPathEffect::Make(0.25 + fOffset, 0.75));
|
static constexpr SkScalar kOffsets[][2] = {
|
||||||
paint.setStyle(SkPaint::kStroke_Style);
|
{ -0.33f, -0.66f },
|
||||||
paint.setAntiAlias(true);
|
{ 0 , 1 },
|
||||||
paint.setStrokeWidth(10);
|
{ 0 , 0.25f},
|
||||||
|
{ 0.25f, 0.75f},
|
||||||
|
{ 0.75f, 1 },
|
||||||
|
{ 1 , 0.75f},
|
||||||
|
};
|
||||||
|
|
||||||
SkPath path;
|
SkPaint hairlinePaint;
|
||||||
path.moveTo(50, 300);
|
hairlinePaint.setAntiAlias(true);
|
||||||
path.cubicTo(100, 50, 150, 550, 200, 300);
|
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);
|
for (const auto& offset : kOffsets) {
|
||||||
canvas->drawPath(path, paint);
|
auto start = offset[0] + fOffset,
|
||||||
paint.setPathEffect(nullptr);
|
stop = offset[1] + fOffset;
|
||||||
paint.setStrokeWidth(0);
|
|
||||||
paint.setColor(0xFF000000);
|
auto normalMode = SkTrimPathEffect::Mode::kNormal,
|
||||||
canvas->drawPath(path, paint);
|
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 {
|
bool onAnimate(const SkAnimTimer& t) override {
|
||||||
// fOffset += 1;
|
fOffset = t.msec() / 2000.0f;
|
||||||
|
fOffset -= floorf(fOffset);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SkScalar fOffset = 0;
|
SkTArray<SkPath> fPaths;
|
||||||
|
SkScalar fOffset = 0;
|
||||||
|
|
||||||
typedef skiagm::GM INHERITED;
|
typedef skiagm::GM INHERITED;
|
||||||
};
|
};
|
||||||
DEF_GM(return new TrimGM;)
|
DEF_GM(return new TrimGM;)
|
||||||
|
@ -12,6 +12,11 @@
|
|||||||
|
|
||||||
class SK_API SkTrimPathEffect {
|
class SK_API SkTrimPathEffect {
|
||||||
public:
|
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
|
* Take start and stop "t" values (values between 0...1), and return a path that is that
|
||||||
* subset of the original path.
|
* 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
|
* 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.
|
* 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
|
* Note: for Mode::kNormal, this will return one (logical) segment (even if it is spread
|
||||||
* across multiple contours). If startT > stopT, then this will return 2 logical
|
* across multiple contours). For Mode::kInverted, this will return 2 logical
|
||||||
* segments: 0...stopT and startT...1
|
* segments: 0...stopT and startT...1
|
||||||
*/
|
*/
|
||||||
static sk_sp<SkPathEffect> Make(SkScalar startT, SkScalar stopT);
|
static sk_sp<SkPathEffect> Make(SkScalar startT, SkScalar stopT, Mode = Mode::kNormal);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -10,9 +10,11 @@
|
|||||||
|
|
||||||
#include "SkPathEffect.h"
|
#include "SkPathEffect.h"
|
||||||
|
|
||||||
|
#include "SkTrimPathEffect.h"
|
||||||
|
|
||||||
class SkTrimPE : public SkPathEffect {
|
class SkTrimPE : public SkPathEffect {
|
||||||
public:
|
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;
|
bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*) const override;
|
||||||
|
|
||||||
@ -23,8 +25,9 @@ protected:
|
|||||||
void flatten(SkWriteBuffer&) const override;
|
void flatten(SkWriteBuffer&) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const SkScalar fStartT;
|
const SkScalar fStartT,
|
||||||
const SkScalar fStopT;
|
fStopT;
|
||||||
|
const SkTrimPathEffect::Mode fMode;
|
||||||
|
|
||||||
typedef SkPathEffect INHERITED;
|
typedef SkPathEffect INHERITED;
|
||||||
};
|
};
|
||||||
|
@ -11,35 +11,90 @@
|
|||||||
#include "SkReadBuffer.h"
|
#include "SkReadBuffer.h"
|
||||||
#include "SkWriteBuffer.h"
|
#include "SkWriteBuffer.h"
|
||||||
|
|
||||||
SkTrimPE::SkTrimPE(SkScalar startT, SkScalar stopT) : fStartT(startT), fStopT(stopT) {
|
namespace {
|
||||||
SkASSERT(startT >= 0 && startT <= 1);
|
|
||||||
SkASSERT(stopT >= 0 && stopT <= 1);
|
class Segmentator : public SkNoncopyable {
|
||||||
SkASSERT(startT != stopT);
|
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,
|
bool SkTrimPE::filterPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec,
|
||||||
const SkRect* cullRect) const {
|
const SkRect* cullRect) const {
|
||||||
SkPathMeasure meas(src, false);
|
if (fStartT >= fStopT) {
|
||||||
SkScalar length = meas.getLength();
|
SkASSERT(fMode == SkTrimPathEffect::Mode::kNormal);
|
||||||
|
return true;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkTrimPE::flatten(SkWriteBuffer& buffer) const {
|
void SkTrimPE::flatten(SkWriteBuffer& buffer) const {
|
||||||
buffer.writeScalar(fStartT);
|
buffer.writeScalar(fStartT);
|
||||||
buffer.writeScalar(fStopT);
|
buffer.writeScalar(fStopT);
|
||||||
|
buffer.writeUInt(static_cast<uint32_t>(fMode));
|
||||||
}
|
}
|
||||||
|
|
||||||
sk_sp<SkFlattenable> SkTrimPE::CreateProc(SkReadBuffer& buffer) {
|
sk_sp<SkFlattenable> SkTrimPE::CreateProc(SkReadBuffer& buffer) {
|
||||||
SkScalar start = buffer.readScalar();
|
const auto start = buffer.readScalar(),
|
||||||
SkScalar stop = buffer.readScalar();
|
stop = buffer.readScalar();
|
||||||
return SkTrimPathEffect::Make(start, stop);
|
const auto mode = buffer.readUInt();
|
||||||
|
|
||||||
|
return SkTrimPathEffect::Make(start, stop,
|
||||||
|
(mode & 1) ? SkTrimPathEffect::Mode::kInverted : SkTrimPathEffect::Mode::kNormal);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef SK_IGNORE_TO_STRING
|
#ifndef SK_IGNORE_TO_STRING
|
||||||
@ -50,14 +105,21 @@ void SkTrimPE::toString(SkString* str) const {
|
|||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
sk_sp<SkPathEffect> SkTrimPathEffect::Make(SkScalar startT, SkScalar stopT) {
|
sk_sp<SkPathEffect> SkTrimPathEffect::Make(SkScalar startT, SkScalar stopT, Mode mode) {
|
||||||
if (!SkScalarsAreFinite(startT, stopT)) {
|
if (!SkScalarsAreFinite(startT, stopT)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
startT = SkTPin(startT, 0.f, 1.f);
|
|
||||||
stopT = SkTPin(stopT, 0.f, 1.f);
|
if (startT <= 0 && stopT >= 1 && mode == Mode::kNormal) {
|
||||||
if (startT == stopT) {
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return sk_sp<SkPathEffect>(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<SkPathEffect>(new SkTrimPE(startT, stopT, mode));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user