blob: 2004acbe6379eb82e1ad7e7db03fd8da32f1c749 [file] [log] [blame]
Andy Hung9fc8b5c2017-01-24 13:36:48 -08001/*
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
Ytai Ben-Tsvif0658f42020-10-26 11:51:14 -070024#include <android/media/InterpolatorConfig.h>
Andy Hung9fc8b5c2017-01-24 13:36:48 -080025#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
32namespace 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
39template <typename S, typename T>
40class Interpolator : public std::map<S, T> {
41public:
42 // Polynomial spline interpolators
Ytai Ben-Tsvif0658f42020-10-26 11:51:14 -070043 using InterpolatorType = media::InterpolatorType;
Andy Hung9fc8b5c2017-01-24 13:36:48 -080044
45 explicit Interpolator(
Ytai Ben-Tsvif0658f42020-10-26 11:51:14 -070046 InterpolatorType interpolatorType = InterpolatorType::LINEAR,
Andy Hung9fc8b5c2017-01-24 13:36:48 -080047 bool cache = true)
48 : mCache(cache)
49 , mFirstSlope(0)
50 , mLastSlope(0) {
51 setInterpolatorType(interpolatorType);
52 }
53
54 std::pair<S, T> first() const {
55 return *this->begin();
56 }
57
58 std::pair<S, T> last() const {
59 return *this->rbegin();
60 }
61
62 // find the corresponding Y point from a X point.
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) {
Ytai Ben-Tsvif0658f42020-10-26 11:51:14 -070079 case InterpolatorType::STEP:
Andy Hung9fc8b5c2017-01-24 13:36:48 -080080 return high->first == x ? high->second : low->second;
Ytai Ben-Tsvif0658f42020-10-26 11:51:14 -070081 case InterpolatorType::LINEAR:
Andy Hung9fc8b5c2017-01-24 13:36:48 -080082 return ((high->first - x) * low->second + (x - low->first) * high->second)
83 / (high->first - low->first);
Ytai Ben-Tsvif0658f42020-10-26 11:51:14 -070084 case InterpolatorType::CUBIC:
85 case InterpolatorType::CUBIC_MONOTONIC:
Andy Hung9fc8b5c2017-01-24 13:36:48 -080086 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;
Ytai Ben-Tsvif0658f42020-10-26 11:51:14 -0700113 if (mInterpolatorType == InterpolatorType::CUBIC_MONOTONIC) {
Andy Hung9fc8b5c2017-01-24 13:36:48 -0800114 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
Colin Cross11280a12017-05-02 10:32:56 -0700149 m0 = (sec0 + sec) * 0.5f;
150 m1 = (sec1 + sec) * 0.5f;
Andy Hung9fc8b5c2017-01-24 13:36:48 -0800151 }
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
193 InterpolatorType getInterpolatorType() const {
194 return mInterpolatorType;
195 }
196
197 status_t setInterpolatorType(InterpolatorType interpolatorType) {
198 switch (interpolatorType) {
Ytai Ben-Tsvif0658f42020-10-26 11:51:14 -0700199 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:
Andy Hung9fc8b5c2017-01-24 13:36:48 -0800204 mInterpolatorType = interpolatorType;
205 return NO_ERROR;
206 default:
207 ALOGE("invalid interpolatorType: %d", interpolatorType);
208 return BAD_VALUE;
209 }
210 }
211
212 T getFirstSlope() const {
213 return mFirstSlope;
214 }
215
216 void setFirstSlope(T slope) {
217 mFirstSlope = slope;
218 }
219
220 T getLastSlope() const {
221 return mLastSlope;
222 }
223
224 void setLastSlope(T slope) {
225 mLastSlope = slope;
226 }
227
228 void clearCache() {
229 mMemo.clear();
230 }
231
Ytai Ben-Tsvif0658f42020-10-26 11:51:14 -0700232 // TODO(ytai): remove this method once it is not used.
Andy Hung9fc8b5c2017-01-24 13:36:48 -0800233 status_t writeToParcel(Parcel *parcel) const {
Ytai Ben-Tsvif0658f42020-10-26 11:51:14 -0700234 media::InterpolatorConfig config;
235 writeToConfig(&config);
236 return config.writeToParcel(parcel);
Andy Hung9fc8b5c2017-01-24 13:36:48 -0800237 }
238
Ytai Ben-Tsvif0658f42020-10-26 11:51:14 -0700239 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.
Andy Hung9fc8b5c2017-01-24 13:36:48 -0800250 status_t readFromParcel(const Parcel &parcel) {
Ytai Ben-Tsvif0658f42020-10-26 11:51:14 -0700251 media::InterpolatorConfig config;
252 status_t res = config.readFromParcel(&parcel);
Andy Hung9fc8b5c2017-01-24 13:36:48 -0800253 if (res != NO_ERROR) {
254 return res;
255 }
Ytai Ben-Tsvif0658f42020-10-26 11:51:14 -0700256 return readFromConfig(config);
257 }
258
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
Andy Hung9fc8b5c2017-01-24 13:36:48 -0800270 // 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) {
Ytai Ben-Tsvif0658f42020-10-26 11:51:14 -0700274 float x = config.xy[i * 2];
275 float y = config.xy[i * 2 + 1];
Colin Cross1759bb52017-04-28 12:46:17 -0700276 if ((i > 0 && !(x > lastx)) /* handle nan */
Andy Hung9fc8b5c2017-01-24 13:36:48 -0800277 || 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
290 std::string toString() const {
291 std::stringstream ss;
Andy Hungf3702642017-05-05 17:33:32 -0700292 ss << "Interpolator{mInterpolatorType=" << static_cast<int32_t>(mInterpolatorType);
293 ss << ", mFirstSlope=" << mFirstSlope;
294 ss << ", mLastSlope=" << mLastSlope;
295 ss << ", {";
296 bool first = true;
Andy Hung9fc8b5c2017-01-24 13:36:48 -0800297 for (const auto &pt : *this) {
Andy Hungf3702642017-05-05 17:33:32 -0700298 if (first) {
299 first = false;
300 ss << "{";
301 } else {
302 ss << ", {";
303 }
304 ss << pt.first << ", " << pt.second << "}";
Andy Hung9fc8b5c2017-01-24 13:36:48 -0800305 }
Andy Hungf3702642017-05-05 17:33:32 -0700306 ss << "}}";
Andy Hung9fc8b5c2017-01-24 13:36:48 -0800307 return ss.str();
308 }
309
310private:
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;
Andy Hungf3702642017-05-05 17:33:32 -0700331}; // Interpolator
Andy Hung9fc8b5c2017-01-24 13:36:48 -0800332
333} // namespace android
334
335#pragma pop_macro("LOG_TAG")
336
337#endif // ANDROID_INTERPOLATOR_H