Skip to content

Commit 1a30b55

Browse files
author
Jeff Brown
committed
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
1 parent 270e338 commit 1a30b55

File tree

3 files changed

+219
-41
lines changed

3 files changed

+219
-41
lines changed

core/java/android/util/Spline.java

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* Copyright (C) 2012 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+
package android.util;
18+
19+
/**
20+
* Performs spline interpolation given a set of control points.
21+
* @hide
22+
*/
23+
public final class Spline {
24+
private final float[] mX;
25+
private final float[] mY;
26+
private final float[] mM;
27+
28+
private Spline(float[] x, float[] y, float[] m) {
29+
mX = x;
30+
mY = y;
31+
mM = m;
32+
}
33+
34+
/**
35+
* Creates a monotone cubic spline from a given set of control points.
36+
*
37+
* The spline is guaranteed to pass through each control point exactly.
38+
* Moreover, assuming the control points are monotonic (Y is non-decreasing or
39+
* non-increasing) then the interpolated values will also be monotonic.
40+
*
41+
* This function uses the Fritsch-Carlson method for computing the spline parameters.
42+
* http://en.wikipedia.org/wiki/Monotone_cubic_interpolation
43+
*
44+
* @param x The X component of the control points, strictly increasing.
45+
* @param y The Y component of the control points, monotonic.
46+
* @return
47+
*
48+
* @throws IllegalArgumentException if the X or Y arrays are null, have
49+
* different lengths or have fewer than 2 values.
50+
* @throws IllegalArgumentException if the control points are not monotonic.
51+
*/
52+
public static Spline createMonotoneCubicSpline(float[] x, float[] y) {
53+
if (x == null || y == null || x.length != y.length || x.length < 2) {
54+
throw new IllegalArgumentException("There must be at least two control "
55+
+ "points and the arrays must be of equal length.");
56+
}
57+
58+
final int n = x.length;
59+
float[] d = new float[n - 1]; // could optimize this out
60+
float[] m = new float[n];
61+
62+
// Compute slopes of secant lines between successive points.
63+
for (int i = 0; i < n - 1; i++) {
64+
float h = x[i + 1] - x[i];
65+
if (h <= 0f) {
66+
throw new IllegalArgumentException("The control points must all "
67+
+ "have strictly increasing X values.");
68+
}
69+
d[i] = (y[i + 1] - y[i]) / h;
70+
}
71+
72+
// Initialize the tangents as the average of the secants.
73+
m[0] = d[0];
74+
for (int i = 1; i < n - 1; i++) {
75+
m[i] = (d[i - 1] + d[i]) * 0.5f;
76+
}
77+
m[n - 1] = d[n - 2];
78+
79+
// Update the tangents to preserve monotonicity.
80+
for (int i = 0; i < n - 1; i++) {
81+
if (d[i] == 0f) { // successive Y values are equal
82+
m[i] = 0f;
83+
m[i + 1] = 0f;
84+
} else {
85+
float a = m[i] / d[i];
86+
float b = m[i + 1] / d[i];
87+
if (a < 0f || b < 0f) {
88+
throw new IllegalArgumentException("The control points must have "
89+
+ "monotonic Y values.");
90+
}
91+
float h = FloatMath.hypot(a, b);
92+
if (h > 9f) {
93+
float t = 3f / h;
94+
m[i] = t * a * d[i];
95+
m[i + 1] = t * b * d[i];
96+
}
97+
}
98+
}
99+
return new Spline(x, y, m);
100+
}
101+
102+
/**
103+
* Interpolates the value of Y = f(X) for given X.
104+
* Clamps X to the domain of the spline.
105+
*
106+
* @param x The X value.
107+
* @return The interpolated Y = f(X) value.
108+
*/
109+
public float interpolate(float x) {
110+
// Handle the boundary cases.
111+
final int n = mX.length;
112+
if (Float.isNaN(x)) {
113+
return x;
114+
}
115+
if (x <= mX[0]) {
116+
return mY[0];
117+
}
118+
if (x >= mX[n - 1]) {
119+
return mY[n - 1];
120+
}
121+
122+
// Find the index 'i' of the last point with smaller X.
123+
// We know this will be within the spline due to the boundary tests.
124+
int i = 0;
125+
while (x >= mX[i + 1]) {
126+
i += 1;
127+
if (x == mX[i]) {
128+
return mY[i];
129+
}
130+
}
131+
132+
// Perform cubic Hermite spline interpolation.
133+
float h = mX[i + 1] - mX[i];
134+
float t = (x - mX[i]) / h;
135+
return (mY[i] * (1 + 2 * t) + h * mM[i] * t) * (1 - t) * (1 - t)
136+
+ (mY[i + 1] * (3 - 2 * t) + h * mM[i + 1] * (t - 1)) * t * t;
137+
}
138+
139+
// For debugging.
140+
@Override
141+
public String toString() {
142+
StringBuilder str = new StringBuilder();
143+
final int n = mX.length;
144+
str.append("[");
145+
for (int i = 0; i < n; i++) {
146+
if (i != 0) {
147+
str.append(", ");
148+
}
149+
str.append("(").append(mX[i]);
150+
str.append(", ").append(mY[i]);
151+
str.append(": ").append(mM[i]).append(")");
152+
}
153+
str.append("]");
154+
return str.toString();
155+
}
156+
}

