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