Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit ed38d20

Browse files
fmalitaSkia Commit-Bot
authored andcommitted
[skottie] Stroke dash support
AE supports dashing all strokes. Dashes are specified as an arbitrary number of intervals (alternating dash/gap) plus a start offset. All values can be animated independently (but of course!). - implement a SkSG dash effect (based on SkDashPathEffect) - expand the shape builder logic to allow local geometry adjustments (kind of a bummer that dashing is a stroke/paint property as opposed to a geometry effect in AE) Change-Id: Ic9ff35f2f9a552a3c26f9e1596ce58ad81f7ced5 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/274550 Reviewed-by: Mike Reed <reed@google.com> Commit-Queue: Florin Malita <fmalita@chromium.org>
1 parent ac6156c commit ed38d20

File tree

6 files changed

+224
-8
lines changed

6 files changed

+224
-8
lines changed

modules/skottie/src/layers/shapelayer/FillStroke.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "modules/skottie/src/SkottiePriv.h"
1111
#include "modules/skottie/src/SkottieValue.h"
1212
#include "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
13+
#include "modules/sksg/include/SkSGDashEffect.h"
1314
#include "modules/sksg/include/SkSGPaint.h"
1415

1516
namespace skottie {
@@ -87,6 +88,41 @@ class FillStrokeAdapter final : public DiscardableAdapterBase<FillStrokeAdapter,
8788
using INHERITED = DiscardableAdapterBase<FillStrokeAdapter, sksg::PaintNode>;
8889
};
8990

91+
class DashAdapter final : public DiscardableAdapterBase<DashAdapter, sksg::DashEffect> {
92+
public:
93+
DashAdapter(const skjson::ArrayValue& jdash,
94+
const AnimationBuilder& abuilder,
95+
sk_sp<sksg::GeometryNode> geo)
96+
: INHERITED(sksg::DashEffect::Make(std::move(geo))) {
97+
SkASSERT(jdash.size() > 1);
98+
99+
// The dash is encoded as an arbitrary number of intervals (alternating dash/gap),
100+
// plus a single trailing offset. Each value can be animated independently.
101+
const auto interval_count = jdash.size() - 1;
102+
fIntervals.resize(interval_count, 0);
103+
104+
for (size_t i = 0; i < jdash.size(); ++i) {
105+
if (const skjson::ObjectValue* jint = jdash[i]) {
106+
auto* target = i < interval_count
107+
? &fIntervals[i]
108+
: &fOffset;
109+
this->bind(abuilder, (*jint)["v"], target);
110+
}
111+
}
112+
}
113+
114+
private:
115+
void onSync() override {
116+
this->node()->setPhase(fOffset);
117+
this->node()->setIntervals(fIntervals);
118+
}
119+
120+
std::vector<ScalarValue> fIntervals;
121+
ScalarValue fOffset = 0;
122+
123+
using INHERITED = DiscardableAdapterBase<DashAdapter, sksg::DashEffect>;
124+
};
125+
90126
} // namespace
91127

92128
sk_sp<sksg::PaintNode> ShapeBuilder::AttachFill(const skjson::ObjectValue& jpaint,
@@ -129,5 +165,21 @@ sk_sp<sksg::PaintNode> ShapeBuilder::AttachColorStroke(const skjson::ObjectValue
129165
return AttachStroke(jpaint, abuilder, std::move(color_node));
130166
}
131167

168+
std::vector<sk_sp<sksg::GeometryNode>> ShapeBuilder::AdjustStrokeGeometry(
169+
const skjson::ObjectValue& jstroke,
170+
const AnimationBuilder* abuilder,
171+
std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
172+
173+
const skjson::ArrayValue* jdash = jstroke["d"];
174+
if (jdash && jdash->size() > 1) {
175+
for (size_t i = 0; i < geos.size(); ++i) {
176+
geos[i] = abuilder->attachDiscardableAdapter<DashAdapter, sk_sp<sksg::GeometryNode>>(
177+
*jdash, *abuilder, std::move(geos[i]));
178+
}
179+
}
180+
181+
return std::move(geos);
182+
}
183+
132184
} // namespace internal
133185
} // namespace skottie

modules/skottie/src/layers/shapelayer/ShapeLayer.cpp

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ static constexpr GeometryAttacherT gGeometryAttachers[] = {
4141
ShapeBuilder::AttachPolystarGeometry,
4242
};
4343

