blob: 3691a03039e0718623929211b3ce787fa9e4fe72 [file] [log] [blame]
/*
* Copyright 2020 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkM44.h"
#include "modules/skottie/src/Adapter.h"
#include "modules/skottie/src/SkottieJson.h"
#include "modules/skottie/src/SkottiePriv.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
#include "modules/sksg/include/SkSGGeometryEffect.h"
#include "src/core/SkGeometry.h"
#include <vector>
namespace skottie::internal {
namespace {
static SkPoint lerp(const SkPoint& p0, const SkPoint& p1, SkScalar t) {
return p0 + (p1 - p0) * t;
}
// Operates on the cubic representation of a shape. Pulls vertices towards the shape center,
// and cubic control points away from the center. The general shape center is the vertex average.
class PuckerBloatEffect final : public sksg::GeometryEffect {
public:
explicit PuckerBloatEffect(sk_sp<sksg::GeometryNode> geo) : INHERITED({std::move(geo)}) {}
// Fraction of the transition to center. I.e.
//
// 0 -> no effect
// 1 -> vertices collapsed to center
//
// Negative values are allowed (inverse direction), as are extranormal values.
SG_ATTRIBUTE(Amount, float, fAmount)
private:
SkPath onRevalidateEffect(const sk_sp<GeometryNode>& geo) override {
struct CubicInfo {
SkPoint ctrl0, ctrl1, pt; // corresponding to SkPath::cubicTo() params, respectively.
};
const auto input = geo->asPath();
if (SkScalarNearlyZero(fAmount)) {
return input;
}
const auto input_bounds = input.computeTightBounds();
const SkPoint center{input_bounds.centerX(), input_bounds.centerY()};
SkPath path;
SkPoint contour_start = {0, 0};
std::vector<CubicInfo> cubics;
auto commit_contour = [&]() {
path.moveTo(lerp(contour_start, center, fAmount));
for (const auto& c : cubics) {
path.cubicTo(lerp(c.ctrl0, center, -fAmount),
lerp(c.ctrl1, center, -fAmount),
lerp(c.pt , center, fAmount));
}
path.close();
cubics.clear();
};
// Normalize all verbs to cubic representation.
SkPoint pts[4];
SkPath::Verb verb;
SkPath::Iter iter(input, true);
while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
switch (verb) {
case SkPath::kMove_Verb:
commit_contour();
contour_start = pts[0];
break;
case SkPath::kLine_Verb: {
// Empirically, straight lines are treated as cubics with control points
// located length/100 away from extremities.
static constexpr float kCtrlPosFraction = 1.f / 100;
const auto line_start = pts[0],
line_end = pts[1];
cubics.push_back({
lerp(line_start, line_end, kCtrlPosFraction),
lerp(line_start, line_end, 1 - kCtrlPosFraction),
line_end
});
} break;
case SkPath::kQuad_Verb:
SkConvertQuadToCubic(pts, pts);
cubics.push_back({pts[1], pts[2], pts[3]});
break;
case SkPath::kConic_Verb: {
// We should only ever encounter conics from circles/ellipses.
SkASSERT(SkScalarNearlyEqual(iter.conicWeight(), SK_ScalarRoot2Over2));
// http://spencermortensen.com/articles/bezier-circle/
static constexpr float kCubicCircleCoeff = 1 - 0.551915024494f;
const auto conic_start = cubics.empty() ? contour_start
: cubics.back().pt,
conic_end = pts[2];
cubics.push_back({
lerp(pts[1], conic_start, kCubicCircleCoeff),
lerp(pts[1], conic_end , kCubicCircleCoeff),
conic_end
});
} break;
case SkPath::kCubic_Verb:
cubics.push_back({pts[1], pts[2], pts[3]});
break;
case SkPath::kClose_Verb:
commit_contour();
break;
default:
break;
}
}
return path;
}
float fAmount = 0;
using INHERITED = sksg::GeometryEffect;
};
class PuckerBloatAdapter final : public DiscardableAdapterBase<PuckerBloatAdapter,
PuckerBloatEffect> {
public:
PuckerBloatAdapter(const skjson::ObjectValue& joffset,
const AnimationBuilder& abuilder,
sk_sp<sksg::GeometryNode> child)
: INHERITED(sk_make_sp<PuckerBloatEffect>(std::move(child))) {
this->bind(abuilder, joffset["a" ], fAmount);
}
private:
void onSync() override {
// AE amount is percentage-based.
this->node()->setAmount(fAmount / 100);
}
ScalarValue fAmount = 0;
using INHERITED = DiscardableAdapterBase<PuckerBloatAdapter, PuckerBloatEffect>;
};
} // namespace
std::vector<sk_sp<sksg::GeometryNode>> ShapeBuilder::AttachPuckerBloatGeometryEffect(
const skjson::ObjectValue& jround, const AnimationBuilder* abuilder,
std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
std::vector<sk_sp<sksg::GeometryNode>> bloated;
bloated.reserve(geos.size());
for (auto& g : geos) {
bloated.push_back(abuilder->attachDiscardableAdapter<PuckerBloatAdapter>
(jround, *abuilder, std::move(g)));
}
return bloated;
}
} // namespace skottie::internal