You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

338 lines
11 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_INTERPOLATOR_H
#define ANDROID_INTERPOLATOR_H
#include <map>
#include <sstream>
#include <unordered_map>
#include <android/media/InterpolatorConfig.h>
#include <binder/Parcel.h>
#include <utils/RefBase.h>
#pragma push_macro("LOG_TAG")
#undef LOG_TAG
#define LOG_TAG "Interpolator"
namespace android {
/*
* A general purpose spline interpolator class which takes a set of points
* and performs interpolation. This is used for the VolumeShaper class.
*/
template <typename S, typename T>
class Interpolator : public std::map<S, T> {
public:
// Polynomial spline interpolators
using InterpolatorType = media::InterpolatorType;
explicit Interpolator(
InterpolatorType interpolatorType = InterpolatorType::CUBIC,
bool cache = true)
: mCache(cache)
, mFirstSlope(0)
, mLastSlope(0) {
setInterpolatorType(interpolatorType);
}
std::pair<S, T> first() const {
return *this->begin();
}
std::pair<S, T> last() const {
return *this->rbegin();
}
// find the corresponding Y point from a X point.
T findY(S x) { // logically const, but modifies cache
auto high = this->lower_bound(x);
// greater than last point
if (high == this->end()) {
return this->rbegin()->second;
}
// at or before first point
if (high == this->begin()) {
return high->second;
}
// go lower.
auto low = high;
--low;
// now that we have two adjacent points:
switch (mInterpolatorType) {
case InterpolatorType::STEP:
return high->first == x ? high->second : low->second;
case InterpolatorType::LINEAR:
return ((high->first - x) * low->second + (x - low->first) * high->second)
/ (high->first - low->first);
case InterpolatorType::CUBIC:
case InterpolatorType::CUBIC_MONOTONIC:
default: {
// See https://en.wikipedia.org/wiki/Cubic_Hermite_spline
const S interval = high->first - low->first;
// check to see if we've cached the polynomial coefficients
if (mMemo.count(low->first) != 0) {
const S t = (x - low->first) / interval;
const S t2 = t * t;
const auto &memo = mMemo[low->first];
return low->second + std::get<0>(memo) * t
+ (std::get<1>(memo) + std::get<2>(memo) * t) * t2;
}
// find the neighboring points (low2 < low < high < high2)
auto low2 = this->end();
if (low != this->begin()) {
low2 = low;
--low2; // decrementing this->begin() is undefined
}
auto high2 = high;
++high2;
// you could have catmullRom with monotonic or
// non catmullRom (finite difference) with regular cubic;
// the choices here minimize computation.
bool monotonic, catmullRom;
if (mInterpolatorType == InterpolatorType::CUBIC_MONOTONIC) {
monotonic = true;
catmullRom = false;
} else {
monotonic = false;
catmullRom = true;
}
// secants are only needed for finite difference splines or
// monotonic computation.
// we use lazy computation here - if we precompute in
// a single pass, duplicate secant computations may be avoided.
S sec, sec0, sec1;
if (!catmullRom || monotonic) {
sec = (high->second - low->second) / interval;
sec0 = low2 != this->end()
? (low->second - low2->second) / (low->first - low2->first)
: mFirstSlope;
sec1 = high2 != this->end()
? (high2->second - high->second) / (high2->first - high->first)
: mLastSlope;
}
// compute the tangent slopes at the control points
S m0, m1;
if (catmullRom) {
// Catmull-Rom spline
m0 = low2 != this->end()
? (high->second - low2->second) / (high->first - low2->first)
: mFirstSlope;
m1 = high2 != this->end()
? (high2->second - low->second) / (high2->first - low->first)
: mLastSlope;
} else {
// finite difference spline
m0 = (sec0 + sec) * 0.5f;
m1 = (sec1 + sec) * 0.5f;
}
if (monotonic) {
// https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
// A sufficient condition for FritschCarlson monotonicity is constraining
// (1) the normalized slopes to be within the circle of radius 3, or
// (2) the normalized slopes to be within the square of radius 3.
// Condition (2) is more generous and easier to compute.
const S maxSlope = 3 * sec;
m0 = constrainSlope(m0, maxSlope);
m1 = constrainSlope(m1, maxSlope);
m0 = constrainSlope(m0, 3 * sec0);
m1 = constrainSlope(m1, 3 * sec1);
}
const S t = (x - low->first) / interval;
const S t2 = t * t;
if (mCache) {
// convert to cubic polynomial coefficients and compute
m0 *= interval;
m1 *= interval;
const T dy = high->second - low->second;
const S c0 = low->second;
const S c1 = m0;
const S c2 = 3 * dy - 2 * m0 - m1;
const S c3 = m0 + m1 - 2 * dy;
mMemo[low->first] = std::make_tuple(c1, c2, c3);
return c0 + c1 * t + (c2 + c3 * t) * t2;
} else {
// classic Hermite interpolation
const S t3 = t2 * t;
const S h00 = 2 * t3 - 3 * t2 + 1;
const S h10 = t3 - 2 * t2 + t ;
const S h01 = -2 * t3 + 3 * t2 ;
const S h11 = t3 - t2 ;
return h00 * low->second + (h10 * m0 + h11 * m1) * interval + h01 * high->second;
}
} // default
}
}
InterpolatorType getInterpolatorType() const {
return mInterpolatorType;
}
status_t setInterpolatorType(InterpolatorType interpolatorType) {
switch (interpolatorType) {
case InterpolatorType::STEP: // Not continuous
case InterpolatorType::LINEAR: // C0
case InterpolatorType::CUBIC: // C1
case InterpolatorType::CUBIC_MONOTONIC: // C1 + other constraints
// case InterpolatorType::CUBIC_C2:
mInterpolatorType = interpolatorType;
return NO_ERROR;
default:
ALOGE("invalid interpolatorType: %d", interpolatorType);
return BAD_VALUE;
}
}
T getFirstSlope() const {
return mFirstSlope;
}
void setFirstSlope(T slope) {
mFirstSlope = slope;
}
T getLastSlope() const {
return mLastSlope;
}
void setLastSlope(T slope) {
mLastSlope = slope;
}
void clearCache() {
mMemo.clear();
}
// TODO(ytai): remove this method once it is not used.
status_t writeToParcel(Parcel *parcel) const {
media::InterpolatorConfig config;
writeToConfig(&config);
return config.writeToParcel(parcel);
}
void writeToConfig(media::InterpolatorConfig *config) const {
config->type = mInterpolatorType;
config->firstSlope = mFirstSlope;
config->lastSlope = mLastSlope;
for (const auto &pt : *this) {
config->xy.push_back(pt.first);
config->xy.push_back(pt.second);
}
}
// TODO(ytai): remove this method once it is not used.
status_t readFromParcel(const Parcel &parcel) {
media::InterpolatorConfig config;
status_t res = config.readFromParcel(&parcel);
if (res != NO_ERROR) {
return res;
}
return readFromConfig(config);
}
status_t readFromConfig(const media::InterpolatorConfig &config) {
this->clear();
setInterpolatorType(config.type);
if ((config.xy.size() & 1) != 0) {
// xy size must be even.
return BAD_VALUE;
}
uint32_t size = config.xy.size() / 2;
mFirstSlope = config.firstSlope;
mLastSlope = config.lastSlope;
// Note: We don't need to check size is within some bounds as
// the Parcel read will fail if size is incorrectly specified too large.
float lastx;
for (uint32_t i = 0; i < size; ++i) {
float x = config.xy[i * 2];
float y = config.xy[i * 2 + 1];
if ((i > 0 && !(x > lastx)) /* handle nan */
|| y != y /* handle nan */) {
// This is a std::map object which imposes sorted order
// automatically on emplace.
// Nevertheless for reading from a Parcel,
// we require that the points be specified monotonic in x.
return BAD_VALUE;
}
this->emplace(x, y);
lastx = x;
}
return NO_ERROR;
}
std::string toString() const {
std::stringstream ss;
ss << "Interpolator{mInterpolatorType=" << static_cast<int32_t>(mInterpolatorType);
ss << ", mFirstSlope=" << mFirstSlope;
ss << ", mLastSlope=" << mLastSlope;
ss << ", {";
bool first = true;
for (const auto &pt : *this) {
if (first) {
first = false;
ss << "{";
} else {
ss << ", {";
}
ss << pt.first << ", " << pt.second << "}";
}
ss << "}}";
return ss.str();
}
private:
static S constrainSlope(S slope, S maxSlope) {
if (maxSlope > 0) {
slope = std::min(slope, maxSlope);
slope = std::max(slope, S(0)); // not globally monotonic
} else {
slope = std::max(slope, maxSlope);
slope = std::min(slope, S(0)); // not globally monotonic
}
return slope;
}
InterpolatorType mInterpolatorType;
bool mCache; // whether we cache spline coefficient computation
// for cubic interpolation, the boundary conditions in slope.
S mFirstSlope;
S mLastSlope;
// spline cubic polynomial coefficient cache
std::unordered_map<S, std::tuple<S /* c1 */, S /* c2 */, S /* c3 */>> mMemo;
}; // Interpolator
} // namespace android
#pragma pop_macro("LOG_TAG")
#endif // ANDROID_INTERPOLATOR_H