1 /* 2 * Copyright 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #ifndef ANDROID_INTERPOLATOR_H 18 #define ANDROID_INTERPOLATOR_H 19 20 #include <map> 21 #include <sstream> 22 #include <unordered_map> 23 24 #include <android/media/InterpolatorConfig.h> 25 #include <binder/Parcel.h> 26 #include <utils/RefBase.h> 27 28 #pragma push_macro("LOG_TAG") 29 #undef LOG_TAG 30 #define LOG_TAG "Interpolator" 31 32 namespace android { 33 34 /* 35 * A general purpose spline interpolator class which takes a set of points 36 * and performs interpolation. This is used for the VolumeShaper class. 37 */ 38 39 template <typename S, typename T> 40 class Interpolator : public std::map<S, T> { 41 public: 42 // Polynomial spline interpolators 43 using InterpolatorType = media::InterpolatorType; 44 45 explicit Interpolator( 46 InterpolatorType interpolatorType = InterpolatorType::CUBIC, 47 bool cache = true) mCache(cache)48 : mCache(cache) 49 , mFirstSlope(0) 50 , mLastSlope(0) { 51 setInterpolatorType(interpolatorType); 52 } 53 first()54 std::pair<S, T> first() const { 55 return *this->begin(); 56 } 57 last()58 std::pair<S, T> last() const { 59 return *this->rbegin(); 60 } 61 62 // find the corresponding Y point from a X point. findY(S x)63 T findY(S x) { // logically const, but modifies cache 64 auto high = this->lower_bound(x); 65 // greater than last point 66 if (high == this->end()) { 67 return this->rbegin()->second; 68 } 69 // at or before first point 70 if (high == this->begin()) { 71 return high->second; 72 } 73 // go lower. 74 auto low = high; 75 --low; 76 77 // now that we have two adjacent points: 78 switch (mInterpolatorType) { 79 case InterpolatorType::STEP: 80 return high->first == x ? high->second : low->second; 81 case InterpolatorType::LINEAR: 82 return ((high->first - x) * low->second + (x - low->first) * high->second) 83 / (high->first - low->first); 84 case InterpolatorType::CUBIC: 85 case InterpolatorType::CUBIC_MONOTONIC: 86 default: { 87 // See https://en.wikipedia.org/wiki/Cubic_Hermite_spline 88 89 const S interval = high->first - low->first; 90 91 // check to see if we've cached the polynomial coefficients 92 if (mMemo.count(low->first) != 0) { 93 const S t = (x - low->first) / interval; 94 const S t2 = t * t; 95 const auto &memo = mMemo[low->first]; 96 return low->second + std::get<0>(memo) * t 97 + (std::get<1>(memo) + std::get<2>(memo) * t) * t2; 98 } 99 100 // find the neighboring points (low2 < low < high < high2) 101 auto low2 = this->end(); 102 if (low != this->begin()) { 103 low2 = low; 104 --low2; // decrementing this->begin() is undefined 105 } 106 auto high2 = high; 107 ++high2; 108 109 // you could have catmullRom with monotonic or 110 // non catmullRom (finite difference) with regular cubic; 111 // the choices here minimize computation. 112 bool monotonic, catmullRom; 113 if (mInterpolatorType == InterpolatorType::CUBIC_MONOTONIC) { 114 monotonic = true; 115 catmullRom = false; 116 } else { 117 monotonic = false; 118 catmullRom = true; 119 } 120 121 // secants are only needed for finite difference splines or 122 // monotonic computation. 123 // we use lazy computation here - if we precompute in 124 // a single pass, duplicate secant computations may be avoided. 125 S sec, sec0, sec1; 126 if (!catmullRom || monotonic) { 127 sec = (high->second - low->second) / interval; 128 sec0 = low2 != this->end() 129 ? (low->second - low2->second) / (low->first - low2->first) 130 : mFirstSlope; 131 sec1 = high2 != this->end() 132 ? (high2->second - high->second) / (high2->first - high->first) 133 : mLastSlope; 134 } 135 136 // compute the tangent slopes at the control points 137 S m0, m1; 138 if (catmullRom) { 139 // Catmull-Rom spline 140 m0 = low2 != this->end() 141 ? (high->second - low2->second) / (high->first - low2->first) 142 : mFirstSlope; 143 144 m1 = high2 != this->end() 145 ? (high2->second - low->second) / (high2->first - low->first) 146 : mLastSlope; 147 } else { 148 // finite difference spline 149 m0 = (sec0 + sec) * 0.5f; 150 m1 = (sec1 + sec) * 0.5f; 151 } 152 153 if (monotonic) { 154 // https://en.wikipedia.org/wiki/Monotone_cubic_interpolation 155 // A sufficient condition for Fritsch–Carlson monotonicity is constraining 156 // (1) the normalized slopes to be within the circle of radius 3, or 157 // (2) the normalized slopes to be within the square of radius 3. 158 // Condition (2) is more generous and easier to compute. 159 const S maxSlope = 3 * sec; 160 m0 = constrainSlope(m0, maxSlope); 161 m1 = constrainSlope(m1, maxSlope); 162 163 m0 = constrainSlope(m0, 3 * sec0); 164 m1 = constrainSlope(m1, 3 * sec1); 165 } 166 167 const S t = (x - low->first) / interval; 168 const S t2 = t * t; 169 if (mCache) { 170 // convert to cubic polynomial coefficients and compute 171 m0 *= interval; 172 m1 *= interval; 173 const T dy = high->second - low->second; 174 const S c0 = low->second; 175 const S c1 = m0; 176 const S c2 = 3 * dy - 2 * m0 - m1; 177 const S c3 = m0 + m1 - 2 * dy; 178 mMemo[low->first] = std::make_tuple(c1, c2, c3); 179 return c0 + c1 * t + (c2 + c3 * t) * t2; 180 } else { 181 // classic Hermite interpolation 182 const S t3 = t2 * t; 183 const S h00 = 2 * t3 - 3 * t2 + 1; 184 const S h10 = t3 - 2 * t2 + t ; 185 const S h01 = -2 * t3 + 3 * t2 ; 186 const S h11 = t3 - t2 ; 187 return h00 * low->second + (h10 * m0 + h11 * m1) * interval + h01 * high->second; 188 } 189 } // default 190 } 191 } 192 getInterpolatorType()193 InterpolatorType getInterpolatorType() const { 194 return mInterpolatorType; 195 } 196 setInterpolatorType(InterpolatorType interpolatorType)197 status_t setInterpolatorType(InterpolatorType interpolatorType) { 198 switch (interpolatorType) { 199 case InterpolatorType::STEP: // Not continuous 200 case InterpolatorType::LINEAR: // C0 201 case InterpolatorType::CUBIC: // C1 202 case InterpolatorType::CUBIC_MONOTONIC: // C1 + other constraints 203 // case InterpolatorType::CUBIC_C2: 204 mInterpolatorType = interpolatorType; 205 return NO_ERROR; 206 default: 207 ALOGE("invalid interpolatorType: %d", interpolatorType); 208 return BAD_VALUE; 209 } 210 } 211 getFirstSlope()212 T getFirstSlope() const { 213 return mFirstSlope; 214 } 215 setFirstSlope(T slope)216 void setFirstSlope(T slope) { 217 mFirstSlope = slope; 218 } 219 getLastSlope()220 T getLastSlope() const { 221 return mLastSlope; 222 } 223 setLastSlope(T slope)224 void setLastSlope(T slope) { 225 mLastSlope = slope; 226 } 227 clearCache()228 void clearCache() { 229 mMemo.clear(); 230 } 231 232 // TODO(ytai): remove this method once it is not used. writeToParcel(Parcel * parcel)233 status_t writeToParcel(Parcel *parcel) const { 234 media::InterpolatorConfig config; 235 writeToConfig(&config); 236 return config.writeToParcel(parcel); 237 } 238 writeToConfig(media::InterpolatorConfig * config)239 void writeToConfig(media::InterpolatorConfig *config) const { 240 config->type = mInterpolatorType; 241 config->firstSlope = mFirstSlope; 242 config->lastSlope = mLastSlope; 243 for (const auto &pt : *this) { 244 config->xy.push_back(pt.first); 245 config->xy.push_back(pt.second); 246 } 247 } 248 249 // TODO(ytai): remove this method once it is not used. readFromParcel(const Parcel & parcel)250 status_t readFromParcel(const Parcel &parcel) { 251 media::InterpolatorConfig config; 252 status_t res = config.readFromParcel(&parcel); 253 if (res != NO_ERROR) { 254 return res; 255 } 256 return readFromConfig(config); 257 } 258 readFromConfig(const media::InterpolatorConfig & config)259 status_t readFromConfig(const media::InterpolatorConfig &config) { 260 this->clear(); 261 setInterpolatorType(config.type); 262 if ((config.xy.size() & 1) != 0) { 263 // xy size must be even. 264 return BAD_VALUE; 265 } 266 uint32_t size = config.xy.size() / 2; 267 mFirstSlope = config.firstSlope; 268 mLastSlope = config.lastSlope; 269 270 // Note: We don't need to check size is within some bounds as 271 // the Parcel read will fail if size is incorrectly specified too large. 272 float lastx; 273 for (uint32_t i = 0; i < size; ++i) { 274 float x = config.xy[i * 2]; 275 float y = config.xy[i * 2 + 1]; 276 if ((i > 0 && !(x > lastx)) /* handle nan */ 277 || y != y /* handle nan */) { 278 // This is a std::map object which imposes sorted order 279 // automatically on emplace. 280 // Nevertheless for reading from a Parcel, 281 // we require that the points be specified monotonic in x. 282 return BAD_VALUE; 283 } 284 this->emplace(x, y); 285 lastx = x; 286 } 287 return NO_ERROR; 288 } 289 toString()290 std::string toString() const { 291 std::stringstream ss; 292 ss << "Interpolator{mInterpolatorType=" << static_cast<int32_t>(mInterpolatorType); 293 ss << ", mFirstSlope=" << mFirstSlope; 294 ss << ", mLastSlope=" << mLastSlope; 295 ss << ", {"; 296 bool first = true; 297 for (const auto &pt : *this) { 298 if (first) { 299 first = false; 300 ss << "{"; 301 } else { 302 ss << ", {"; 303 } 304 ss << pt.first << ", " << pt.second << "}"; 305 } 306 ss << "}}"; 307 return ss.str(); 308 } 309 310 private: constrainSlope(S slope,S maxSlope)311 static S constrainSlope(S slope, S maxSlope) { 312 if (maxSlope > 0) { 313 slope = std::min(slope, maxSlope); 314 slope = std::max(slope, S(0)); // not globally monotonic 315 } else { 316 slope = std::max(slope, maxSlope); 317 slope = std::min(slope, S(0)); // not globally monotonic 318 } 319 return slope; 320 } 321 322 InterpolatorType mInterpolatorType; 323 bool mCache; // whether we cache spline coefficient computation 324 325 // for cubic interpolation, the boundary conditions in slope. 326 S mFirstSlope; 327 S mLastSlope; 328 329 // spline cubic polynomial coefficient cache 330 std::unordered_map<S, std::tuple<S /* c1 */, S /* c2 */, S /* c3 */>> mMemo; 331 }; // Interpolator 332 333 } // namespace android 334 335 #pragma pop_macro("LOG_TAG") 336 337 #endif // ANDROID_INTERPOLATOR_H 338