core/res/res/values/config.xml

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -533,13 +533,22 @@
533533
<integer name="config_longPressOnHomeBehavior">2</integer>
534534

535535
<!-- Array of light sensor LUX values to define our levels for auto backlight brightness support.
536-
The N entries of this array define N + 1 zones as follows:
536+
The N entries of this array define N + 1 control points as follows:
537537
538-
Zone 0: 0 <= LUX < array[0]
539-
Zone 1: array[0] <= LUX < array[1]
538+
Point 1: LUX <= 0 (implicit)
539+
Point 2: 0 < level[1] == LUX < level[2]
540540
...
541-
Zone N: array[N - 1] <= LUX < array[N]
542-
Zone N + 1: array[N] <= LUX < infinity
541+
Point N: level[N - 1] == LUX < level[N]
542+
Point N + 1: level[N] <= LUX < infinity
543+
544+
The control points must be strictly increasing. Each control point
545+
corresponds to an entry in the brightness backlight values arrays.
546+
For example, if LUX == level[1] (first element of the levels array)
547+
then the brightness will be determined by value[1] (first element
548+
of the brightness values array).
549+
550+
Spline interpolation is used to determine the auto-brightness
551+
backlight values for LUX levels between these control points.
543552
544553
Must be overridden in platform specific overlays -->
545554
<integer-array name="config_autoBrightnessLevels">
@@ -552,20 +561,23 @@
552561
<!-- Array of output values for LCD backlight corresponding to the LUX values
553562
in the config_autoBrightnessLevels array. This array should have size one greater
554563
than the size of the config_autoBrightnessLevels array.
564+
The brightness values must be between 0 and 255 and be non-decreasing.
555565
This must be overridden in platform specific overlays -->
556566
<integer-array name="config_autoBrightnessLcdBacklightValues">
557567
</integer-array>
558568

559569
<!-- Array of output values for button backlight corresponding to the LUX values
560570
in the config_autoBrightnessLevels array. This array should have size one greater
561571
than the size of the config_autoBrightnessLevels array.
572+
The brightness values must be between 0 and 255 and be non-decreasing.
562573
This must be overridden in platform specific overlays -->
563574
<integer-array name="config_autoBrightnessButtonBacklightValues">
564575
</integer-array>
565576

