/*
* 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 "GrStyle.h"
#include "SkDashPathPriv.h"
int GrStyle::KeySize(const GrStyle &style, Apply apply, uint32_t flags) {
GR_STATIC_ASSERT(sizeof(uint32_t) == sizeof(SkScalar));
int size = 0;
if (style.isDashed()) {
// One scalar for scale, one for dash phase, and one for each dash value.
size += 2 + style.dashIntervalCnt();
} else if (style.pathEffect()) {
// No key for a generic path effect.
return -1;
}
if (Apply::kPathEffectOnly == apply) {
return size;
}
if (style.strokeRec().needToApply()) {
// One for res scale, one for style/cap/join, one for miter limit, and one for width.
size += 4;
}
return size;
}
void GrStyle::WriteKey(uint32_t *key, const GrStyle &style, Apply apply, SkScalar scale,
uint32_t flags) {
SkASSERT(key);
SkASSERT(KeySize(style, apply) >= 0);
GR_STATIC_ASSERT(sizeof(uint32_t) == sizeof(SkScalar));
int i = 0;
// The scale can influence both the path effect and stroking. We want to preserve the
// property that the following two are equal:
// 1. WriteKey with apply == kPathEffectAndStrokeRec
// 2. WriteKey with apply == kPathEffectOnly followed by WriteKey of a GrStyle made
// from SkStrokeRec output by the the path effect (and no additional path effect).
// Since the scale can affect both parts of 2 we write it into the key twice.
if (style.isDashed()) {
GR_STATIC_ASSERT(sizeof(style.dashPhase()) == sizeof(uint32_t));
SkScalar phase = style.dashPhase();
memcpy(&key[i++], &scale, sizeof(SkScalar));
memcpy(&key[i++], &phase, sizeof(SkScalar));
int32_t count = style.dashIntervalCnt();
// Dash count should always be even.
SkASSERT(0 == (count & 0x1));
const SkScalar *intervals = style.dashIntervals();
int intervalByteCnt = count * sizeof(SkScalar);
memcpy(&key[i], intervals, intervalByteCnt);
i += count;
} else {
SkASSERT(!style.pathEffect());
}
if (Apply::kPathEffectAndStrokeRec == apply && style.strokeRec().needToApply()) {
memcpy(&key[i++], &scale, sizeof(SkScalar));
enum {
kStyleBits = 2,
kJoinBits = 2,
kCapBits = 32 - kStyleBits - kJoinBits,
kJoinShift = kStyleBits,
kCapShift = kJoinShift + kJoinBits,
};
GR_STATIC_ASSERT(SkStrokeRec::kStyleCount <= (1 << kStyleBits));
GR_STATIC_ASSERT(SkPaint::kJoinCount <= (1 << kJoinBits));
GR_STATIC_ASSERT(SkPaint::kCapCount <= (1 << kCapBits));
// The cap type only matters for unclosed shapes. However, a path effect could unclose
// the shape before it is stroked.
SkPaint::Cap cap = SkPaint::kDefault_Cap;
if (!(flags & kClosed_KeyFlag) || style.pathEffect()) {
cap = style.strokeRec().getCap();
}
SkScalar miter = -1.f;
SkPaint::Join join = SkPaint::kDefault_Join;
// Dashing will not insert joins but other path effects may.
if (!(flags & kNoJoins_KeyFlag) || style.hasNonDashPathEffect()) {
join = style.strokeRec().getJoin();
// Miter limit only affects miter joins
if (SkPaint::kMiter_Join == join) {
miter = style.strokeRec().getMiter();
}
}
key[i++] = style.strokeRec().getStyle() |
join << kJoinShift |
cap << kCapShift;
memcpy(&key[i++], &miter, sizeof(miter));
SkScalar width = style.strokeRec().getWidth();
memcpy(&key[i++], &width, sizeof(width));
}
SkASSERT(KeySize(style, apply) == i);
}
void GrStyle::initPathEffect(sk_sp<SkPathEffect> pe) {
SkASSERT(!fPathEffect);
SkASSERT(SkPathEffect::kNone_DashType == fDashInfo.fType);
SkASSERT(0 == fDashInfo.fIntervals.count());
if (!pe) {
return;
}
SkPathEffect::DashInfo info;
if (SkPathEffect::kDash_DashType == pe->asADash(&info)) {
SkStrokeRec::Style recStyle = fStrokeRec.getStyle();
if (recStyle != SkStrokeRec::kFill_Style && recStyle != SkStrokeRec::kStrokeAndFill_Style) {
fDashInfo.fType = SkPathEffect::kDash_DashType;
fDashInfo.fIntervals.reset(info.fCount);
fDashInfo.fPhase = info.fPhase;
info.fIntervals = fDashInfo.fIntervals.get();
pe->asADash(&info);
fPathEffect = std::move(pe);
}
} else {
fPathEffect = std::move(pe);
}
}
bool GrStyle::applyPathEffect(SkPath* dst, SkStrokeRec* strokeRec, const SkPath& src) const {
if (!fPathEffect) {
return false;
}
if (SkPathEffect::kDash_DashType == fDashInfo.fType) {
// We apply the dash ourselves here rather than using the path effect. This is so that
// we can control whether the dasher applies the strokeRec for special cases. Our keying
// depends on the strokeRec being applied separately.
SkScalar phase = fDashInfo.fPhase;
const SkScalar* intervals = fDashInfo.fIntervals.get();
int intervalCnt = fDashInfo.fIntervals.count();
SkScalar initialLength;
int initialIndex;
SkScalar intervalLength;
SkDashPath::CalcDashParameters(phase, intervals, intervalCnt, &initialLength,
&initialIndex, &intervalLength);
if (!SkDashPath::InternalFilter(dst, src, strokeRec,
nullptr, intervals, intervalCnt,
initialLength, initialIndex, intervalLength,
SkDashPath::StrokeRecApplication::kDisallow)) {
return false;
}
} else if (!fPathEffect->filterPath(dst, src, strokeRec, nullptr)) {
return false;
}
dst->setIsVolatile(true);
return true;
}
bool GrStyle::applyPathEffectToPath(SkPath *dst, SkStrokeRec *remainingStroke,
const SkPath &src, SkScalar resScale) const {
SkASSERT(dst);
SkStrokeRec strokeRec = fStrokeRec;
strokeRec.setResScale(resScale);
if (!this->applyPathEffect(dst, &strokeRec, src)) {
return false;
}
*remainingStroke = strokeRec;
return true;
}
bool GrStyle::applyToPath(SkPath* dst, SkStrokeRec::InitStyle* style, const SkPath& src,
SkScalar resScale) const {
SkASSERT(style);
SkASSERT(dst);
SkStrokeRec strokeRec = fStrokeRec;
strokeRec.setResScale(resScale);
const SkPath* pathForStrokeRec = &src;
if (this->applyPathEffect(dst, &strokeRec, src)) {
pathForStrokeRec = dst;
} else if (fPathEffect) {
return false;
}
if (strokeRec.needToApply()) {
if (!strokeRec.applyToPath(dst, *pathForStrokeRec)) {
return false;
}
dst->setIsVolatile(true);
*style = SkStrokeRec::kFill_InitStyle;
} else if (!fPathEffect) {
// Nothing to do for path effect or stroke, fail.
return false;
} else {
SkASSERT(SkStrokeRec::kFill_Style == strokeRec.getStyle() ||
SkStrokeRec::kHairline_Style == strokeRec.getStyle());
*style = strokeRec.getStyle() == SkStrokeRec::kFill_Style
? SkStrokeRec::kFill_InitStyle
: SkStrokeRec::kHairline_InitStyle;
}
return true;
}