blob: 19c0ef3da466530575a096bd2583219881747624 [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/private/SkTPin.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/SkSGGradient.h"
#include "modules/sksg/include/SkSGPaint.h"
namespace skottie {
namespace internal {
namespace {
class GradientAdapter final : public AnimatablePropertyContainer {
public:
static sk_sp<GradientAdapter> Make(const skjson::ObjectValue& jgrad,
const AnimationBuilder& abuilder) {
const skjson::ObjectValue* jstops = jgrad["g"];
if (!jstops)
return nullptr;
const auto stopCount = ParseDefault<int>((*jstops)["p"], -1);
if (stopCount < 0)
return nullptr;
const auto type = (ParseDefault<int>(jgrad["t"], 1) == 1) ? Type::kLinear
: Type::kRadial;
auto gradient_node = (type == Type::kLinear)
? sk_sp<sksg::Gradient>(sksg::LinearGradient::Make())
: sk_sp<sksg::Gradient>(sksg::RadialGradient::Make());
return sk_sp<GradientAdapter>(new GradientAdapter(std::move(gradient_node),
type,
SkToSizeT(stopCount),
jgrad, *jstops, abuilder));
}
const sk_sp<sksg::Gradient>& node() const { return fGradient; }
private:
enum class Type { kLinear, kRadial };
GradientAdapter(sk_sp<sksg::Gradient> gradient,
Type type,
size_t stop_count,
const skjson::ObjectValue& jgrad,
const skjson::ObjectValue& jstops,
const AnimationBuilder& abuilder)
: fGradient(std::move(gradient))
, fType(type)
, fStopCount(stop_count) {
this->bind(abuilder, jgrad["s"], fStartPoint);
this->bind(abuilder, jgrad["e"], fEndPoint );
this->bind(abuilder, jstops["k"], fStops );
}
void onSync() override {
const auto s_point = SkPoint{fStartPoint.x, fStartPoint.y},
e_point = SkPoint{ fEndPoint.x, fEndPoint.y};
switch (fType) {
case Type::kLinear: {
auto* grad = static_cast<sksg::LinearGradient*>(fGradient.get());
grad->setStartPoint(s_point);
grad->setEndPoint(e_point);
break;
}
case Type::kRadial: {
auto* grad = static_cast<sksg::RadialGradient*>(fGradient.get());
grad->setStartCenter(s_point);
grad->setEndCenter(s_point);
grad->setStartRadius(0);
grad->setEndRadius(SkPoint::Distance(s_point, e_point));
break;
}
}
// Gradient color stops are specified as a consolidated float vector holding:
//
// a) an (optional) array of color/RGB stop records (t, r, g, b)
//
// followed by
//
// b) an (optional) array of opacity/alpha stop records (t, a)
//
struct ColorRec { float t, r, g, b; };
struct OpacityRec { float t, a; };
// The number of color records is explicit (fColorStopCount),
// while the number of opacity stops is implicit (based on the size of fStops).
//
// |fStops| holds ColorRec x |fColorStopCount| + OpacityRec x N
const auto c_count = fStopCount,
c_size = c_count * 4,
o_count = (fStops.size() - c_size) / 2;
if (fStops.size() < c_size || fStops.size() != (c_count * 4 + o_count * 2)) {
// apply() may get called before the stops are set, so only log when we have some stops.
if (!fStops.empty()) {
SkDebugf("!! Invalid gradient stop array size: %zu\n", fStops.size());
}
return;
}
const auto* c_rec = c_count > 0
? reinterpret_cast<const ColorRec*>(fStops.data())
: nullptr;
const auto* o_rec = o_count > 0
? reinterpret_cast<const OpacityRec*>(fStops.data() + c_size)
: nullptr;
const auto* c_end = c_rec + c_count;
const auto* o_end = o_rec + o_count;
sksg::Gradient::ColorStop current_stop = {
0.0f, {
c_rec ? c_rec->r : 0,
c_rec ? c_rec->g : 0,
c_rec ? c_rec->b : 0,
o_rec ? o_rec->a : 1,
}};
std::vector<sksg::Gradient::ColorStop> stops;
stops.reserve(c_count);
// Merge-sort the color and opacity stops, LERP-ing intermediate channel values as needed.
while (c_rec || o_rec) {
// After exhausting one of color recs / opacity recs, continue propagating the last
// computed values (as if they were specified at the current position).
const auto& cs = c_rec
? *c_rec
: ColorRec{ o_rec->t,
current_stop.fColor.fR,
current_stop.fColor.fG,
current_stop.fColor.fB };
const auto& os = o_rec
? *o_rec
: OpacityRec{ c_rec->t, current_stop.fColor.fA };
// Compute component lerp coefficients based on the relative position of the stops
// being considered. The idea is to select the smaller-pos stop, use its own properties
// as specified (lerp with t == 1), and lerp (with t < 1) the properties from the
// larger-pos stop against the previously computed gradient stop values.
const auto c_pos = std::max(cs.t, current_stop.fPosition),
o_pos = std::max(os.t, current_stop.fPosition),
c_pos_rel = c_pos - current_stop.fPosition,
o_pos_rel = o_pos - current_stop.fPosition,
t_c = SkTPin(sk_ieee_float_divide(o_pos_rel, c_pos_rel), 0.0f, 1.0f),
t_o = SkTPin(sk_ieee_float_divide(c_pos_rel, o_pos_rel), 0.0f, 1.0f);
auto lerp = [](float a, float b, float t) { return a + t * (b - a); };
current_stop = {
std::min(c_pos, o_pos),
{
lerp(current_stop.fColor.fR, cs.r, t_c ),
lerp(current_stop.fColor.fG, cs.g, t_c ),
lerp(current_stop.fColor.fB, cs.b, t_c ),
lerp(current_stop.fColor.fA, os.a, t_o)
}
};
stops.push_back(current_stop);
// Consume one of, or both (for coincident positions) color/opacity stops.
if (c_pos <= o_pos) {
c_rec = next_rec<ColorRec>(c_rec, c_end);
}
if (o_pos <= c_pos) {
o_rec = next_rec<OpacityRec>(o_rec, o_end);
}
}
stops.shrink_to_fit();
fGradient->setColorStops(std::move(stops));
}
private:
template <typename T>
const T* next_rec(const T* rec, const T* end_rec) const {
if (!rec) return nullptr;
SkASSERT(rec < end_rec);
rec++;
return rec < end_rec ? rec : nullptr;
}
const sk_sp<sksg::Gradient> fGradient;
const Type fType;
const size_t fStopCount;
VectorValue fStops;
Vec2Value fStartPoint = {0,0},
fEndPoint = {0,0};
};
} // namespace
sk_sp<sksg::PaintNode> ShapeBuilder::AttachGradientFill(const skjson::ObjectValue& jgrad,
const AnimationBuilder* abuilder) {
auto adapter = GradientAdapter::Make(jgrad, *abuilder);
return adapter
? AttachFill(jgrad, abuilder, sksg::ShaderPaint::Make(adapter->node()), adapter)
: nullptr;
}
sk_sp<sksg::PaintNode> ShapeBuilder::AttachGradientStroke(const skjson::ObjectValue& jgrad,
const AnimationBuilder* abuilder) {
auto adapter = GradientAdapter::Make(jgrad, *abuilder);
return adapter
? AttachStroke(jgrad, abuilder, sksg::ShaderPaint::Make(adapter->node()), adapter)
: nullptr;
}
} // namespace internal
} // namespace skottie