| 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/skottie/src/animator/KeyframeAnimator.h" |
| 9 | |
| 10 | #include "modules/skottie/src/SkottieJson.h" |
| 11 | |
| 12 | #define DUMP_KF_RECORDS 0 |
| 13 | |
| 14 | namespace skottie::internal { |
| 15 | |
| 16 | KeyframeAnimator::~KeyframeAnimator() = default; |
| 17 | |
| 18 | KeyframeAnimator::LERPInfo KeyframeAnimator::getLERPInfo(float t) const { |
| 19 | SkASSERT(!fKFs.empty()); |
| 20 | |
| 21 | if (t <= fKFs.front().t) { |
| 22 | // Constant/clamped segment. |
| 23 | return { 0, fKFs.front().v, fKFs.front().v }; |
| 24 | } |
| 25 | if (t >= fKFs.back().t) { |
| 26 | // Constant/clamped segment. |
| 27 | return { 0, fKFs.back().v, fKFs.back().v }; |
| 28 | } |
| 29 | |
| 30 | // Cache the current segment (most queries have good locality). |
| 31 | if (!fCurrentSegment.contains(t)) { |
| 32 | fCurrentSegment = this->find_segment(t); |
| 33 | } |
| 34 | SkASSERT(fCurrentSegment.contains(t)); |
| 35 | |
| 36 | if (fCurrentSegment.kf0->mapping == Keyframe::kConstantMapping) { |
| 37 | // Constant/hold segment. |
| 38 | return { 0, fCurrentSegment.kf0->v, fCurrentSegment.kf0->v }; |
| 39 | } |
| 40 | |
| 41 | return { |
| 42 | this->compute_weight(fCurrentSegment, t), |
| 43 | fCurrentSegment.kf0->v, |
| 44 | fCurrentSegment.kf1->v, |
| 45 | }; |
| 46 | } |
| 47 | |
| 48 | KeyframeAnimator::KFSegment KeyframeAnimator::find_segment(float t) const { |
| 49 | SkASSERT(fKFs.size() > 1); |
| 50 | SkASSERT(t > fKFs.front().t); |
| 51 | SkASSERT(t < fKFs.back().t); |
| 52 | |
| 53 | auto kf0 = &fKFs.front(), |
| 54 | kf1 = &fKFs.back(); |
| 55 | |
| 56 | // Binary-search, until we reduce to sequential keyframes. |
| 57 | while (kf0 + 1 != kf1) { |
| 58 | SkASSERT(kf0 < kf1); |
| 59 | SkASSERT(kf0->t <= t && t < kf1->t); |
| 60 | |
| 61 | const auto mid_kf = kf0 + (kf1 - kf0) / 2; |
| 62 | |
| 63 | if (t >= mid_kf->t) { |
| 64 | kf0 = mid_kf; |
| 65 | } else { |
| 66 | kf1 = mid_kf; |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | return {kf0, kf1}; |
| 71 | } |
| 72 | |
| 73 | float KeyframeAnimator::compute_weight(const KFSegment &seg, float t) const { |
| 74 | SkASSERT(seg.contains(t)); |
| 75 | |
| 76 | // Linear weight. |
| 77 | auto w = (t - seg.kf0->t) / (seg.kf1->t - seg.kf0->t); |
| 78 | |
| 79 | // Optional cubic mapper. |
| 80 | if (seg.kf0->mapping >= Keyframe::kCubicIndexOffset) { |
| 81 | SkASSERT(seg.kf0->v != seg.kf1->v); |
| 82 | const auto mapper_index = SkToSizeT(seg.kf0->mapping - Keyframe::kCubicIndexOffset); |
| 83 | w = fCMs[mapper_index].computeYFromX(w); |
| 84 | } |
| 85 | |
| 86 | return w; |
| 87 | } |
| 88 | |
| 89 | KeyframeAnimatorBuilder::~KeyframeAnimatorBuilder() = default; |
| 90 | |
| 91 | bool KeyframeAnimatorBuilder::parseKeyframes(const AnimationBuilder& abuilder, |
| 92 | const skjson::ArrayValue& jkfs) { |
| 93 | // Keyframe format: |
| 94 | // |
| 95 | // [ // array of |
| 96 | // { |
| 97 | // "t": <float> // keyframe time |
| 98 | // "s": <T> // keyframe value |
| 99 | // "h": <bool> // optional constant/hold keyframe marker |
| 100 | // "i": [<float,float>] // optional "in" Bezier control point |
| 101 | // "o": [<float,float>] // optional "out" Bezier control point |
| 102 | // }, |
| 103 | // ... |
| 104 | // ] |
| 105 | // |
| 106 | // Legacy keyframe format: |
| 107 | // |
| 108 | // [ // array of |
| 109 | // { |
| 110 | // "t": <float> // keyframe time |
| 111 | // "s": <T> // keyframe start value |
| 112 | // "e": <T> // keyframe end value |
| 113 | // "h": <bool> // optional constant/hold keyframe marker (constant mapping) |
| 114 | // "i": [<float,float>] // optional "in" Bezier control point (cubic mapping) |
| 115 | // "o": [<float,float>] // optional "out" Bezier control point (cubic mapping) |
| 116 | // }, |
| 117 | // ... |
| 118 | // { |
| 119 | // "t": <float> // last keyframe only specifies a t |
| 120 | // // the value is prev. keyframe end value |
| 121 | // } |
| 122 | // ] |
| 123 | // |
| 124 | // Note: the legacy format contains duplicates, as normal frames are contiguous: |
| 125 | // frame(n).e == frame(n+1).s |
| 126 | |
| 127 | const auto parse_value = [&](const skjson::ObjectValue& jkf, size_t i, Keyframe::Value* v) { |
| 128 | auto parsed = this->parseKFValue(abuilder, jkf, jkf["s" ], v); |
| 129 | |
| 130 | // A missing value is only OK for the last legacy KF |
| 131 | // (where it is pulled from prev KF 'end' value). |
| 132 | if (!parsed && i > 0 && i == jkfs.size() - 1) { |
| 133 | const skjson::ObjectValue* prev_kf = jkfs[i - 1]; |
| 134 | SkASSERT(prev_kf); |
| 135 | parsed = this->parseKFValue(abuilder, jkf, (*prev_kf)["e" ], v); |
| 136 | } |
| 137 | |
| 138 | return parsed; |
| 139 | }; |
| 140 | |
| 141 | bool constant_value = true; |
| 142 | |
| 143 | fKFs.reserve(jkfs.size()); |
| 144 | |
| 145 | for (size_t i = 0; i < jkfs.size(); ++i) { |
| 146 | const skjson::ObjectValue* jkf = jkfs[i]; |
| 147 | if (!jkf) { |
| 148 | return false; |
| 149 | } |
| 150 | |
| 151 | float t; |
| 152 | if (!Parse<float>((*jkf)["t" ], &t)) { |
| 153 | return false; |
| 154 | } |
| 155 | |
| 156 | Keyframe::Value v; |
| 157 | if (!parse_value(*jkf, i, &v)) { |
| 158 | return false; |
| 159 | } |
| 160 | |
| 161 | if (i > 0) { |
| 162 | auto& prev_kf = fKFs.back(); |
| 163 | |
| 164 | // Ts must be strictly monotonic. |
| 165 | if (t <= prev_kf.t) { |
| 166 | return false; |
| 167 | } |
| 168 | |
| 169 | // We can power-reduce the mapping of repeated values (implicitly constant). |
| 170 | if (v == prev_kf.v) { |
| 171 | prev_kf.mapping = Keyframe::kConstantMapping; |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | fKFs.push_back({t, v, this->parseMapping(*jkf)}); |
| 176 | |
| 177 | constant_value = constant_value && (v == fKFs.front().v); |
| 178 | } |
| 179 | |
| 180 | SkASSERT(fKFs.size() == jkfs.size()); |
| 181 | fCMs.shrink_to_fit(); |
| 182 | |
| 183 | if (constant_value) { |
| 184 | // When all keyframes hold the same value, we can discard all but one |
| 185 | // (interpolation has no effect). |
| 186 | fKFs.resize(1); |
| 187 | } |
| 188 | |
| 189 | #if(DUMP_KF_RECORDS) |
| 190 | SkDEBUGF("Animator[%p], values: %lu, KF records: %zu\n" , |
| 191 | this, fKFs.back().v_idx + 1, fKFs.size()); |
| 192 | for (const auto& kf : fKFs) { |
| 193 | SkDEBUGF(" { t: %1.3f, v_idx: %lu, mapping: %lu }\n" , kf.t, kf.v_idx, kf.mapping); |
| 194 | } |
| 195 | #endif |
| 196 | return true; |
| 197 | } |
| 198 | |
| 199 | uint32_t KeyframeAnimatorBuilder::parseMapping(const skjson::ObjectValue& jkf) { |
| 200 | if (ParseDefault(jkf["h" ], false)) { |
| 201 | return Keyframe::kConstantMapping; |
| 202 | } |
| 203 | |
| 204 | SkPoint c0, c1; |
| 205 | if (!Parse(jkf["o" ], &c0) || |
| 206 | !Parse(jkf["i" ], &c1) || |
| 207 | SkCubicMap::IsLinear(c0, c1)) { |
| 208 | return Keyframe::kLinearMapping; |
| 209 | } |
| 210 | |
| 211 | // De-dupe sequential cubic mappers. |
| 212 | if (c0 != prev_c0 || c1 != prev_c1 || fCMs.empty()) { |
| 213 | fCMs.emplace_back(c0, c1); |
| 214 | prev_c0 = c0; |
| 215 | prev_c1 = c1; |
| 216 | } |
| 217 | |
| 218 | SkASSERT(!fCMs.empty()); |
| 219 | return SkToU32(fCMs.size()) - 1 + Keyframe::kCubicIndexOffset; |
| 220 | } |
| 221 | |
| 222 | } // namespace skottie::internal |
| 223 | |