566577
<!-- Array of output values for keyboard backlight corresponding to the LUX values
567578
in the config_autoBrightnessLevels array. This array should have size one greater
568579
than the size of the config_autoBrightnessLevels array.
580+
The brightness values must be between 0 and 255 and be non-decreasing.
569581
This must be overridden in platform specific overlays -->
570582
<integer-array name="config_autoBrightnessKeyboardBacklightValues">
571583
</integer-array>

services/java/com/android/server/power/DisplayPowerController.java

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@
3333
import android.os.Message;
3434
import android.os.SystemClock;
3535
import android.util.Slog;
36+
import android.util.Spline;
3637
import android.util.TimeUtils;
3738

3839
import java.io.PrintWriter;
3940
import java.io.StringWriter;
40-
import java.util.Arrays;
4141
import java.util.concurrent.CountDownLatch;
4242
import java.util.concurrent.Executor;
4343

@@ -98,9 +98,9 @@ final class DisplayPowerController {
9898
// average of light samples. Different constants are used
9999
// to calculate the average light level when adapting to brighter or
100100
// dimmer environments.
101-
// This parameter only controls the averaging of light samples.
102-
private static final long BRIGHTENING_LIGHT_TIME_CONSTANT = 1500;
103-
private static final long DIMMING_LIGHT_TIME_CONSTANT = 3000;
101+
// This parameter only controls the filtering of light samples.
102+
private static final long BRIGHTENING_LIGHT_TIME_CONSTANT = 500;
103+
private static final long DIMMING_LIGHT_TIME_CONSTANT = 2000;
104104

105105
// Stability requirements in milliseconds for accepting a new brightness
106106
// level. This is used for debouncing the light sensor. Different constants
@@ -144,8 +144,7 @@ final class DisplayPowerController {
144144

145145
// Auto-brightness.
146146
private boolean mUseSoftwareAutoBrightnessConfig;
147-
private int[] mAutoBrightnessLevelsConfig;
148-
private int[] mAutoBrightnessLcdBacklightValuesConfig;
147+
private Spline mScreenAutoBrightnessSpline;
149148

150149
// Amount of time to delay auto-brightness after screen on while waiting for
151150
// the light sensor to warm-up in milliseconds.
@@ -289,17 +288,18 @@ public DisplayPowerController(Looper looper, Context context, Notifier notifier,
289288
mUseSoftwareAutoBrightnessConfig = resources.getBoolean(
290289
com.android.internal.R.bool.config_automatic_brightness_available);
291290
if (mUseSoftwareAutoBrightnessConfig) {
292-
mAutoBrightnessLevelsConfig = resources.getIntArray(
291+
int[] lux = resources.getIntArray(
293292
com.android.internal.R.array.config_autoBrightnessLevels);
294-
mAutoBrightnessLcdBacklightValuesConfig = resources.getIntArray(
293+
int[] screenBrightness = resources.getIntArray(
295294
com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
296-
if (mAutoBrightnessLcdBacklightValuesConfig.length
297-
!= mAutoBrightnessLevelsConfig.length + 1) {
295+
296+
mScreenAutoBrightnessSpline = createAutoBrightnessSpline(lux, screenBrightness);
297+
if (mScreenAutoBrightnessSpline == null) {
298298
Slog.e(TAG, "Error in config.xml. config_autoBrightnessLcdBacklightValues "
299-
+ "(size " + mAutoBrightnessLcdBacklightValuesConfig.length + ") "
300-
+ "should have exactly one more entry than "
301-
+ "config_autoBrightnessLevels (size "
302-
+ mAutoBrightnessLevelsConfig.length + "). "
299+
+ "(size " + screenBrightness.length + ") "
300+
+ "must be monotic and have exactly one more entry than "
301+
+ "config_autoBrightnessLevels (size " + lux.length + ") "
302+
+ "which must be strictly increasing. "
303303
+ "Auto-brightness will be disabled.");
304304
mUseSoftwareAutoBrightnessConfig = false;
305305
}
@@ -322,6 +322,31 @@ public DisplayPowerController(Looper looper, Context context, Notifier notifier,
322322
}
323323
}
324324

325+
private static Spline createAutoBrightnessSpline(int[] lux, int[] brightness) {
326+
try {
327+
final int n = brightness.length;
328+
float[] x = new float[n];
329+
float[] y = new float[n];
330+
y[0] = brightness[0];
331+
for (int i = 1; i < n; i++) {
332+
x[i] = lux[i - 1];
333+
y[i] = brightness[i];
334+
}
335+
336+
Spline spline = Spline.createMonotoneCubicSpline(x, y);
337+
if (false) {
338+
Slog.d(TAG, "Auto-brightness spline: " + spline);
339+
for (float v = 1f; v < lux[lux.length - 1] * 1.25f; v *= 1.25f) {
340+
Slog.d(TAG, String.format(" %7.1f: %7.1f", v, spline.interpolate(v)));
341+
}
342+
}
343+
return spline;
344+
} catch (IllegalArgumentException ex) {
345+
Slog.e(TAG, "Could not create auto-brightness spline.", ex);
346+
return null;
347+
}
348+
}
349+
325350
/**
326351
* Returns true if the proximity sensor screen-off function is available.
327352
*/
@@ -768,13 +793,13 @@ private void updateAutoBrightness(boolean sendUpdate) {
768793
return;
769794
}
770795

771-
final int newScreenAutoBrightness = mapLuxToBrightness(mLightMeasurement,
772-
mAutoBrightnessLevelsConfig,
773-
mAutoBrightnessLcdBacklightValuesConfig);
796+
final int newScreenAutoBrightness = interpolateBrightness(
797+
mScreenAutoBrightnessSpline, mLightMeasurement);
774798
if (mScreenAutoBrightness != newScreenAutoBrightness) {
775799
if (DEBUG) {
776800
Slog.d(TAG, "updateAutoBrightness: mScreenAutoBrightness="
777-
+ mScreenAutoBrightness);
801+
+ mScreenAutoBrightness + "newScreenAutoBrightness="
802+
+ newScreenAutoBrightness);
778803
}
779804

780805
mScreenAutoBrightness = newScreenAutoBrightness;
@@ -784,20 +809,8 @@ private void updateAutoBrightness(boolean sendUpdate) {
784809
}
785810
}
786811

787-
/**
788-
* Maps a light sensor measurement in lux to a brightness value given
789-
* a table of lux breakpoint values and a table of brightnesses that
790-
* is one element larger.
791-
*/
792-
private static int mapLuxToBrightness(float lux,
793-
int[] fromLux, int[] toBrightness) {
794-
// TODO implement interpolation and possibly range expansion
795-
int level = 0;
796-
final int count = fromLux.length;
797-
while (level < count && lux >= fromLux[level]) {
798-
level += 1;
799-
}
800-
return toBrightness[level];
812+
private static int interpolateBrightness(Spline spline, float lux) {
813+
return Math.min(255, Math.max(0, (int)Math.round(spline.interpolate(lux))));
801814
}
802815

803816
private void sendOnStateChanged() {
@@ -839,10 +852,7 @@ public void dump(PrintWriter pw) {
839852
pw.println(" mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig);
840853
pw.println(" mUseSoftwareAutoBrightnessConfig="
841854
+ mUseSoftwareAutoBrightnessConfig);
842-
pw.println(" mAutoBrightnessLevelsConfig="
843-
+ Arrays.toString(mAutoBrightnessLevelsConfig));
844-
pw.println(" mAutoBrightnessLcdBacklightValuesConfig="
845-
+ Arrays.toString(mAutoBrightnessLcdBacklightValuesConfig));
855+
pw.println(" mScreenAutoBrightnessSpline=" + mScreenAutoBrightnessSpline);
846856
pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig);
847857

848858
if (Looper.myLooper() == mHandler.getLooper()) {

0 commit comments

Comments
 (0)