Home | History | Annotate | Download | only in display
      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 package com.android.server.display;
     18 
     19 import static org.junit.Assert.assertEquals;
     20 import static org.junit.Assert.assertNotEquals;
     21 import static org.junit.Assert.assertNotNull;
     22 import static org.junit.Assert.assertNull;
     23 import static org.junit.Assert.assertTrue;
     24 import static org.mockito.ArgumentMatchers.anyFloat;
     25 import static org.mockito.ArgumentMatchers.anyInt;
     26 import static org.mockito.Mockito.mock;
     27 import static org.mockito.Mockito.when;
     28 
     29 import android.content.res.Resources;
     30 import android.content.res.TypedArray;
     31 import android.hardware.display.BrightnessConfiguration;
     32 import android.os.PowerManager;
     33 import android.support.test.filters.SmallTest;
     34 import android.support.test.runner.AndroidJUnit4;
     35 import android.util.MathUtils;
     36 import android.util.Spline;
     37 import android.util.Slog;
     38 
     39 import org.junit.Test;
     40 import org.junit.runner.RunWith;
     41 
     42 import java.util.Arrays;
     43 
     44 @SmallTest
     45 @RunWith(AndroidJUnit4.class)
     46 public class BrightnessMappingStrategyTest {
     47 
     48     private static final int[] LUX_LEVELS = {
     49         0,
     50         5,
     51         20,
     52         40,
     53         100,
     54         325,
     55         600,
     56         1250,
     57         2200,
     58         4000,
     59         5000
     60     };
     61 
     62     private static final float[] DISPLAY_LEVELS_NITS = {
     63         13.25f,
     64         54.0f,
     65         78.85f,
     66         105.02f,
     67         132.7f,
     68         170.12f,
     69         212.1f,
     70         265.2f,
     71         335.8f,
     72         415.2f,
     73         478.5f,
     74     };
     75 
     76     private static final int[] DISPLAY_LEVELS_BACKLIGHT = {
     77         9,
     78         30,
     79         45,
     80         62,
     81         78,
     82         96,
     83         119,
     84         146,
     85         178,
     86         221,
     87         255
     88     };
     89 
     90     private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f };
     91     private static final int[] BACKLIGHT_RANGE = { 1, 255 };
     92 
     93     private static final float[] EMPTY_FLOAT_ARRAY = new float[0];
     94     private static final int[] EMPTY_INT_ARRAY = new int[0];
     95 
     96     private static final float MAXIMUM_GAMMA = 3.0f;
     97     private static final int[] GAMMA_CORRECTION_LUX = {
     98         0,
     99         100,
    100         1000,
    101         2500,
    102         4000,
    103         4900,
    104         5000
    105     };
    106     private static final float[] GAMMA_CORRECTION_NITS = {
    107         1.0f,
    108         10.55f,
    109         96.5f,
    110         239.75f,
    111         383.0f,
    112         468.95f,
    113         478.5f,
    114     };
    115     private static final Spline GAMMA_CORRECTION_SPLINE = Spline.createSpline(
    116             new float[] { 0.0f, 100.0f, 1000.0f, 2500.0f, 4000.0f, 4900.0f, 5000.0f },
    117             new float[] { 0.035f, 0.035f, 0.221f, 0.523f, 0.797f, 0.980f, 1.0f });
    118 
    119     @Test
    120     public void testSimpleStrategyMappingAtControlPoints() {
    121         Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
    122         BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res);
    123         assertNotNull("BrightnessMappingStrategy should not be null", simple);
    124         for (int i = 0; i < LUX_LEVELS.length; i++) {
    125             final float expectedLevel =
    126                     (float) DISPLAY_LEVELS_BACKLIGHT[i] / PowerManager.BRIGHTNESS_ON;
    127             assertEquals(expectedLevel,
    128                     simple.getBrightness(LUX_LEVELS[i]), 0.01f /*tolerance*/);
    129         }
    130     }
    131 
    132     @Test
    133     public void testSimpleStrategyMappingBetweenControlPoints() {
    134         Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
    135         BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res);
    136         assertNotNull("BrightnessMappingStrategy should not be null", simple);
    137         for (int i = 1; i < LUX_LEVELS.length; i++) {
    138             final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2;
    139             final float backlight = simple.getBrightness(lux) * PowerManager.BRIGHTNESS_ON;
    140             assertTrue("Desired brightness should be between adjacent control points.",
    141                     backlight > DISPLAY_LEVELS_BACKLIGHT[i - 1]
    142                         && backlight < DISPLAY_LEVELS_BACKLIGHT[i]);
    143         }
    144     }
    145 
    146     @Test
    147     public void testSimpleStrategyIgnoresNewConfiguration() {
    148         Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
    149         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res);
    150 
    151         final int N = LUX_LEVELS.length;
    152         final float[] lux = { 0f, 1f };
    153         final float[] nits = { 0, PowerManager.BRIGHTNESS_ON };
    154 
    155         BrightnessConfiguration config = new BrightnessConfiguration.Builder()
    156                 .setCurve(lux, nits)
    157                 .build();
    158         strategy.setBrightnessConfiguration(config);
    159         assertNotEquals(1.0f, strategy.getBrightness(1f), 0.01 /*tolerance*/);
    160     }
    161 
    162     @Test
    163     public void testSimpleStrategyIgnoresNullConfiguration() {
    164         Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
    165         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res);
    166 
    167         strategy.setBrightnessConfiguration(null);
    168         final int N = DISPLAY_LEVELS_BACKLIGHT.length;
    169         final float expectedBrightness =
    170                 (float) DISPLAY_LEVELS_BACKLIGHT[N - 1] / PowerManager.BRIGHTNESS_ON;
    171         assertEquals(expectedBrightness,
    172                 strategy.getBrightness(LUX_LEVELS[N - 1]), 0.01 /*tolerance*/);
    173     }
    174 
    175     @Test
    176     public void testPhysicalStrategyMappingAtControlPoints() {
    177         Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS,
    178                 DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
    179         BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res);
    180         assertNotNull("BrightnessMappingStrategy should not be null", physical);
    181         for (int i = 0; i < LUX_LEVELS.length; i++) {
    182             final float expectedLevel = DISPLAY_LEVELS_NITS[i] / DISPLAY_RANGE_NITS[1];
    183             assertEquals(expectedLevel,
    184                     physical.getBrightness(LUX_LEVELS[i]), 0.01f /*tolerance*/);
    185         }
    186     }
    187 
    188     @Test
    189     public void testPhysicalStrategyMappingBetweenControlPoints() {
    190         Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS,
    191                 DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
    192         BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res);
    193         assertNotNull("BrightnessMappingStrategy should not be null", physical);
    194         Spline backlightToBrightness =
    195                 Spline.createSpline(toFloatArray(BACKLIGHT_RANGE), DISPLAY_RANGE_NITS);
    196         for (int i = 1; i < LUX_LEVELS.length; i++) {
    197             final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2;
    198             final float backlight = physical.getBrightness(lux) * PowerManager.BRIGHTNESS_ON;
    199             final float nits = backlightToBrightness.interpolate(backlight);
    200             assertTrue("Desired brightness should be between adjacent control points.",
    201                     nits > DISPLAY_LEVELS_NITS[i - 1] && nits < DISPLAY_LEVELS_NITS[i]);
    202         }
    203     }
    204 
    205     @Test
    206     public void testPhysicalStrategyUsesNewConfigurations() {
    207         Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS,
    208                 DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
    209         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res);
    210 
    211         final float[] lux = { 0f, 1f };
    212         final float[] nits = {
    213                 DISPLAY_RANGE_NITS[0],
    214                 DISPLAY_RANGE_NITS[DISPLAY_RANGE_NITS.length - 1]
    215         };
    216 
    217         BrightnessConfiguration config = new BrightnessConfiguration.Builder()
    218                 .setCurve(lux, nits)
    219                 .build();
    220         strategy.setBrightnessConfiguration(config);
    221         assertEquals(1.0f, strategy.getBrightness(1f), 0.01 /*tolerance*/);
    222 
    223         // Check that null returns us to the default configuration.
    224         strategy.setBrightnessConfiguration(null);
    225         final int N = DISPLAY_LEVELS_NITS.length;
    226         final float expectedBrightness = DISPLAY_LEVELS_NITS[N - 1] / DISPLAY_RANGE_NITS[1];
    227         assertEquals(expectedBrightness,
    228                 strategy.getBrightness(LUX_LEVELS[N - 1]), 0.01f /*tolerance*/);
    229     }
    230 
    231     @Test
    232     public void testDefaultStrategyIsPhysical() {
    233         Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT,
    234                 DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
    235         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res);
    236         assertTrue(strategy instanceof BrightnessMappingStrategy.PhysicalMappingStrategy);
    237     }
    238 
    239     @Test
    240     public void testNonStrictlyIncreasingLuxLevelsFails() {
    241         final int[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length);
    242         final int idx = lux.length / 2;
    243         int tmp = lux[idx];
    244         lux[idx] = lux[idx+1];
    245         lux[idx+1] = tmp;
    246         Resources res = createResources(lux, DISPLAY_LEVELS_NITS,
    247                 DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
    248         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res);
    249         assertNull(strategy);
    250 
    251         // And make sure we get the same result even if it's monotone but not increasing.
    252         lux[idx] = lux[idx+1];
    253         res = createResources(lux, DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
    254         strategy = BrightnessMappingStrategy.create(res);
    255         assertNull(strategy);
    256     }
    257 
    258     @Test
    259     public void testDifferentNumberOfControlPointValuesFails() {
    260         //Extra lux level
    261         final int[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length+1);
    262         // Make sure it's strictly increasing so that the only failure is the differing array
    263         // lengths
    264         lux[lux.length - 1] = lux[lux.length - 2] + 1;
    265         Resources res = createResources(lux, DISPLAY_LEVELS_NITS,
    266                 DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
    267         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res);
    268         assertNull(strategy);
    269 
    270         res = createResources(lux, DISPLAY_LEVELS_BACKLIGHT);
    271         strategy = BrightnessMappingStrategy.create(res);
    272         assertNull(strategy);
    273 
    274         // Extra backlight level
    275         final int[] backlight = Arrays.copyOf(
    276                 DISPLAY_LEVELS_BACKLIGHT, DISPLAY_LEVELS_BACKLIGHT.length+1);
    277         backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1;
    278         res = createResources(LUX_LEVELS, backlight);
    279         strategy = BrightnessMappingStrategy.create(res);
    280         assertNull(strategy);
    281 
    282         // Extra nits level
    283         final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length+1);
    284         nits[nits.length - 1] = nits[nits.length - 2] + 1;
    285         res = createResources(LUX_LEVELS, nits, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
    286         strategy = BrightnessMappingStrategy.create(res);
    287         assertNull(strategy);
    288     }
    289 
    290     @Test
    291     public void testPhysicalStrategyRequiresNitsMapping() {
    292         Resources res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/,
    293                 DISPLAY_LEVELS_NITS, EMPTY_FLOAT_ARRAY /*nitsRange*/, BACKLIGHT_RANGE);
    294         BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res);
    295         assertNull(physical);
    296 
    297         res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/,
    298                 DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, EMPTY_INT_ARRAY /*backlightRange*/);
    299         physical = BrightnessMappingStrategy.create(res);
    300         assertNull(physical);
    301 
    302         res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/,
    303                 DISPLAY_LEVELS_NITS, EMPTY_FLOAT_ARRAY /*nitsRange*/,
    304                 EMPTY_INT_ARRAY /*backlightRange*/);
    305         physical = BrightnessMappingStrategy.create(res);
    306         assertNull(physical);
    307     }
    308 
    309     @Test
    310     public void testStrategiesAdaptToUserDataPoint() {
    311         Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS,
    312                 DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
    313         assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res));
    314         res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
    315         assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res));
    316     }
    317 
    318     private static void assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy strategy) {
    319         // Save out all of the initial brightness data for comparison after reset.
    320         float[] initialBrightnessLevels = new float[LUX_LEVELS.length];
    321         for (int i = 0; i < LUX_LEVELS.length; i++) {
    322             initialBrightnessLevels[i] = strategy.getBrightness(LUX_LEVELS[i]);
    323         }
    324 
    325         // Add a data point in the middle of the curve where the user has set the brightness max
    326         final int idx = LUX_LEVELS.length / 2;
    327         strategy.addUserDataPoint(LUX_LEVELS[idx], 1.0f);
    328 
    329         // Then make sure that all control points after the middle lux level are also set to max...
    330         for (int i = idx; i < LUX_LEVELS.length; i++) {
    331             assertEquals(strategy.getBrightness(LUX_LEVELS[idx]), 1.0, 0.01 /*tolerance*/);
    332         }
    333 
    334         // ...and that all control points before the middle lux level are strictly less than the
    335         // previous one still.
    336         float prevBrightness = strategy.getBrightness(LUX_LEVELS[idx]);
    337         for (int i = idx - 1; i >= 0; i--) {
    338             float brightness = strategy.getBrightness(LUX_LEVELS[i]);
    339             assertTrue("Brightness levels must be monotonic after adapting to user data",
    340                     prevBrightness >= brightness);
    341             prevBrightness = brightness;
    342         }
    343 
    344         // Now reset the curve and make sure we go back to the initial brightness levels recorded
    345         // before adding the user data point.
    346         strategy.clearUserDataPoints();
    347         for (int i = 0; i < LUX_LEVELS.length; i++) {
    348             assertEquals(initialBrightnessLevels[i], strategy.getBrightness(LUX_LEVELS[i]),
    349                     0.01 /*tolerance*/);
    350         }
    351 
    352         // Now set the middle of the lux range to something just above the minimum.
    353         float minBrightness = strategy.getBrightness(LUX_LEVELS[0]);
    354         strategy.addUserDataPoint(LUX_LEVELS[idx], minBrightness + 0.01f);
    355 
    356         // Then make sure the curve is still monotonic.
    357         prevBrightness = 0f;
    358         for (float lux : LUX_LEVELS) {
    359             float brightness = strategy.getBrightness(lux);
    360             assertTrue("Brightness levels must be monotonic after adapting to user data",
    361                     prevBrightness <= brightness);
    362             prevBrightness = brightness;
    363         }
    364 
    365         // And that the lowest lux level still gives the absolute minimum brightness. This should
    366         // be true assuming that there are more than two lux levels in the curve since we picked a
    367         // brightness just barely above the minimum for the middle of the curve.
    368         minBrightness = (float) MathUtils.pow(minBrightness, MAXIMUM_GAMMA); // Gamma correction.
    369         assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.01 /*tolerance*/);
    370     }
    371 
    372     private static float[] toFloatArray(int[] vals) {
    373         float[] newVals = new float[vals.length];
    374         for (int i = 0; i < vals.length; i++) {
    375             newVals[i] = (float) vals[i];
    376         }
    377         return newVals;
    378     }
    379 
    380     private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight) {
    381         return createResources(luxLevels, brightnessLevelsBacklight,
    382                 EMPTY_FLOAT_ARRAY /*brightnessLevelsNits*/, EMPTY_FLOAT_ARRAY /*nitsRange*/,
    383                 EMPTY_INT_ARRAY /*backlightRange*/);
    384     }
    385 
    386     private Resources createResources(int[] luxLevels, float[] brightnessLevelsNits,
    387             float[] nitsRange, int[] backlightRange) {
    388         return createResources(luxLevels, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/,
    389                 brightnessLevelsNits, nitsRange, backlightRange);
    390     }
    391 
    392     private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight,
    393             float[] brightnessLevelsNits, float[] nitsRange, int[] backlightRange) {
    394         Resources mockResources = mock(Resources.class);
    395         // For historical reasons, the lux levels resource implicitly defines the first point as 0,
    396         // so we need to chop it off of the array the mock resource object returns.
    397         int[] luxLevelsResource = Arrays.copyOfRange(luxLevels, 1, luxLevels.length);
    398         when(mockResources.getIntArray(com.android.internal.R.array.config_autoBrightnessLevels))
    399                 .thenReturn(luxLevelsResource);
    400 
    401         when(mockResources.getIntArray(
    402                 com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
    403                 .thenReturn(brightnessLevelsBacklight);
    404 
    405         TypedArray mockBrightnessLevelNits = createFloatTypedArray(brightnessLevelsNits);
    406         when(mockResources.obtainTypedArray(
    407                 com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
    408                 .thenReturn(mockBrightnessLevelNits);
    409 
    410         TypedArray mockNitsRange = createFloatTypedArray(nitsRange);
    411         when(mockResources.obtainTypedArray(
    412                 com.android.internal.R.array.config_screenBrightnessNits))
    413                 .thenReturn(mockNitsRange);
    414 
    415         when(mockResources.getIntArray(
    416                 com.android.internal.R.array.config_screenBrightnessBacklight))
    417                 .thenReturn(backlightRange);
    418 
    419         when(mockResources.getInteger(
    420                 com.android.internal.R.integer.config_screenBrightnessSettingMinimum))
    421                 .thenReturn(1);
    422         when(mockResources.getInteger(
    423                 com.android.internal.R.integer.config_screenBrightnessSettingMaximum))
    424                 .thenReturn(255);
    425         when(mockResources.getFraction(
    426                 com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma, 1, 1))
    427                 .thenReturn(MAXIMUM_GAMMA);
    428         return mockResources;
    429     }
    430 
    431     private TypedArray createFloatTypedArray(float[] vals) {
    432         TypedArray mockArray = mock(TypedArray.class);
    433         when(mockArray.length()).thenAnswer(invocation -> {
    434             return vals.length;
    435         });
    436         when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
    437             final float def = (float) invocation.getArguments()[1];
    438             if (vals == null) {
    439                 return def;
    440             }
    441             int idx = (int) invocation.getArguments()[0];
    442             if (idx >= 0 && idx < vals.length) {
    443                 return vals[idx];
    444             } else {
    445                 return def;
    446             }
    447         });
    448         return mockArray;
    449     }
    450 
    451     // Gamma correction tests.
    452     // x0 = 100   y0 = ~0.01
    453     // x1 = 1000  y1 = ~0.20
    454     // x2 = 2500  y2 = ~0.50
    455     // x3 = 4000  y3 = ~0.80
    456     // x4 = 4900  y4 = ~0.99
    457 
    458     @Test
    459     public void testGammaCorrectionLowChangeAtCenter() {
    460         // If we set a user data point at (x2, y2^0.5), i.e. gamma = 0.5, it should bump the rest
    461         // of the spline accordingly.
    462         final int x1 = 1000;
    463         final int x2 = 2500;
    464         final int x3 = 4000;
    465         final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1);
    466         final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
    467         final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
    468         Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS,
    469                 DISPLAY_LEVELS_NITS, DISPLAY_LEVELS_BACKLIGHT);
    470         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources);
    471         // Let's start with a sanity check:
    472         assertEquals(y1, strategy.getBrightness(x1), 0.01f /* tolerance */);
    473         assertEquals(y2, strategy.getBrightness(x2), 0.01f /* tolerance */);
    474         assertEquals(y3, strategy.getBrightness(x3), 0.01f /* tolerance */);
    475         // OK, let's roll:
    476         float gamma = 0.5f;
    477         strategy.addUserDataPoint(x2, (float) MathUtils.pow(y2, gamma));
    478         assertEquals(MathUtils.pow(y1, gamma), strategy.getBrightness(x1), 0.01f /* tolerance */);
    479         assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.01f /* tolerance */);
    480         assertEquals(MathUtils.pow(y3, gamma), strategy.getBrightness(x3), 0.01f /* tolerance */);
    481         // The adjustment should be +0.63 (manual calculation).
    482         assertEquals(+0.63f, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */);
    483     }
    484 
    485     @Test
    486     public void testGammaCorrectionHighChangeAtCenter() {
    487         // This time we set a user data point at (x2, y2^0.25), i.e. gamma = 0.3 (the minimum),
    488         // which should bump the rest of the spline accordingly, and further correct x2 to hit
    489         // y2^0.25 (not y2^0.3).
    490         final int x1 = 1000;
    491         final int x2 = 2500;
    492         final int x3 = 4000;
    493         final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1);
    494         final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
    495         final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
    496         Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS,
    497                 DISPLAY_LEVELS_NITS, DISPLAY_LEVELS_BACKLIGHT);
    498         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources);
    499         // Sanity check:
    500         assertEquals(y1, strategy.getBrightness(x1), 0.01f /* tolerance */);
    501         assertEquals(y2, strategy.getBrightness(x2), 0.01f /* tolerance */);
    502         assertEquals(y3, strategy.getBrightness(x3), 0.01f /* tolerance */);
    503         // Let's roll:
    504         float gamma = 0.25f;
    505         final float minGamma = 1.0f / MAXIMUM_GAMMA;
    506         strategy.addUserDataPoint(x2, (float) MathUtils.pow(y2, gamma));
    507         assertEquals(MathUtils.pow(y1, minGamma), strategy.getBrightness(x1),
    508                 0.01f /* tolerance */);
    509         assertEquals(MathUtils.pow(y2, gamma),    strategy.getBrightness(x2),
    510                 0.01f /* tolerance */);
    511         assertEquals(MathUtils.pow(y3, minGamma), strategy.getBrightness(x3),
    512                 0.01f /* tolerance */);
    513         // The adjustment should be +1.0 (maximum adjustment).
    514         assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */);
    515     }
    516 
    517     @Test
    518     public void testGammaCorrectionExtremeChangeAtCenter() {
    519         // Extreme changes (e.g. setting brightness to 0.0 or 1.0) can't be gamma corrected, so we
    520         // just make sure the adjustment reflects the change.
    521         Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS,
    522                 DISPLAY_LEVELS_NITS, DISPLAY_LEVELS_BACKLIGHT);
    523         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources);
    524         assertEquals(0.0f, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */);
    525         strategy.addUserDataPoint(2500, 1.0f);
    526         assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */);
    527         strategy.addUserDataPoint(2500, 0.0f);
    528         assertEquals(-1.0f, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */);
    529     }
    530 
    531     @Test
    532     public void testGammaCorrectionChangeAtEdges() {
    533         // The algorithm behaves differently at the edges, because gamma correction there tends to
    534         // be extreme. If we add a user data point at (x0, y0+0.3), the adjustment should be
    535         // 0.3*2 = 0.6, resulting in a gamma of 3**-0.6 = ~0.52.
    536         final int x0 = 100;
    537         final int x2 = 2500;
    538         final int x4 = 4900;
    539         final float y0 = GAMMA_CORRECTION_SPLINE.interpolate(x0);
    540         final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
    541         final float y4 = GAMMA_CORRECTION_SPLINE.interpolate(x4);
    542         Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS,
    543                 DISPLAY_LEVELS_NITS, DISPLAY_LEVELS_BACKLIGHT);
    544         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources);
    545         // Sanity, as per tradition:
    546         assertEquals(y0, strategy.getBrightness(x0), 0.01f /* tolerance */);
    547         assertEquals(y2, strategy.getBrightness(x2), 0.01f /* tolerance */);
    548         assertEquals(y4, strategy.getBrightness(x4), 0.01f /* tolerance */);
    549         // Rollin':
    550         float increase = 0.3f;
    551         float adjustment = increase * 2;
    552         float gamma = (float) MathUtils.pow(MAXIMUM_GAMMA, -adjustment);
    553         strategy.addUserDataPoint(x0, y0 + increase);
    554         assertEquals(y0 + increase, strategy.getBrightness(x0), 0.01f /* tolerance */);
    555         assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.01f /* tolerance */);
    556         assertEquals(MathUtils.pow(y4, gamma), strategy.getBrightness(x4), 0.01f /* tolerance */);
    557         assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */);
    558         // Similarly, if we set a user data point at (x4, 1.0), the adjustment should be (1-y4)*2.
    559         increase = 1.0f - y4;
    560         adjustment = increase * 2;
    561         gamma = (float) MathUtils.pow(MAXIMUM_GAMMA, -adjustment);
    562         strategy.addUserDataPoint(x4, 1.0f);
    563         assertEquals(MathUtils.pow(y0, gamma), strategy.getBrightness(x0), 0.01f /* tolerance */);
    564         assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.01f /* tolerance */);
    565         assertEquals(1.0f, strategy.getBrightness(x4), 0.01f /* tolerance */);
    566         assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */);
    567     }
    568 }
    569