/* * 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 "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 Make(const skjson::ObjectValue& jgrad, const AnimationBuilder& abuilder) { const skjson::ObjectValue* jstops = jgrad["g"]; if (!jstops) return nullptr; const auto stopCount = ParseDefault((*jstops)["p"], -1); if (stopCount < 0) return nullptr; const auto type = (ParseDefault(jgrad["t"], 1) == 1) ? Type::kLinear : Type::kRadial; auto gradient_node = (type == Type::kLinear) ? sk_sp(sksg::LinearGradient::Make()) : sk_sp(sksg::RadialGradient::Make()); return sk_sp(new GradientAdapter(std::move(gradient_node), type, SkToSizeT(stopCount), jgrad, *jstops, abuilder)); } const sk_sp& node() const { return fGradient; } private: enum class Type { kLinear, kRadial }; GradientAdapter(sk_sp 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 = ValueTraits::As(this->fStartPoint), e_point = ValueTraits::As(this->fEndPoint); switch (fType) { case Type::kLinear: { auto* grad = static_cast(fGradient.get()); grad->setStartPoint(s_point); grad->setEndPoint(e_point); break; } case Type::kRadial: { auto* grad = static_cast(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(fStops.data()) : nullptr; const auto* o_rec = o_count > 0 ? reinterpret_cast(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 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(c_rec, c_end); } if (o_pos <= c_pos) { o_rec = next_rec(o_rec, o_end); } } stops.shrink_to_fit(); fGradient->setColorStops(std::move(stops)); } private: template 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 fGradient; const Type fType; const size_t fStopCount; VectorValue fStartPoint, fEndPoint, fStops; }; } // namespace sk_sp 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 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