44+
using GeometryEffectAttacherT =
45+
std::vector<sk_sp<sksg::GeometryNode>> (*)(const skjson::ObjectValue&,
46+
const AnimationBuilder*,
47+
std::vector<sk_sp<sksg::GeometryNode>>&&);
48+
static constexpr GeometryEffectAttacherT gGeometryEffectAttachers[] = {
49+
ShapeBuilder::AttachMergeGeometryEffect,
50+
ShapeBuilder::AttachTrimGeometryEffect,
51+
ShapeBuilder::AttachRoundGeometryEffect,
52+
};
53+
4454
using PaintAttacherT = sk_sp<sksg::PaintNode> (*)(const skjson::ObjectValue&,
4555
const AnimationBuilder*);
4656
static constexpr PaintAttacherT gPaintAttachers[] = {
@@ -50,15 +60,14 @@ static constexpr PaintAttacherT gPaintAttachers[] = {
5060
ShapeBuilder::AttachGradientStroke,
5161
};
5262

53-
using GeometryEffectAttacherT =
54-
std::vector<sk_sp<sksg::GeometryNode>> (*)(const skjson::ObjectValue&,
55-
const AnimationBuilder*,
56-
std::vector<sk_sp<sksg::GeometryNode>>&&);
57-
static constexpr GeometryEffectAttacherT gGeometryEffectAttachers[] = {
58-
ShapeBuilder::AttachMergeGeometryEffect,
59-
ShapeBuilder::AttachTrimGeometryEffect,
60-
ShapeBuilder::AttachRoundGeometryEffect,
63+
// Some paint types (looking at you dashed-stroke) mess with the local geometry.
64+
static constexpr GeometryEffectAttacherT gPaintGeometryAdjusters[] = {
65+
nullptr, // color fill
66+
ShapeBuilder::AdjustStrokeGeometry, // color stroke
67+
nullptr, // gradient fill
68+
ShapeBuilder::AdjustStrokeGeometry, // gradient stroke
6169
};
70+
static_assert(SK_ARRAY_COUNT(gPaintGeometryAdjusters) == SK_ARRAY_COUNT(gPaintAttachers), "");
6271

6372
using DrawEffectAttacherT =
6473
std::vector<sk_sp<sksg::RenderNode>> (*)(const skjson::ObjectValue&,
@@ -258,6 +267,12 @@ sk_sp<sksg::RenderNode> AnimationBuilder::attachShape(const skjson::ArrayValue*
258267
drawGeos = it->fAttach(it->fJson, this, std::move(drawGeos));
259268
}
260269

270+
// Apply local paint geometry adjustments (e.g. dashing).
271+
SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gPaintGeometryAdjusters));
272+
if (const auto adjuster = gPaintGeometryAdjusters[rec->fInfo.fAttacherIndex]) {
273+
drawGeos = adjuster(rec->fJson, this, std::move(drawGeos));
274+
}
275+
261276
// If we still have multiple geos, reduce using 'merge'.
262277
auto geo = drawGeos.size() > 1
263278
? ShapeBuilder::MergeGeometry(std::move(drawGeos), sksg::Merge::Mode::kMerge)

modules/skottie/src/layers/shapelayer/ShapeLayer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ class ShapeBuilder final : SkNoncopyable {
6767
static std::vector<sk_sp<sksg::GeometryNode>> AttachRoundGeometryEffect(
6868
const skjson::ObjectValue&, const AnimationBuilder*,
6969
std::vector<sk_sp<sksg::GeometryNode>>&&);
70+
static std::vector<sk_sp<sksg::GeometryNode>> AdjustStrokeGeometry(
71+
const skjson::ObjectValue&, const AnimationBuilder*,
72+
std::vector<sk_sp<sksg::GeometryNode>>&&);
7073

7174
static std::vector<sk_sp<sksg::RenderNode>> AttachRepeaterDrawEffect(
7275
const skjson::ObjectValue&,
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2020 Google Inc.
3+
*
4+
* Use of this source code is governed by a BSD-style license that can be
5+
* found in the LICENSE file.
6+
*/
7+
8+
#ifndef SkSGDashEffect_DEFINED
9+
#define SkSGDashEffect_DEFINED
10+
11+
#include "include/core/SkPath.h"
12+
#include "modules/sksg/include/SkSGGeometryNode.h"
13+
14+
#include <vector>
15+
16+
namespace sksg {
17+
18+
/**
19+
* Apply a dash effect to the child geometry.
20+
*
21+
* Follows the same semantics as SkDashPathEffect, with one minor tweak: when the number of
22+
* intervals is odd, they are repeated once more to attain an even sequence (same as SVG
23+
* stroke-dasharray: https://www.w3.org/TR/SVG11/painting.html#StrokeDasharrayProperty).
24+
*/
25+
class DashEffect final : public GeometryNode {
26+
public:
27+
static sk_sp<DashEffect> Make(sk_sp<GeometryNode> child) {
28+
return child ? sk_sp<DashEffect>(new DashEffect(std::move(child))) : nullptr;
29+
}
30+
31+
~DashEffect() override;
32+
33+
SG_ATTRIBUTE(Intervals, std::vector<float>, fIntervals)
34+
SG_ATTRIBUTE(Phase, float , fPhase )
35+
36+
protected:
37+
void onClip(SkCanvas*, bool antiAlias) const override;
38+
void onDraw(SkCanvas*, const SkPaint&) const override;
39+
bool onContains(const SkPoint&) const override;
40+
41+
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
42+
SkPath onAsPath() const override;
43+
44+
private:
45+
explicit DashEffect(sk_sp<GeometryNode>);
46+
47+
const sk_sp<GeometryNode> fChild;
48+
49+
SkPath fDashedPath; // cache
50+
51+
std::vector<float> fIntervals;
52+
float fPhase;
53+
};
54+
55+
} // namespace sksg
56+
57+
#endif // SkSGDashEffect_DEFINED

modules/sksg/sksg.gni

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ _src = get_path_info("src", "abspath")
99
skia_sksg_sources = [
1010
"$_src/SkSGClipEffect.cpp",
1111
"$_src/SkSGColorFilter.cpp",
12+
"$_src/SkSGDashEffect.cpp",
1213
"$_src/SkSGDraw.cpp",
1314
"$_src/SkSGEffectNode.cpp",
1415
"$_src/SkSGGeometryNode.cpp",
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2020 Google Inc.
3+
*
4+
* Use of this source code is governed by a BSD-style license that can be
5+
* found in the LICENSE file.
6+
*/
7+
8+
#include "modules/sksg/include/SkSGDashEffect.h"
9+
10+
#include "include/core/SkCanvas.h"
11+
#include "include/core/SkStrokeRec.h"
12+
#include "include/effects/SkDashPathEffect.h"
13+
14+
#include <algorithm>
15+
16+
namespace sksg {
17+
18+
namespace {
19+
20+
sk_sp<SkPathEffect> make_dash(const std::vector<float> intervals, float phase) {
21+
if (intervals.empty()) {
22+
return nullptr;
23+
}
24+
25+
const auto* intervals_ptr = intervals.data();
26+
auto intervals_count = intervals.size();
27+
28+
SkSTArray<32, float, true> storage;
29+
if (intervals_count & 1) {
30+
intervals_count *= 2;
31+
storage.resize(intervals_count);
32+
intervals_ptr = storage.data();
33+
34+
std::copy(intervals.begin(), intervals.end(), storage.begin());
35+
std::copy(intervals.begin(), intervals.end(), storage.begin() + intervals.size());
36+
}
37+
38+
return SkDashPathEffect::Make(intervals_ptr, SkToInt(intervals_count), phase);
39+
}
40+
41+
} // namespace
42+
43+
DashEffect::DashEffect(sk_sp<GeometryNode> child)
44+
: fChild(std::move(child)) {
45+
this->observeInval(fChild);
46+
}
47+
48+
DashEffect::~DashEffect() {
49+
this->unobserveInval(fChild);
50+
}
51+
52+
void DashEffect::onClip(SkCanvas* canvas, bool antiAlias) const {
53+
canvas->clipPath(fDashedPath, SkClipOp::kIntersect, antiAlias);
54+
}
55+
56+
void DashEffect::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
57+
canvas->drawPath(fDashedPath, paint);
58+
}
59+
60+
bool DashEffect::onContains(const SkPoint& p) const {
61+
return fDashedPath.contains(p.x(), p.y());
62+
}
63+
64+
SkPath DashEffect::onAsPath() const {
65+
return fDashedPath;
66+
}
67+
68+
SkRect DashEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
69+
SkASSERT(this->hasInval());
70+
71+
const auto child_bounds = fChild->revalidate(ic, ctm);
72+
const auto child_path = fChild->asPath();
73+
74+
fDashedPath.reset();
75+
76+
auto dash_patheffect = make_dash(fIntervals, fPhase);
77+
SkStrokeRec rec(SkStrokeRec::kHairline_InitStyle);
78+
79+
if (!dash_patheffect ||
80+
!dash_patheffect->filterPath(&fDashedPath, child_path, &rec, &child_bounds)) {
81+
fDashedPath = std::move(child_path);
82+
}
83+
fDashedPath.shrinkToFit();
84+
85+
return fDashedPath.computeTightBounds();
86+
}
87+
88+
} // namespace sksg

0 commit comments

Comments
 (0)