• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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