• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.car.util;
18 
19 import static com.android.car.internal.util.VersionUtils.isPlatformVersionAtLeastU;
20 
21 import android.car.builtin.power.PowerManagerHelper;
22 
23 import com.android.internal.annotations.VisibleForTesting;
24 
25 /**
26  * This is the minimized version of {@code com.android.settingslib.display.BrightnessUtils} not to
27  * depend on the library which uses the hidden api.
28  */
29 public class BrightnessUtils {
30 
31     public static final int GAMMA_SPACE_MIN = 0;
32     public static final int GAMMA_SPACE_MAX = 65535;
33     @VisibleForTesting
34     public static final float INVALID_BRIGHTNESS_IN_FLOAT = -1.f;
35 
36     // Hybrid Log Gamma constant values
37     private static final float R = 0.5f;
38     private static final float A = 0.17883277f;
39     private static final float B = 0.28466892f;
40     private static final float C = 0.55991073f;
41 
42     // The tolerance within which we consider brightness values approximately equal to each other.
43     // This value is approximately 1/3 of the smallest possible brightness value.
44     private static final float EPSILON = 0.001f;
45 
46     /**
47      * A function for converting from the gamma space that the slider works in to the
48      * linear space that the setting works in.
49      *
50      * The gamma space effectively provides us a way to make linear changes to the slider that
51      * result in linear changes in perception. If we made changes to the slider in the linear space
52      * then we'd see an approximately logarithmic change in perception (c.f. Fechner's Law).
53      *
54      * Internally, this implements the Hybrid Log Gamma electro-optical transfer function, which is
55      * a slight improvement to the typical gamma transfer function for displays whose max
56      * brightness exceeds the 120 nit reference point, but doesn't set a specific reference
57      * brightness like the PQ function does.
58      *
59      * Note that this transfer function is only valid if the display's backlight value is a linear
60      * control. If it's calibrated to be something non-linear, then a different transfer function
61      * should be used.
62      *
63      * @param val The slider value.
64      * @param min The minimum acceptable value for the setting.
65      * @param max The maximum acceptable value for the setting.
66      * @return The corresponding setting value.
67      */
convertGammaToLinear(int val, int min, int max)68     public static final int convertGammaToLinear(int val, int min, int max) {
69         final float normalizedVal = MathUtils.norm(GAMMA_SPACE_MIN, GAMMA_SPACE_MAX, val);
70         final float ret;
71         if (normalizedVal <= R) {
72             ret = MathUtils.sq(normalizedVal / R);
73         } else {
74             ret = MathUtils.exp((normalizedVal - C) / A) + B;
75         }
76 
77         // HLG is normalized to the range [0, 12], so we need to re-normalize to the range [0, 1]
78         // in order to derive the correct setting value.
79         return Math.round(MathUtils.lerp(min, max, ret / 12));
80     }
81 
82     /**
83      * A function for converting from the linear space that the setting works in to the
84      * gamma space that the slider works in.
85      *
86      * The gamma space effectively provides us a way to make linear changes to the slider that
87      * result in linear changes in perception. If we made changes to the slider in the linear space
88      * then we'd see an approximately logarithmic change in perception (c.f. Fechner's Law).
89      *
90      * Internally, this implements the Hybrid Log Gamma opto-electronic transfer function, which is
91      * a slight improvement to the typical gamma transfer function for displays whose max
92      * brightness exceeds the 120 nit reference point, but doesn't set a specific reference
93      * brightness like the PQ function does.
94      *
95      * Note that this transfer function is only valid if the display's backlight value is a linear
96      * control. If it's calibrated to be something non-linear, then a different transfer function
97      * should be used.
98      *
99      * @param val The brightness setting value.
100      * @param min The minimum acceptable value for the setting.
101      * @param max The maximum acceptable value for the setting.
102      * @return The corresponding slider value
103      */
convertLinearToGamma(int val, int min, int max)104     public static final int convertLinearToGamma(int val, int min, int max) {
105         return convertLinearToGammaFloat((float) val, (float) min, (float) max);
106     }
107 
108     /**
109      * Version of {@link #convertLinearToGamma} that takes float values.
110      * TODO: brightnessfloat merge with above method(?)
111      * @param val The brightness setting value.
112      * @param min The minimum acceptable value for the setting.
113      * @param max The maximum acceptable value for the setting.
114      * @return The corresponding slider value
115      */
convertLinearToGammaFloat(float val, float min, float max)116     public static final int convertLinearToGammaFloat(float val, float min, float max) {
117         // For some reason, HLG normalizes to the range [0, 12] rather than [0, 1]
118         final float normalizedVal = MathUtils.norm(min, max, val) * 12;
119         final float ret;
120         if (normalizedVal <= 1f) {
121             ret = MathUtils.sqrt(normalizedVal) * R;
122         } else {
123             ret = A * MathUtils.log(normalizedVal - B) + C;
124         }
125 
126         return Math.round(MathUtils.lerp(GAMMA_SPACE_MIN, GAMMA_SPACE_MAX, ret));
127     }
128 
129     /**
130      * Converts between the int brightness system and the float brightness system.
131      *
132      * <p>This is the copy of
133      * {@code com.android.internal.display.BrightnessSynchronizer#brightnessIntToFloat}.
134      */
brightnessIntToFloat(int brightnessInt)135     public static float brightnessIntToFloat(int brightnessInt) {
136         if (!isPlatformVersionAtLeastU()) {
137             return INVALID_BRIGHTNESS_IN_FLOAT;
138         }
139         if (brightnessInt == PowerManagerHelper.BRIGHTNESS_OFF) {
140             return PowerManagerHelper.BRIGHTNESS_OFF_FLOAT;
141         } else if (brightnessInt == PowerManagerHelper.BRIGHTNESS_INVALID) {
142             return PowerManagerHelper.BRIGHTNESS_INVALID_FLOAT;
143         } else {
144             final float minFloat = PowerManagerHelper.BRIGHTNESS_MIN;
145             final float maxFloat = PowerManagerHelper.BRIGHTNESS_MAX;
146             final float minInt = PowerManagerHelper.BRIGHTNESS_OFF + 1;
147             final float maxInt = PowerManagerHelper.BRIGHTNESS_ON;
148             return MathUtils.constrainedMap(minFloat, maxFloat, minInt, maxInt, brightnessInt);
149         }
150     }
151 
152     /**
153      * Converts between the float brightness system and the int brightness system.
154      *
155      * <p>This is the copy of
156      * {@code com.android.internal.display.BrightnessSynchronizer#brightnessFloatToInt}.
157      */
brightnessFloatToInt(float brightnessFloat)158     public static int brightnessFloatToInt(float brightnessFloat) {
159         return Math.round(brightnessFloatToIntRange(brightnessFloat));
160     }
161 
162     /**
163      * Translates specified value from the float brightness system to the int brightness system,
164      * given the min/max of each range. Accounts for special values such as OFF and invalid values.
165      * Value returned as a float primitive (to preserve precision), but is a value within the
166      * int-system range.
167      *
168      * <p>This is the copy of
169      * {@code com.android.internal.display.BrightnessSynchronizer#brightnessFloatToIntRange}.
170      */
brightnessFloatToIntRange(float brightnessFloat)171     private static float brightnessFloatToIntRange(float brightnessFloat) {
172         if (!isPlatformVersionAtLeastU()) {
173             return INVALID_BRIGHTNESS_IN_FLOAT;
174         }
175         if (floatEquals(brightnessFloat, PowerManagerHelper.BRIGHTNESS_OFF_FLOAT)) {
176             return PowerManagerHelper.BRIGHTNESS_OFF;
177         } else if (Float.isNaN(brightnessFloat)) {
178             return PowerManagerHelper.BRIGHTNESS_INVALID;
179         } else {
180             final float minFloat = PowerManagerHelper.BRIGHTNESS_MIN;
181             final float maxFloat = PowerManagerHelper.BRIGHTNESS_MAX;
182             final float minInt = PowerManagerHelper.BRIGHTNESS_OFF + 1;
183             final float maxInt = PowerManagerHelper.BRIGHTNESS_ON;
184             return MathUtils.constrainedMap(minInt, maxInt, minFloat, maxFloat, brightnessFloat);
185         }
186     }
187 
188     /**
189      * Tests whether two brightness float values are within a small enough tolerance
190      * of each other.
191      *
192      * <p>This is the copy of
193      * {@code com.android.internal.display.BrightnessSynchronizer#floatEquals}.
194      *
195      * @param a first float to compare
196      * @param b second float to compare
197      * @return whether the two values are within a small enough tolerance value
198      */
floatEquals(float a, float b)199     private static boolean floatEquals(float a, float b) {
200         return a == b
201                 || (Float.isNaN(a) && Float.isNaN(b))
202                 || (Math.abs(a - b) < EPSILON);
203     }
204 
205     /**
206      * This is the minimized version of {@code android.util.MathUtils} which is the hidden api.
207      */
208     private static final class MathUtils {
MathUtils()209         private MathUtils() {
210         }
211 
constrain(float amount, float low, float high)212         public static float constrain(float amount, float low, float high) {
213             return amount < low ? low : (amount > high ? high : amount);
214         }
215 
log(float a)216         public static float log(float a) {
217             return (float) Math.log(a);
218         }
219 
exp(float a)220         public static float exp(float a) {
221             return (float) Math.exp(a);
222         }
223 
sqrt(float a)224         public static float sqrt(float a) {
225             return (float) Math.sqrt(a);
226         }
227 
sq(float v)228         public static float sq(float v) {
229             return v * v;
230         }
231 
lerp(float start, float stop, float amount)232         public static float lerp(float start, float stop, float amount) {
233             return start + (stop - start) * amount;
234         }
235 
lerpInv(float a, float b, float value)236         public static float lerpInv(float a, float b, float value) {
237             return a != b ? ((value - a) / (b - a)) : 0.0f;
238         }
239 
saturate(float value)240         public static float saturate(float value) {
241             return constrain(value, 0.0f, 1.0f);
242         }
243 
lerpInvSat(float a, float b, float value)244         public static float lerpInvSat(float a, float b, float value) {
245             return saturate(lerpInv(a, b, value));
246         }
247 
norm(float start, float stop, float value)248         public static float norm(float start, float stop, float value) {
249             return (value - start) / (stop - start);
250         }
251 
constrainedMap( float rangeMin, float rangeMax, float valueMin, float valueMax, float value)252         public static float constrainedMap(
253                 float rangeMin, float rangeMax, float valueMin, float valueMax, float value) {
254             return lerp(rangeMin, rangeMax, lerpInvSat(valueMin, valueMax, value));
255         }
256     }
257 }
258