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 "include/private/SkTPin.h" 9#include "modules/skottie/src/Adapter.h" 10#include "modules/skottie/src/SkottieJson.h" 11#include "modules/skottie/src/SkottiePriv.h" 12#include "modules/skottie/src/SkottieValue.h" 13#include "modules/skottie/src/layers/shapelayer/ShapeLayer.h" 14#include "modules/sksg/include/SkSGGradient.h" 15#include "modules/sksg/include/SkSGPaint.h" 16 17namespace skottie { 18namespace internal { 19 20namespace { 21 22class GradientAdapter final : public AnimatablePropertyContainer { 23public: 24 static sk_sp<GradientAdapter> Make(const skjson::ObjectValue& jgrad, 25 const AnimationBuilder& abuilder) { 26 const skjson::ObjectValue* jstops = jgrad["g"]; 27 if (!jstops) 28 return nullptr; 29 30 const auto stopCount = ParseDefault<int>((*jstops)["p"], -1); 31 if (stopCount < 0) 32 return nullptr; 33 34 const auto type = (ParseDefault<int>(jgrad["t"], 1) == 1) ? Type::kLinear 35 : Type::kRadial; 36 auto gradient_node = (type == Type::kLinear) 37 ? sk_sp<sksg::Gradient>(sksg::LinearGradient::Make()) 38 : sk_sp<sksg::Gradient>(sksg::RadialGradient::Make()); 39 40 return sk_sp<GradientAdapter>(new GradientAdapter(std::move(gradient_node), 41 type, 42 SkToSizeT(stopCount), 43 jgrad, *jstops, abuilder)); 44 } 45 46 const sk_sp<sksg::Gradient>& node() const { return fGradient; } 47 48private: 49 enum class Type { kLinear, kRadial }; 50 51 GradientAdapter(sk_sp<sksg::Gradient> gradient, 52 Type type, 53 size_t stop_count, 54 const skjson::ObjectValue& jgrad, 55 const skjson::ObjectValue& jstops, 56 const AnimationBuilder& abuilder) 57 : fGradient(std::move(gradient)) 58 , fType(type) 59 , fStopCount(stop_count) { 60 this->bind(abuilder, jgrad["s"], fStartPoint); 61 this->bind(abuilder, jgrad["e"], fEndPoint ); 62 this->bind(abuilder, jstops["k"], fStops ); 63 } 64 65 void onSync() override { 66 const auto s_point = SkPoint{fStartPoint.x, fStartPoint.y}, 67 e_point = SkPoint{ fEndPoint.x, fEndPoint.y}; 68 69 switch (fType) { 70 case Type::kLinear: { 71 auto* grad = static_cast<sksg::LinearGradient*>(fGradient.get()); 72 grad->setStartPoint(s_point); 73 grad->setEndPoint(e_point); 74 75 break; 76 } 77 case Type::kRadial: { 78 auto* grad = static_cast<sksg::RadialGradient*>(fGradient.get()); 79 grad->setStartCenter(s_point); 80 grad->setEndCenter(s_point); 81 grad->setStartRadius(0); 82 grad->setEndRadius(SkPoint::Distance(s_point, e_point)); 83 84 break; 85 } 86 } 87 88 // Gradient color stops are specified as a consolidated float vector holding: 89 // 90 // a) an (optional) array of color/RGB stop records (t, r, g, b) 91 // 92 // followed by 93 // 94 // b) an (optional) array of opacity/alpha stop records (t, a) 95 // 96 struct ColorRec { float t, r, g, b; }; 97 struct OpacityRec { float t, a; }; 98 99 // The number of color records is explicit (fColorStopCount), 100 // while the number of opacity stops is implicit (based on the size of fStops). 101 // 102 // |fStops| holds ColorRec x |fColorStopCount| + OpacityRec x N 103 const auto c_count = fStopCount, 104 c_size = c_count * 4, 105 o_count = (fStops.size() - c_size) / 2; 106 if (fStops.size() < c_size || fStops.size() != (c_count * 4 + o_count * 2)) { 107 // apply() may get called before the stops are set, so only log when we have some stops. 108 if (!fStops.empty()) { 109 SkDebugf("!! Invalid gradient stop array size: %zu\n", fStops.size()); 110 } 111 return; 112 } 113 114 const auto* c_rec = c_count > 0 115 ? reinterpret_cast<const ColorRec*>(fStops.data()) 116 : nullptr; 117 const auto* o_rec = o_count > 0 118 ? reinterpret_cast<const OpacityRec*>(fStops.data() + c_size) 119 : nullptr; 120 const auto* c_end = c_rec + c_count; 121 const auto* o_end = o_rec + o_count; 122 123 sksg::Gradient::ColorStop current_stop = { 124 0.0f, { 125 c_rec ? c_rec->r : 0, 126 c_rec ? c_rec->g : 0, 127 c_rec ? c_rec->b : 0, 128 o_rec ? o_rec->a : 1, 129 }}; 130 131 std::vector<sksg::Gradient::ColorStop> stops; 132 stops.reserve(c_count); 133 134 // Merge-sort the color and opacity stops, LERP-ing intermediate channel values as needed. 135 while (c_rec || o_rec) { 136 // After exhausting one of color recs / opacity recs, continue propagating the last 137 // computed values (as if they were specified at the current position). 138 const auto& cs = c_rec 139 ? *c_rec 140 : ColorRec{ o_rec->t, 141 current_stop.fColor.fR, 142 current_stop.fColor.fG, 143 current_stop.fColor.fB }; 144 const auto& os = o_rec 145 ? *o_rec 146 : OpacityRec{ c_rec->t, current_stop.fColor.fA }; 147 148 // Compute component lerp coefficients based on the relative position of the stops 149 // being considered. The idea is to select the smaller-pos stop, use its own properties 150 // as specified (lerp with t == 1), and lerp (with t < 1) the properties from the 151 // larger-pos stop against the previously computed gradient stop values. 152 const auto c_pos = std::max(cs.t, current_stop.fPosition), 153 o_pos = std::max(os.t, current_stop.fPosition), 154 c_pos_rel = c_pos - current_stop.fPosition, 155 o_pos_rel = o_pos - current_stop.fPosition, 156 t_c = SkTPin(sk_ieee_float_divide(o_pos_rel, c_pos_rel), 0.0f, 1.0f), 157 t_o = SkTPin(sk_ieee_float_divide(c_pos_rel, o_pos_rel), 0.0f, 1.0f); 158 159 auto lerp = [](float a, float b, float t) { return a + t * (b - a); }; 160 161 current_stop = { 162 std::min(c_pos, o_pos), 163 { 164 lerp(current_stop.fColor.fR, cs.r, t_c ), 165 lerp(current_stop.fColor.fG, cs.g, t_c ), 166 lerp(current_stop.fColor.fB, cs.b, t_c ), 167 lerp(current_stop.fColor.fA, os.a, t_o) 168 } 169 }; 170 stops.push_back(current_stop); 171 172 // Consume one of, or both (for coincident positions) color/opacity stops. 173 if (c_pos <= o_pos) { 174 c_rec = next_rec<ColorRec>(c_rec, c_end); 175 } 176 if (o_pos <= c_pos) { 177 o_rec = next_rec<OpacityRec>(o_rec, o_end); 178 } 179 } 180 181 stops.shrink_to_fit(); 182 fGradient->setColorStops(std::move(stops)); 183 } 184 185private: 186 template <typename T> 187 const T* next_rec(const T* rec, const T* end_rec) const { 188 if (!rec) return nullptr; 189 190 SkASSERT(rec < end_rec); 191 rec++; 192 193 return rec < end_rec ? rec : nullptr; 194 } 195 196 const sk_sp<sksg::Gradient> fGradient; 197 const Type fType; 198 const size_t fStopCount; 199 200 VectorValue fStops; 201 Vec2Value fStartPoint = {0,0}, 202 fEndPoint = {0,0}; 203}; 204 205} // namespace 206 207sk_sp<sksg::PaintNode> ShapeBuilder::AttachGradientFill(const skjson::ObjectValue& jgrad, 208 const AnimationBuilder* abuilder) { 209 auto adapter = GradientAdapter::Make(jgrad, *abuilder); 210 211 return adapter 212 ? AttachFill(jgrad, abuilder, sksg::ShaderPaint::Make(adapter->node()), adapter) 213 : nullptr; 214} 215 216sk_sp<sksg::PaintNode> ShapeBuilder::AttachGradientStroke(const skjson::ObjectValue& jgrad, 217 const AnimationBuilder* abuilder) { 218 auto adapter = GradientAdapter::Make(jgrad, *abuilder); 219 220 return adapter 221 ? AttachStroke(jgrad, abuilder, sksg::ShaderPaint::Make(adapter->node()), adapter) 222 : nullptr; 223} 224 225} // namespace internal 226} // namespace skottie 227