From 692f088bba1a2b0e095cefba7f86298fa8531aba Mon Sep 17 00:00:00 2001 From: Chris Kuiper Date: Wed, 26 Jan 2022 17:17:07 -0800 Subject: [PATCH 1/2] [RESTRICT AUTOMERGE]DisplayManager: Switch RampAnimator to ramp in HLG space. This switches the ramp animator logic to ramp in the perceptual HLG space, thereby making ramping appear linear to the human eye. Bug: 204252940 Test: Verified on Pixel 6 Pro that ramping is much more linear and consistent. Change-Id: I946e1570e654eb25aa334be827ca3d15cba21c48 --- .../server/display/BrightnessUtils.java | 83 +++++++++++++++++++ .../android/server/display/RampAnimator.java | 20 +++-- 2 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 services/core/java/com/android/server/display/BrightnessUtils.java diff --git a/services/core/java/com/android/server/display/BrightnessUtils.java b/services/core/java/com/android/server/display/BrightnessUtils.java new file mode 100644 index 0000000000000..84fa0cccbd10b --- /dev/null +++ b/services/core/java/com/android/server/display/BrightnessUtils.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.util.MathUtils; + +/** + * Utility class providing functions to convert between linear and perceptual gamma space. + * + * Internally, this implements the Hybrid Log Gamma electro-optical transfer function, which is a + * slight improvement to the typical gamma transfer function for displays whose max brightness + * exceeds the 120 nit reference point, but doesn't set a specific reference brightness like the PQ + * function does. + * + * Note that this transfer function is only valid if the display's backlight value is a linear + * control. If it's calibrated to be something non-linear, then a different transfer function + * should be used. + * + * Note: This code is based on the same class in the com.android.settingslib.display package. + */ +public class BrightnessUtils { + + // Hybrid Log Gamma constant values + private static final float R = 0.5f; + private static final float A = 0.17883277f; + private static final float B = 0.28466892f; + private static final float C = 0.55991073f; + + /** + * A function for converting from the gamma space into the linear space. + * + * @param val The value in the gamma space [0 .. 1.0] + * @return The corresponding value in the linear space [0 .. 1.0]. + */ + public static final float convertGammaToLinear(float val) { + final float ret; + if (val <= R) { + ret = MathUtils.sq(val / R); + } else { + ret = MathUtils.exp((val - C) / A) + B; + } + + // HLG is normalized to the range [0, 12], ensure that value is within that range, + // it shouldn't be out of bounds. + final float normalizedRet = MathUtils.constrain(ret, 0, 12); + + // Re-normalize to the range [0, 1] + // in order to derive the correct setting value. + return normalizedRet / 12; + } + + /** + * A function for converting from the linear space into the gamma space. + * + * @param val The value in linear space [0 .. 1.0] + * @return The corresponding value in gamma space [0 .. 1.0] + */ + public static final float convertLinearToGamma(float val) { + // For some reason, HLG normalizes to the range [0, 12] rather than [0, 1] + final float normalizedVal = val * 12; + final float ret; + if (normalizedVal <= 1f) { + ret = MathUtils.sqrt(normalizedVal) * R; + } else { + ret = A * MathUtils.log(normalizedVal - B) + C; + } + return ret; + } +} diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java index ed3b15fb26619..1ebd1f5a535ce 100644 --- a/services/core/java/com/android/server/display/RampAnimator.java +++ b/services/core/java/com/android/server/display/RampAnimator.java @@ -23,6 +23,8 @@ import android.view.Choreographer; /** * A custom animator that progressively updates a property value at * a given variable rate until it reaches a particular target value. + * The ramping at the given rate is done in the perceptual space using + * the HLG transfer functions. */ class RampAnimator { private final T mObject; @@ -57,7 +59,9 @@ class RampAnimator { * @param rate The convergence rate in units per second, or 0 to set the value immediately. * @return True if the target differs from the previous target. */ - public boolean animateTo(float target, float rate) { + public boolean animateTo(float targetLinear, float rate) { + // Convert the target from the linear into the HLG space. + final float target = BrightnessUtils.convertLinearToGamma(targetLinear); // Immediately jump to the target the first time. if (mFirstTime || rate <= 0) { @@ -156,7 +160,9 @@ class RampAnimator { final float oldCurrentValue = mCurrentValue; mCurrentValue = mAnimatedValue; if (oldCurrentValue != mCurrentValue) { - mProperty.setValue(mObject, mCurrentValue); + // Convert value from HLG into linear space for the property. + final float linearCurrentVal = BrightnessUtils.convertGammaToLinear(mCurrentValue); + mProperty.setValue(mObject, linearCurrentVal); } if (mTargetValue != mCurrentValue) { postAnimationCallback(); @@ -201,14 +207,14 @@ class RampAnimator { * If this is the first time the property is being set or if the rate is 0, * the value jumps directly to the target. * - * @param firstTarget The first target value. - * @param secondTarget The second target value. + * @param linearFirstTarget The first target value in linear space. + * @param linearSecondTarget The second target value in linear space. * @param rate The convergence rate in units per second, or 0 to set the value immediately. * @return True if either target differs from the previous target. */ - public boolean animateTo(float firstTarget, float secondTarget, float rate) { - final boolean firstRetval = mFirst.animateTo(firstTarget, rate); - final boolean secondRetval = mSecond.animateTo(secondTarget, rate); + public boolean animateTo(float linearFirstTarget, float linearSecondTarget, float rate) { + final boolean firstRetval = mFirst.animateTo(linearFirstTarget, rate); + final boolean secondRetval = mSecond.animateTo(linearSecondTarget, rate); return firstRetval && secondRetval; } From 47461bb5aef25af6cdb6234e93c3daa99c166f6d Mon Sep 17 00:00:00 2001 From: Chris Kuiper Date: Tue, 1 Feb 2022 17:56:07 -0800 Subject: [PATCH 2/2] [RESTRICT AUTOMERGE]DisplayManager: Make sure RampAnimator sets property in linear space. This fixes the case where RampAnimator is used the first time and immediately jumps to the target brightness. In that case the property was set to a value that has not been converted back into linear space, causing an incorrect initial brightness value. To avoid these bugs, consolidate setting of the property into one function. Bug: 217289549 Test: Cannot reproduce bug. Change-Id: I2ccdaa51ddba062fc5de1209470b90eb429280db --- .../com/android/server/display/RampAnimator.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java index 1ebd1f5a535ce..d8672fc076197 100644 --- a/services/core/java/com/android/server/display/RampAnimator.java +++ b/services/core/java/com/android/server/display/RampAnimator.java @@ -70,7 +70,7 @@ class RampAnimator { mRate = 0; mTargetValue = target; mCurrentValue = target; - mProperty.setValue(mObject, target); + setPropertyValue(target); if (mAnimating) { mAnimating = false; cancelAnimationCallback(); @@ -125,6 +125,15 @@ class RampAnimator { mListener = listener; } + /** + * Sets the brightness property by converting the given value from HLG space + * into linear space. + */ + private void setPropertyValue(float val) { + final float linearVal = BrightnessUtils.convertGammaToLinear(val); + mProperty.setValue(mObject, linearVal); + } + private void postAnimationCallback() { mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimationCallback, null); } @@ -160,9 +169,7 @@ class RampAnimator { final float oldCurrentValue = mCurrentValue; mCurrentValue = mAnimatedValue; if (oldCurrentValue != mCurrentValue) { - // Convert value from HLG into linear space for the property. - final float linearCurrentVal = BrightnessUtils.convertGammaToLinear(mCurrentValue); - mProperty.setValue(mObject, linearCurrentVal); + setPropertyValue(mCurrentValue); } if (mTargetValue != mCurrentValue) { postAnimationCallback();