From 270e3381e7053c3b15aa8f508c9df9d98032cd62 Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Thu, 16 Aug 2012 01:30:22 -0700 Subject: [PATCH 1/2] Add FloatMath.hypot. Change-Id: I6a5a7ea2254300614dbbf540f40e39dbec2d2900 --- api/current.txt | 1 + core/java/android/util/FloatMath.java | 10 ++++++++++ core/jni/android_util_FloatMath.cpp | 5 +++++ 3 files changed, 16 insertions(+) diff --git a/api/current.txt b/api/current.txt index 9b5481407d5ac..860ae0907f3c1 100644 --- a/api/current.txt +++ b/api/current.txt @@ -22876,6 +22876,7 @@ package android.util { method public static float cos(float); method public static float exp(float); method public static float floor(float); + method public static float hypot(float, float); method public static float sin(float); method public static float sqrt(float); } diff --git a/core/java/android/util/FloatMath.java b/core/java/android/util/FloatMath.java index 1d4eda41473c7..e05169ad6ddfb 100644 --- a/core/java/android/util/FloatMath.java +++ b/core/java/android/util/FloatMath.java @@ -80,4 +80,14 @@ public class FloatMath { * @return the exponential of value */ public static native float exp(float value); + + /** + * Returns {@code sqrt(}{@code x}{@code 2}{@code +} + * {@code y}{@code 2}{@code )}. + * + * @param x a float number + * @param y a float number + * @return the hypotenuse + */ + public static native float hypot(float x, float y); } diff --git a/core/jni/android_util_FloatMath.cpp b/core/jni/android_util_FloatMath.cpp index e30756b5dee3b..529fbe9b64a88 100644 --- a/core/jni/android_util_FloatMath.cpp +++ b/core/jni/android_util_FloatMath.cpp @@ -29,6 +29,10 @@ public: static float ExpF(JNIEnv* env, jobject clazz, float x) { return expf(x); } + + static float HypotF(JNIEnv* env, jobject clazz, float x, float y) { + return hypotf(x, y); + } }; static JNINativeMethod gMathUtilsMethods[] = { @@ -38,6 +42,7 @@ static JNINativeMethod gMathUtilsMethods[] = { {"cos", "(F)F", (void*) MathUtilsGlue::CosF}, {"sqrt", "(F)F", (void*) MathUtilsGlue::SqrtF}, {"exp", "(F)F", (void*) MathUtilsGlue::ExpF}, + {"hypot", "(FF)F", (void*) MathUtilsGlue::HypotF}, }; int register_android_util_FloatMath(JNIEnv* env) From 1a30b55036c2279d72ba69cb1107ec5f6f40d5e9 Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Thu, 16 Aug 2012 01:31:11 -0700 Subject: [PATCH 2/2] Use spline interpolation for auto-brightness. Strictly speaking, this is a change in behavior for all products. Instead of using discrete zones, they will all now use spline interpolation. We could make this behavior configurable but there seems to be little point to it. The range of brightness values used will be more or less the same as before, it's just that what used to be the brightness value for all levels within a particular zone now becomes the brightness value for the highest level in that zone and lower values are used for lower levels within the zone. Change-Id: I39804ee630ba55f018e1e53c0576b28e7bd27931 --- core/java/android/util/Spline.java | 156 ++++++++++++++++++ core/res/res/values/config.xml | 22 ++- .../server/power/DisplayPowerController.java | 82 +++++---- 3 files changed, 219 insertions(+), 41 deletions(-) create mode 100644 core/java/android/util/Spline.java diff --git a/core/java/android/util/Spline.java b/core/java/android/util/Spline.java new file mode 100644 index 0000000000000..ed027eb18166e --- /dev/null +++ b/core/java/android/util/Spline.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2012 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. + */ + +package android.util; + +/** + * Performs spline interpolation given a set of control points. + * @hide + */ +public final class Spline { + private final float[] mX; + private final float[] mY; + private final float[] mM; + + private Spline(float[] x, float[] y, float[] m) { + mX = x; + mY = y; + mM = m; + } + + /** + * Creates a monotone cubic spline from a given set of control points. + * + * The spline is guaranteed to pass through each control point exactly. + * Moreover, assuming the control points are monotonic (Y is non-decreasing or + * non-increasing) then the interpolated values will also be monotonic. + * + * This function uses the Fritsch-Carlson method for computing the spline parameters. + * http://en.wikipedia.org/wiki/Monotone_cubic_interpolation + * + * @param x The X component of the control points, strictly increasing. + * @param y The Y component of the control points, monotonic. + * @return + * + * @throws IllegalArgumentException if the X or Y arrays are null, have + * different lengths or have fewer than 2 values. + * @throws IllegalArgumentException if the control points are not monotonic. + */ + public static Spline createMonotoneCubicSpline(float[] x, float[] y) { + if (x == null || y == null || x.length != y.length || x.length < 2) { + throw new IllegalArgumentException("There must be at least two control " + + "points and the arrays must be of equal length."); + } + + final int n = x.length; + float[] d = new float[n - 1]; // could optimize this out + float[] m = new float[n]; + + // Compute slopes of secant lines between successive points. + for (int i = 0; i < n - 1; i++) { + float h = x[i + 1] - x[i]; + if (h <= 0f) { + throw new IllegalArgumentException("The control points must all " + + "have strictly increasing X values."); + } + d[i] = (y[i + 1] - y[i]) / h; + } + + // Initialize the tangents as the average of the secants. + m[0] = d[0]; + for (int i = 1; i < n - 1; i++) { + m[i] = (d[i - 1] + d[i]) * 0.5f; + } + m[n - 1] = d[n - 2]; + + // Update the tangents to preserve monotonicity. + for (int i = 0; i < n - 1; i++) { + if (d[i] == 0f) { // successive Y values are equal + m[i] = 0f; + m[i + 1] = 0f; + } else { + float a = m[i] / d[i]; + float b = m[i + 1] / d[i]; + if (a < 0f || b < 0f) { + throw new IllegalArgumentException("The control points must have " + + "monotonic Y values."); + } + float h = FloatMath.hypot(a, b); + if (h > 9f) { + float t = 3f / h; + m[i] = t * a * d[i]; + m[i + 1] = t * b * d[i]; + } + } + } + return new Spline(x, y, m); + } + + /** + * Interpolates the value of Y = f(X) for given X. + * Clamps X to the domain of the spline. + * + * @param x The X value. + * @return The interpolated Y = f(X) value. + */ + public float interpolate(float x) { + // Handle the boundary cases. + final int n = mX.length; + if (Float.isNaN(x)) { + return x; + } + if (x <= mX[0]) { + return mY[0]; + } + if (x >= mX[n - 1]) { + return mY[n - 1]; + } + + // Find the index 'i' of the last point with smaller X. + // We know this will be within the spline due to the boundary tests. + int i = 0; + while (x >= mX[i + 1]) { + i += 1; + if (x == mX[i]) { + return mY[i]; + } + } + + // Perform cubic Hermite spline interpolation. + float h = mX[i + 1] - mX[i]; + float t = (x - mX[i]) / h; + return (mY[i] * (1 + 2 * t) + h * mM[i] * t) * (1 - t) * (1 - t) + + (mY[i + 1] * (3 - 2 * t) + h * mM[i + 1] * (t - 1)) * t * t; + } + + // For debugging. + @Override + public String toString() { + StringBuilder str = new StringBuilder(); + final int n = mX.length; + str.append("["); + for (int i = 0; i < n; i++) { + if (i != 0) { + str.append(", "); + } + str.append("(").append(mX[i]); + str.append(", ").append(mY[i]); + str.append(": ").append(mM[i]).append(")"); + } + str.append("]"); + return str.toString(); + } +} diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index ee0ff8ed04500..e3c957beb7e94 100755 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -533,13 +533,22 @@ 2 @@ -552,6 +561,7 @@ @@ -559,6 +569,7 @@ @@ -566,6 +577,7 @@ diff --git a/services/java/com/android/server/power/DisplayPowerController.java b/services/java/com/android/server/power/DisplayPowerController.java index 54e2b574a59c8..b0c79fa13d95c 100644 --- a/services/java/com/android/server/power/DisplayPowerController.java +++ b/services/java/com/android/server/power/DisplayPowerController.java @@ -33,11 +33,11 @@ import android.os.Looper; import android.os.Message; import android.os.SystemClock; import android.util.Slog; +import android.util.Spline; import android.util.TimeUtils; import java.io.PrintWriter; import java.io.StringWriter; -import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; @@ -98,9 +98,9 @@ final class DisplayPowerController { // average of light samples. Different constants are used // to calculate the average light level when adapting to brighter or // dimmer environments. - // This parameter only controls the averaging of light samples. - private static final long BRIGHTENING_LIGHT_TIME_CONSTANT = 1500; - private static final long DIMMING_LIGHT_TIME_CONSTANT = 3000; + // This parameter only controls the filtering of light samples. + private static final long BRIGHTENING_LIGHT_TIME_CONSTANT = 500; + private static final long DIMMING_LIGHT_TIME_CONSTANT = 2000; // Stability requirements in milliseconds for accepting a new brightness // level. This is used for debouncing the light sensor. Different constants @@ -144,8 +144,7 @@ final class DisplayPowerController { // Auto-brightness. private boolean mUseSoftwareAutoBrightnessConfig; - private int[] mAutoBrightnessLevelsConfig; - private int[] mAutoBrightnessLcdBacklightValuesConfig; + private Spline mScreenAutoBrightnessSpline; // Amount of time to delay auto-brightness after screen on while waiting for // the light sensor to warm-up in milliseconds. @@ -289,17 +288,18 @@ final class DisplayPowerController { mUseSoftwareAutoBrightnessConfig = resources.getBoolean( com.android.internal.R.bool.config_automatic_brightness_available); if (mUseSoftwareAutoBrightnessConfig) { - mAutoBrightnessLevelsConfig = resources.getIntArray( + int[] lux = resources.getIntArray( com.android.internal.R.array.config_autoBrightnessLevels); - mAutoBrightnessLcdBacklightValuesConfig = resources.getIntArray( + int[] screenBrightness = resources.getIntArray( com.android.internal.R.array.config_autoBrightnessLcdBacklightValues); - if (mAutoBrightnessLcdBacklightValuesConfig.length - != mAutoBrightnessLevelsConfig.length + 1) { + + mScreenAutoBrightnessSpline = createAutoBrightnessSpline(lux, screenBrightness); + if (mScreenAutoBrightnessSpline == null) { Slog.e(TAG, "Error in config.xml. config_autoBrightnessLcdBacklightValues " - + "(size " + mAutoBrightnessLcdBacklightValuesConfig.length + ") " - + "should have exactly one more entry than " - + "config_autoBrightnessLevels (size " - + mAutoBrightnessLevelsConfig.length + "). " + + "(size " + screenBrightness.length + ") " + + "must be monotic and have exactly one more entry than " + + "config_autoBrightnessLevels (size " + lux.length + ") " + + "which must be strictly increasing. " + "Auto-brightness will be disabled."); mUseSoftwareAutoBrightnessConfig = false; } @@ -322,6 +322,31 @@ final class DisplayPowerController { } } + private static Spline createAutoBrightnessSpline(int[] lux, int[] brightness) { + try { + final int n = brightness.length; + float[] x = new float[n]; + float[] y = new float[n]; + y[0] = brightness[0]; + for (int i = 1; i < n; i++) { + x[i] = lux[i - 1]; + y[i] = brightness[i]; + } + + Spline spline = Spline.createMonotoneCubicSpline(x, y); + if (false) { + Slog.d(TAG, "Auto-brightness spline: " + spline); + for (float v = 1f; v < lux[lux.length - 1] * 1.25f; v *= 1.25f) { + Slog.d(TAG, String.format(" %7.1f: %7.1f", v, spline.interpolate(v))); + } + } + return spline; + } catch (IllegalArgumentException ex) { + Slog.e(TAG, "Could not create auto-brightness spline.", ex); + return null; + } + } + /** * Returns true if the proximity sensor screen-off function is available. */ @@ -768,13 +793,13 @@ final class DisplayPowerController { return; } - final int newScreenAutoBrightness = mapLuxToBrightness(mLightMeasurement, - mAutoBrightnessLevelsConfig, - mAutoBrightnessLcdBacklightValuesConfig); + final int newScreenAutoBrightness = interpolateBrightness( + mScreenAutoBrightnessSpline, mLightMeasurement); if (mScreenAutoBrightness != newScreenAutoBrightness) { if (DEBUG) { Slog.d(TAG, "updateAutoBrightness: mScreenAutoBrightness=" - + mScreenAutoBrightness); + + mScreenAutoBrightness + "newScreenAutoBrightness=" + + newScreenAutoBrightness); } mScreenAutoBrightness = newScreenAutoBrightness; @@ -784,20 +809,8 @@ final class DisplayPowerController { } } - /** - * Maps a light sensor measurement in lux to a brightness value given - * a table of lux breakpoint values and a table of brightnesses that - * is one element larger. - */ - private static int mapLuxToBrightness(float lux, - int[] fromLux, int[] toBrightness) { - // TODO implement interpolation and possibly range expansion - int level = 0; - final int count = fromLux.length; - while (level < count && lux >= fromLux[level]) { - level += 1; - } - return toBrightness[level]; + private static int interpolateBrightness(Spline spline, float lux) { + return Math.min(255, Math.max(0, (int)Math.round(spline.interpolate(lux)))); } private void sendOnStateChanged() { @@ -839,10 +852,7 @@ final class DisplayPowerController { pw.println(" mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig); pw.println(" mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig); - pw.println(" mAutoBrightnessLevelsConfig=" - + Arrays.toString(mAutoBrightnessLevelsConfig)); - pw.println(" mAutoBrightnessLcdBacklightValuesConfig=" - + Arrays.toString(mAutoBrightnessLcdBacklightValuesConfig)); + pw.println(" mScreenAutoBrightnessSpline=" + mScreenAutoBrightnessSpline); pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig); if (Looper.myLooper() == mHandler.getLooper()) {