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.settingslib.display; 18 19 import android.util.MathUtils; 20 21 /** Utility methods for calculating the display brightness. */ 22 public class BrightnessUtils { 23 24 public static final int GAMMA_SPACE_MIN = 0; 25 public static final int GAMMA_SPACE_MAX = 65535; 26 27 // Hybrid Log Gamma constant values 28 private static final float R = 0.5f; 29 private static final float A = 0.17883277f; 30 private static final float B = 0.28466892f; 31 private static final float C = 0.55991073f; 32 33 /** 34 * A function for converting from the gamma space that the slider works in to the 35 * linear space that the setting works in. 36 * 37 * The gamma space effectively provides us a way to make linear changes to the slider that 38 * result in linear changes in perception. If we made changes to the slider in the linear space 39 * then we'd see an approximately logarithmic change in perception (c.f. Fechner's Law). 40 * 41 * Internally, this implements the Hybrid Log Gamma electro-optical transfer function, which is 42 * a slight improvement to the typical gamma transfer function for displays whose max 43 * brightness exceeds the 120 nit reference point, but doesn't set a specific reference 44 * brightness like the PQ function does. 45 * 46 * Note that this transfer function is only valid if the display's backlight value is a linear 47 * control. If it's calibrated to be something non-linear, then a different transfer function 48 * should be used. 49 * 50 * @param val The slider value. 51 * @param min The minimum acceptable value for the setting. 52 * @param max The maximum acceptable value for the setting. 53 * @return The corresponding setting value. 54 */ convertGammaToLinear(int val, int min, int max)55 public static final int convertGammaToLinear(int val, int min, int max) { 56 final float normalizedVal = MathUtils.norm(GAMMA_SPACE_MIN, GAMMA_SPACE_MAX, val); 57 final float ret; 58 if (normalizedVal <= R) { 59 ret = MathUtils.sq(normalizedVal / R); 60 } else { 61 ret = MathUtils.exp((normalizedVal - C) / A) + B; 62 } 63 64 // HLG is normalized to the range [0, 12], so we need to re-normalize to the range [0, 1] 65 // in order to derive the correct setting value. 66 return Math.round(MathUtils.lerp(min, max, ret / 12)); 67 } 68 69 /** 70 * Version of {@link #convertGammaToLinear} that takes and returns float values. 71 * TODO(flc): refactor Android Auto to use float version 72 * 73 * @param val The slider value. 74 * @param min The minimum acceptable value for the setting. 75 * @param max The maximum acceptable value for the setting. 76 * @return The corresponding setting value. 77 */ convertGammaToLinearFloat(int val, float min, float max)78 public static final float convertGammaToLinearFloat(int val, float min, float max) { 79 final float normalizedVal = MathUtils.norm(GAMMA_SPACE_MIN, GAMMA_SPACE_MAX, val); 80 final float ret; 81 if (normalizedVal <= R) { 82 ret = MathUtils.sq(normalizedVal / R); 83 } else { 84 ret = MathUtils.exp((normalizedVal - C) / A) + B; 85 } 86 87 // HLG is normalized to the range [0, 12], ensure that value is within that range, 88 // it shouldn't be out of bounds. 89 final float normalizedRet = MathUtils.constrain(ret, 0, 12); 90 91 // Re-normalize to the range [0, 1] 92 // in order to derive the correct setting value. 93 return MathUtils.lerp(min, max, normalizedRet / 12); 94 } 95 96 /** 97 * A function for converting from the linear space that the setting works in to the 98 * gamma space that the slider works in. 99 * 100 * The gamma space effectively provides us a way to make linear changes to the slider that 101 * result in linear changes in perception. If we made changes to the slider in the linear space 102 * then we'd see an approximately logarithmic change in perception (c.f. Fechner's Law). 103 * 104 * Internally, this implements the Hybrid Log Gamma opto-electronic transfer function, which is 105 * a slight improvement to the typical gamma transfer function for displays whose max 106 * brightness exceeds the 120 nit reference point, but doesn't set a specific reference 107 * brightness like the PQ function does. 108 * 109 * Note that this transfer function is only valid if the display's backlight value is a linear 110 * control. If it's calibrated to be something non-linear, then a different transfer function 111 * should be used. 112 * 113 * @param val The brightness setting value. 114 * @param min The minimum acceptable value for the setting. 115 * @param max The maximum acceptable value for the setting. 116 * @return The corresponding slider value 117 */ convertLinearToGamma(int val, int min, int max)118 public static final int convertLinearToGamma(int val, int min, int max) { 119 return convertLinearToGammaFloat((float) val, (float) min, (float) max); 120 } 121 122 /** 123 * Version of {@link #convertLinearToGamma} that takes float values. 124 * TODO: brightnessfloat merge with above method(?) 125 * @param val The brightness setting value. 126 * @param min The minimum acceptable value for the setting. 127 * @param max The maximum acceptable value for the setting. 128 * @return The corresponding slider value 129 */ convertLinearToGammaFloat(float val, float min, float max)130 public static final int convertLinearToGammaFloat(float val, float min, float max) { 131 // For some reason, HLG normalizes to the range [0, 12] rather than [0, 1] 132 final float normalizedVal = MathUtils.norm(min, max, val) * 12; 133 final float ret; 134 if (normalizedVal <= 1f) { 135 ret = MathUtils.sqrt(normalizedVal) * R; 136 } else { 137 ret = A * MathUtils.log(normalizedVal - B) + C; 138 } 139 140 return Math.round(MathUtils.lerp(GAMMA_SPACE_MIN, GAMMA_SPACE_MAX, ret)); 141 } 142 } 143