From 084b6b2488a5a25caed4bb9678134d6b7ecbb5a5 Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Thu, 8 Jul 2021 03:27:19 -0700 Subject: [PATCH 01/11] display: Make Night Light transition more gradual At 3 seconds long, the transition is a bit too sharp when Night Light is set to max intensity. 10 seconds is less jarring when the feature automatically turns on and off at scheduled times. Change-Id: Ie60c7d41d523f0c14aae37219ef7c75f49390e8b --- .../com/android/server/display/color/ColorDisplayService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index 16d41150e502..18f95c95ad8c 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -110,7 +110,7 @@ public final class ColorDisplayService extends SystemService { /** * The transition time, in milliseconds, for Night Display to turn on/off. */ - private static final long TRANSITION_DURATION = 6000L; + private static final long TRANSITION_DURATION = 10000L; private static final int MSG_USER_CHANGED = 0; private static final int MSG_SET_UP = 1; From 5bf3883c3405245de800964592427f1d322b81c8 Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Thu, 8 Jul 2021 03:34:50 -0700 Subject: [PATCH 02/11] display: Add simple RGB color balance transform This adds support for adjusting the display's RGB color balance globally via ColorDisplayManager. This is implemented as a simple color transformation matrix that scales each channel. The effect is similar to LineageOS' LiveDisplay feature, but being a native color transform, it doesn't require changes to native code, SELinux policies, or custom HALs. Change-Id: Ia7ce363e3042ecfae438e452dbf155811a6bedeb --- .../hardware/display/ColorDisplayManager.java | 40 +++++++++ .../display/IColorDisplayManager.aidl | 3 + core/java/android/provider/Settings.java | 18 ++++ .../color/ColorBalanceTintController.java | 85 +++++++++++++++++++ .../display/color/ColorDisplayService.java | 62 ++++++++++++++ .../color/DisplayTransformManager.java | 4 + 6 files changed, 212 insertions(+) create mode 100644 services/core/java/com/android/server/display/color/ColorBalanceTintController.java diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java index a7096c9d43a0..09a4de803166 100644 --- a/core/java/android/hardware/display/ColorDisplayManager.java +++ b/core/java/android/hardware/display/ColorDisplayManager.java @@ -437,6 +437,30 @@ public boolean setAppSaturationLevel(@NonNull String packageName, return mManager.setAppSaturationLevel(packageName, saturationLevel); } + /** + * Set the global color balance for a specific RGB channel. + * + * @param channel RGB (0,1,2) channel to change + * @param value 0-255 (inclusive), where 255 is default balance + * @return whether the change was successful + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + public boolean setColorBalanceChannel(int channel, int value) { + return mManager.setColorBalanceChannel(channel, value); + } + + /** + * Get the current global color balance for a specific RGB channel. + * + * @param channel RGB (0,1,2) channel to get the balance of + * @return weight of the channel, 0-255 (inclusive) where 255 is the default + * @hide + */ + public int getColorBalanceChannel(int channel) { + return mManager.getColorBalanceChannel(channel); + } + /** * Enables or disables display white balance. * @@ -682,6 +706,22 @@ boolean setAppSaturationLevel(String packageName, int saturationLevel) { } } + boolean setColorBalanceChannel(int channel, int value) { + try { + return mCdm.setColorBalanceChannel(channel, value); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + int getColorBalanceChannel(int channel) { + try { + return mCdm.getColorBalanceChannel(channel); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + boolean isDisplayWhiteBalanceEnabled() { try { return mCdm.isDisplayWhiteBalanceEnabled(); diff --git a/core/java/android/hardware/display/IColorDisplayManager.aidl b/core/java/android/hardware/display/IColorDisplayManager.aidl index 0a29d4489b0b..ca8224c2f804 100644 --- a/core/java/android/hardware/display/IColorDisplayManager.aidl +++ b/core/java/android/hardware/display/IColorDisplayManager.aidl @@ -43,6 +43,9 @@ interface IColorDisplayManager { int getColorMode(); void setColorMode(int colorMode); + int getColorBalanceChannel(int channel); + boolean setColorBalanceChannel(int channel, int value); + boolean isDisplayWhiteBalanceEnabled(); boolean setDisplayWhiteBalanceEnabled(boolean enabled); Time getNightDisplayAutoStartTime(); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index b84f3f689c1c..351e9ca5e1b8 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10386,6 +10386,24 @@ public static boolean putFloatForUser(ContentResolver cr, String name, float val public static final String NIGHT_DISPLAY_LAST_ACTIVATED_TIME = "night_display_last_activated_time"; + /** + * Display color balance for the red channel, from 0 to 255. + * @hide + */ + public static final String DISPLAY_COLOR_BALANCE_RED = "display_color_balance_red"; + + /** + * Display color balance for the green channel, from 0 to 255. + * @hide + */ + public static final String DISPLAY_COLOR_BALANCE_GREEN = "display_color_balance_green"; + + /** + * Display color balance for the blue channel, from 0 to 255. + * @hide + */ + public static final String DISPLAY_COLOR_BALANCE_BLUE = "display_color_balance_blue"; + /** * Control whether display white balance is currently enabled. * @hide diff --git a/services/core/java/com/android/server/display/color/ColorBalanceTintController.java b/services/core/java/com/android/server/display/color/ColorBalanceTintController.java new file mode 100644 index 000000000000..0c2db0cb4780 --- /dev/null +++ b/services/core/java/com/android/server/display/color/ColorBalanceTintController.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2019 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.color; + +import static com.android.server.display.color.DisplayTransformManager.LEVEL_COLOR_MATRIX_COLOR_BALANCE; + +import android.content.Context; +import android.graphics.Color; +import android.hardware.display.ColorDisplayManager; +import android.opengl.Matrix; +import android.provider.Settings; + +import java.util.Arrays; + +/** Control the color transform for global color balance. */ +final class ColorBalanceTintController extends TintController { + + private final float[] mMatrix = new float[16]; + + @Override + public void setUp(Context context, boolean needsLinear) { + } + + @Override + public float[] getMatrix() { + return Arrays.copyOf(mMatrix, mMatrix.length); + } + + @Override + public void setMatrix(int rgb) { + Matrix.setIdentityM(mMatrix, 0); + mMatrix[0] = ((float) Color.red(rgb)) / 255.0f; + mMatrix[5] = ((float) Color.green(rgb)) / 255.0f; + mMatrix[10] = ((float) Color.blue(rgb)) / 255.0f; + } + + @Override + public int getLevel() { + return LEVEL_COLOR_MATRIX_COLOR_BALANCE; + } + + @Override + public boolean isAvailable(Context context) { + return ColorDisplayManager.isColorTransformAccelerated(context); + } + + public void updateBalance(Context context, int userId) { + int red = Settings.Secure.getIntForUser(context.getContentResolver(), channelToKey(0), + 255, userId); + int green = Settings.Secure.getIntForUser(context.getContentResolver(), channelToKey(1), + 255, userId); + int blue = Settings.Secure.getIntForUser(context.getContentResolver(), channelToKey(2), + 255, userId); + + int rgb = Color.rgb(red, green, blue); + setMatrix(rgb); + } + + public static String channelToKey(int channel) { + switch (channel) { + case 0: + return Settings.Secure.DISPLAY_COLOR_BALANCE_RED; + case 1: + return Settings.Secure.DISPLAY_COLOR_BALANCE_GREEN; + case 2: + return Settings.Secure.DISPLAY_COLOR_BALANCE_BLUE; + default: + throw new IllegalArgumentException("Unknown channel: " + channel); + } + } +} diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index 18f95c95ad8c..d132ca2c2584 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -118,6 +118,7 @@ public final class ColorDisplayService extends SystemService { private static final int MSG_APPLY_NIGHT_DISPLAY_ANIMATED = 3; private static final int MSG_APPLY_GLOBAL_SATURATION = 4; private static final int MSG_APPLY_DISPLAY_WHITE_BALANCE = 5; + private static final int MSG_APPLY_DISPLAY_COLOR_BALANCE = 6; /** * Return value if a setting has not been set. @@ -132,6 +133,9 @@ public final class ColorDisplayService extends SystemService { private final NightDisplayTintController mNightDisplayTintController = new NightDisplayTintController(); + private final ColorBalanceTintController mColorBalanceTintController = + new ColorBalanceTintController(); + @VisibleForTesting final DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController(); @@ -356,6 +360,11 @@ public void onChange(boolean selfChange, Uri uri) { case Secure.ACCESSIBILITY_DISPLAY_DALTONIZER: onAccessibilityDaltonizerChanged(); break; + case Secure.DISPLAY_COLOR_BALANCE_RED: + case Secure.DISPLAY_COLOR_BALANCE_BLUE: + case Secure.DISPLAY_COLOR_BALANCE_GREEN: + mHandler.sendEmptyMessage(MSG_APPLY_DISPLAY_COLOR_BALANCE); + break; case Secure.DISPLAY_WHITE_BALANCE_ENABLED: updateDisplayWhiteBalanceStatus(); break; @@ -386,6 +395,12 @@ public void onChange(boolean selfChange, Uri uri) { cr.registerContentObserver( Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER), false /* notifyForDescendants */, mContentObserver, mCurrentUser); + cr.registerContentObserver(Secure.getUriFor(Secure.DISPLAY_COLOR_BALANCE_RED), + false /* notifyForDescendants */, mContentObserver, mCurrentUser); + cr.registerContentObserver(Secure.getUriFor(Secure.DISPLAY_COLOR_BALANCE_GREEN), + false /* notifyForDescendants */, mContentObserver, mCurrentUser); + cr.registerContentObserver(Secure.getUriFor(Secure.DISPLAY_COLOR_BALANCE_BLUE), + false /* notifyForDescendants */, mContentObserver, mCurrentUser); cr.registerContentObserver(Secure.getUriFor(Secure.DISPLAY_WHITE_BALANCE_ENABLED), false /* notifyForDescendants */, mContentObserver, mCurrentUser); @@ -425,6 +440,10 @@ public void onChange(boolean selfChange, Uri uri) { updateDisplayWhiteBalanceStatus(); } + + if (mColorBalanceTintController.isAvailable(getContext())) { + mHandler.sendEmptyMessage(MSG_APPLY_DISPLAY_COLOR_BALANCE); + } } private void tearDown() { @@ -930,6 +949,25 @@ private boolean isColorModeAvailable(@ColorMode int colorMode) { return false; } + private boolean setColorBalanceChannelInternal(int channel, int value) { + if (mCurrentUser == UserHandle.USER_NULL) { + return false; + } + + boolean putSuccess = Secure.putIntForUser(getContext().getContentResolver(), + ColorBalanceTintController.channelToKey(channel), value, mCurrentUser); + if (putSuccess) { + mHandler.sendEmptyMessage(MSG_APPLY_DISPLAY_COLOR_BALANCE); + } + + return putSuccess; + } + + private int getColorBalanceChannelInternal(int channel) { + return Secure.getIntForUser(getContext().getContentResolver(), + ColorBalanceTintController.channelToKey(channel), 255, mCurrentUser); + } + private void dumpInternal(PrintWriter pw) { pw.println("COLOR DISPLAY MANAGER dumpsys (color_display)"); @@ -1468,6 +1506,10 @@ public void handleMessage(Message msg) { case MSG_APPLY_DISPLAY_WHITE_BALANCE: applyTint(mDisplayWhiteBalanceTintController, false); break; + case MSG_APPLY_DISPLAY_COLOR_BALANCE: + mColorBalanceTintController.updateBalance(getContext(), mCurrentUser); + applyTint(mColorBalanceTintController, true); + break; } } } @@ -1711,6 +1753,26 @@ public Time getNightDisplayCustomEndTime() { } } + @Override + public boolean setColorBalanceChannel(int channel, int value) { + final long token = Binder.clearCallingIdentity(); + try { + return setColorBalanceChannelInternal(channel, value); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public int getColorBalanceChannel(int channel) { + final long token = Binder.clearCallingIdentity(); + try { + return getColorBalanceChannelInternal(channel); + } finally { + Binder.restoreCallingIdentity(token); + } + } + @Override public boolean setDisplayWhiteBalanceEnabled(boolean enabled) { getContext().enforceCallingOrSelfPermission( diff --git a/services/core/java/com/android/server/display/color/DisplayTransformManager.java b/services/core/java/com/android/server/display/color/DisplayTransformManager.java index 3b0069cbf001..9d98fded9991 100644 --- a/services/core/java/com/android/server/display/color/DisplayTransformManager.java +++ b/services/core/java/com/android/server/display/color/DisplayTransformManager.java @@ -62,6 +62,10 @@ public class DisplayTransformManager { * Color transform level used by A11y services to invert the display colors. */ public static final int LEVEL_COLOR_MATRIX_INVERT_COLOR = 300; + /** + * Color transform level used to adjust the color balance of the display. + */ + public static final int LEVEL_COLOR_MATRIX_COLOR_BALANCE = 400; private static final int SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX = 1015; private static final int SURFACE_FLINGER_TRANSACTION_DALTONIZER = 1014; From 6eab0918ccc1637c94880e3f65f6fb8974a94793 Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Thu, 8 Jul 2021 03:38:44 -0700 Subject: [PATCH 03/11] graphics: Add CAT16 chromatic adaptation transform This is a newer CAT defined in CAM16 [1], which is a successor to CIECAM02. We'll use this to improve display color transforms in following commits. [1] https://onlinelibrary.wiley.com/doi/abs/10.1002/col.22131 Change-Id: Id660763547acbede226a135c5fc55f64b2486652 --- graphics/java/android/graphics/ColorSpace.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 1aeafa391b41..5222516fb8a4 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -813,6 +813,18 @@ public enum Adaptation { 0.7328f, -0.7036f, 0.0030f, 0.4296f, 1.6975f, 0.0136f, -0.1624f, 0.0061f, 0.9834f + }), + /** + * CAT16 chromatic adaptation transform, as defined in the + * CAM16 color appearance model. + * + * @see Comprehensive color solutions: CAM16, CAT16, and CAM16-UCS + * @hide + */ + CAT16(new float[] { + 0.401288f, -0.250268f, -0.002079f, + 0.650173f, 1.204414f, 0.048952f, + -0.051461f, 0.045854f, 0.953127f, }); final float[] mTransform; From a8c996012c33f9aec0e5d2c78aeb4a8338e58646 Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Thu, 8 Jul 2021 03:46:52 -0700 Subject: [PATCH 04/11] display: Use CAT16 for display white balance transform CAT16 is a successor to CIECAT02 (and the Bradford transform in CIECAM97s by extension). Switch to CAT16 for calculating the DWB chromatic adaptation matrix in order to improve color results. Change-Id: I11d598b38c1b50e89e0958827d10a9bd17b1b81d --- .../server/display/color/DisplayWhiteBalanceTintController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java index 3f1c222ab520..e054d9975169 100644 --- a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java +++ b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java @@ -156,7 +156,7 @@ public void setMatrix(int cct) { mCurrentColorTemperatureXYZ = ColorSpace.cctToXyz(cct); mChromaticAdaptationMatrix = - ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD, + ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.CAT16, mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ); // Convert the adaptation matrix to RGB space From 08c381fb91a38c35a419b53c26816582720b5913 Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Thu, 8 Jul 2021 04:11:04 -0700 Subject: [PATCH 05/11] display: Create common chromatic adaptation tint controller Display white balance is not the only use case for chromatic adaptation and most of the code can be reused, so create a common tint controller class for chromatic adaptation. Change-Id: Ia5edba7669e44720bc98b7542654cda44de4bc74 --- .../ChromaticAdaptationTintController.java | 238 ++++++++++++++++++ .../DisplayWhiteBalanceTintController.java | 223 ++-------------- 2 files changed, 259 insertions(+), 202 deletions(-) create mode 100644 services/core/java/com/android/server/display/color/ChromaticAdaptationTintController.java diff --git a/services/core/java/com/android/server/display/color/ChromaticAdaptationTintController.java b/services/core/java/com/android/server/display/color/ChromaticAdaptationTintController.java new file mode 100644 index 000000000000..e048e9ba23c9 --- /dev/null +++ b/services/core/java/com/android/server/display/color/ChromaticAdaptationTintController.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2019 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.color; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.ColorSpace; +import android.hardware.display.ColorDisplayManager; +import android.opengl.Matrix; +import android.os.IBinder; +import android.util.Slog; +import android.view.SurfaceControl; +import android.view.SurfaceControl.DisplayPrimaries; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; + +import java.io.PrintWriter; + +abstract class ChromaticAdaptationTintController extends TintController { + + // Three chromaticity coordinates per color: X, Y, and Z + private static final int NUM_VALUES_PER_PRIMARY = 3; + // Four colors: red, green, blue, and white + private static final int NUM_DISPLAY_PRIMARIES_VALS = 4 * NUM_VALUES_PER_PRIMARY; + private static final int COLORSPACE_MATRIX_LENGTH = 9; + + protected final Object mLock = new Object(); + @VisibleForTesting + float[] mDisplayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY]; + @VisibleForTesting + ColorSpace.Rgb mDisplayColorSpaceRGB; + private float[] mChromaticAdaptationMatrix; + private float[] mCurrentTargetXYZ; + @VisibleForTesting + boolean mSetUp = false; + protected float[] mMatrix = new float[16]; + + @Override + public void setUp(Context context, boolean needsLinear) { + mSetUp = false; + final Resources res = context.getResources(); + + ColorSpace.Rgb displayColorSpaceRGB = getDisplayColorSpaceFromSurfaceControl(); + if (displayColorSpaceRGB == null) { + Slog.w(ColorDisplayService.TAG, + "Failed to get display color space from SurfaceControl, trying res"); + displayColorSpaceRGB = getDisplayColorSpaceFromResources(res); + } + + // Make sure display color space is valid + if (!isColorMatrixValid(displayColorSpaceRGB.getTransform())) { + Slog.e(ColorDisplayService.TAG, "Invalid display color space RGB-to-XYZ transform"); + return; + } + if (!isColorMatrixValid(displayColorSpaceRGB.getInverseTransform())) { + Slog.e(ColorDisplayService.TAG, "Invalid display color space XYZ-to-RGB transform"); + return; + } + + final String[] nominalWhiteValues = res.getStringArray( + R.array.config_displayWhiteBalanceDisplayNominalWhite); + float[] displayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY]; + for (int i = 0; i < nominalWhiteValues.length; i++) { + displayNominalWhiteXYZ[i] = Float.parseFloat(nominalWhiteValues[i]); + } + + synchronized (mLock) { + mDisplayColorSpaceRGB = displayColorSpaceRGB; + mDisplayNominalWhiteXYZ = displayNominalWhiteXYZ; + setUpLocked(context, needsLinear); + mSetUp = true; + } + } + + abstract protected void setUpLocked(Context context, boolean needsLinear); + + @Override + public float[] getMatrix() { + return mSetUp && isActivated() ? mMatrix + : ColorDisplayService.MATRIX_IDENTITY; + } + + protected void setMatrixLocked(float[] targetXyz) { + if (!mSetUp) { + Slog.w(ColorDisplayService.TAG, + "Can't set chromatic adaptation matrix: uninitialized"); + return; + } + + // Adapt the display's nominal white point to match the requested CCT value + mCurrentTargetXYZ = targetXyz; + + mChromaticAdaptationMatrix = + ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.CAT16, + mDisplayNominalWhiteXYZ, mCurrentTargetXYZ); + + // Convert the adaptation matrix to RGB space + float[] result = ColorSpace.mul3x3(mChromaticAdaptationMatrix, + mDisplayColorSpaceRGB.getTransform()); + result = ColorSpace.mul3x3(mDisplayColorSpaceRGB.getInverseTransform(), result); + + // Normalize the transform matrix to peak white value in RGB space + final float adaptedMaxR = result[0] + result[3] + result[6]; + final float adaptedMaxG = result[1] + result[4] + result[7]; + final float adaptedMaxB = result[2] + result[5] + result[8]; + final float denum = Math.max(Math.max(adaptedMaxR, adaptedMaxG), adaptedMaxB); + + Matrix.setIdentityM(mMatrix, 0); + for (int i = 0; i < result.length; i++) { + result[i] /= denum; + if (!isColorMatrixCoeffValid(result[i])) { + Slog.e(ColorDisplayService.TAG, "Invalid chromatic adaptation color matrix"); + return; + } + } + + System.arraycopy(result, 0, mMatrix, 0, 3); + System.arraycopy(result, 3, mMatrix, 4, 3); + System.arraycopy(result, 6, mMatrix, 8, 3); + + String xyzString = "[" + targetXyz[0] + "," + targetXyz[1] + "," + targetXyz[2] + "]"; + Slog.d(ColorDisplayService.TAG, "setChromaticAdaptationMatrix: xyz = " + xyzString + + " matrix = " + matrixToString(mMatrix, 16)); + } + + @Override + public void dump(PrintWriter pw) { + synchronized (mLock) { + pw.println(" mSetUp = " + mSetUp); + if (!mSetUp) { + return; + } + + dumpLocked(pw); + pw.println(" mCurrentTargetXYZ = " + + matrixToString(mCurrentTargetXYZ, 3)); + pw.println(" mDisplayColorSpaceRGB RGB-to-XYZ = " + + matrixToString(mDisplayColorSpaceRGB.getTransform(), 3)); + pw.println(" mChromaticAdaptationMatrix = " + + matrixToString(mChromaticAdaptationMatrix, 3)); + pw.println(" mDisplayColorSpaceRGB XYZ-to-RGB = " + + matrixToString(mDisplayColorSpaceRGB.getInverseTransform(), 3)); + pw.println(" mMatrix = " + + matrixToString(mMatrix, 4)); + } + } + + protected void dumpLocked(PrintWriter pw) { + } + + private ColorSpace.Rgb makeRgbColorSpaceFromXYZ(float[] redGreenBlueXYZ, float[] whiteXYZ) { + return new ColorSpace.Rgb( + "Display Color Space", + redGreenBlueXYZ, + whiteXYZ, + 2.2f // gamma, unused for chromatic adaptation + ); + } + + private ColorSpace.Rgb getDisplayColorSpaceFromSurfaceControl() { + final IBinder displayToken = SurfaceControl.getInternalDisplayToken(); + if (displayToken == null) { + return null; + } + + DisplayPrimaries primaries = SurfaceControl.getDisplayNativePrimaries(displayToken); + if (primaries == null || primaries.red == null || primaries.green == null + || primaries.blue == null || primaries.white == null) { + return null; + } + + return makeRgbColorSpaceFromXYZ( + new float[]{ + primaries.red.X, primaries.red.Y, primaries.red.Z, + primaries.green.X, primaries.green.Y, primaries.green.Z, + primaries.blue.X, primaries.blue.Y, primaries.blue.Z, + }, + new float[]{primaries.white.X, primaries.white.Y, primaries.white.Z} + ); + } + + private ColorSpace.Rgb getDisplayColorSpaceFromResources(Resources res) { + final String[] displayPrimariesValues = res.getStringArray( + R.array.config_displayWhiteBalanceDisplayPrimaries); + float[] displayRedGreenBlueXYZ = + new float[NUM_DISPLAY_PRIMARIES_VALS - NUM_VALUES_PER_PRIMARY]; + float[] displayWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY]; + + for (int i = 0; i < displayRedGreenBlueXYZ.length; i++) { + displayRedGreenBlueXYZ[i] = Float.parseFloat(displayPrimariesValues[i]); + } + + for (int i = 0; i < displayWhiteXYZ.length; i++) { + displayWhiteXYZ[i] = Float.parseFloat( + displayPrimariesValues[displayRedGreenBlueXYZ.length + i]); + } + + return makeRgbColorSpaceFromXYZ(displayRedGreenBlueXYZ, displayWhiteXYZ); + } + + private boolean isColorMatrixCoeffValid(float coeff) { + if (Float.isNaN(coeff) || Float.isInfinite(coeff)) { + return false; + } + + return true; + } + + private boolean isColorMatrixValid(float[] matrix) { + if (matrix == null || matrix.length != COLORSPACE_MATRIX_LENGTH) { + return false; + } + + for (int i = 0; i < matrix.length; i++) { + if (!isColorMatrixCoeffValid(matrix[i])) { + return false; + } + } + + return true; + } + +} diff --git a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java index e054d9975169..0450448aba70 100644 --- a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java +++ b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java @@ -22,77 +22,34 @@ import android.content.res.Resources; import android.graphics.ColorSpace; import android.hardware.display.ColorDisplayManager; -import android.opengl.Matrix; -import android.os.IBinder; import android.util.Slog; -import android.view.SurfaceControl; -import android.view.SurfaceControl.DisplayPrimaries; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; -import java.lang.System; -final class DisplayWhiteBalanceTintController extends TintController { +final class DisplayWhiteBalanceTintController extends ChromaticAdaptationTintController { - // Three chromaticity coordinates per color: X, Y, and Z - private static final int NUM_VALUES_PER_PRIMARY = 3; - // Four colors: red, green, blue, and white - private static final int NUM_DISPLAY_PRIMARIES_VALS = 4 * NUM_VALUES_PER_PRIMARY; - private static final int COLORSPACE_MATRIX_LENGTH = 9; + private Boolean mIsAvailable; - private final Object mLock = new Object(); @VisibleForTesting int mTemperatureMin; @VisibleForTesting int mTemperatureMax; private int mTemperatureDefault; @VisibleForTesting - float[] mDisplayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY]; - @VisibleForTesting - ColorSpace.Rgb mDisplayColorSpaceRGB; - private float[] mChromaticAdaptationMatrix; - @VisibleForTesting int mCurrentColorTemperature; - private float[] mCurrentColorTemperatureXYZ; - @VisibleForTesting - boolean mSetUp = false; - private float[] mMatrixDisplayWhiteBalance = new float[16]; - private Boolean mIsAvailable; @Override public void setUp(Context context, boolean needsLinear) { - mSetUp = false; - final Resources res = context.getResources(); - - ColorSpace.Rgb displayColorSpaceRGB = getDisplayColorSpaceFromSurfaceControl(); - if (displayColorSpaceRGB == null) { - Slog.w(ColorDisplayService.TAG, - "Failed to get display color space from SurfaceControl, trying res"); - displayColorSpaceRGB = getDisplayColorSpaceFromResources(res); - if (displayColorSpaceRGB == null) { - Slog.e(ColorDisplayService.TAG, "Failed to get display color space from resources"); - return; - } - } - - // Make sure display color space is valid - if (!isColorMatrixValid(displayColorSpaceRGB.getTransform())) { - Slog.e(ColorDisplayService.TAG, "Invalid display color space RGB-to-XYZ transform"); - return; - } - if (!isColorMatrixValid(displayColorSpaceRGB.getInverseTransform())) { - Slog.e(ColorDisplayService.TAG, "Invalid display color space XYZ-to-RGB transform"); - return; - } + super.setUp(context, needsLinear); + setMatrix(mTemperatureDefault); + } - final String[] nominalWhiteValues = res.getStringArray( - R.array.config_displayWhiteBalanceDisplayNominalWhite); - float[] displayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY]; - for (int i = 0; i < nominalWhiteValues.length; i++) { - displayNominalWhiteXYZ[i] = Float.parseFloat(nominalWhiteValues[i]); - } + @Override + protected void setUpLocked(Context context, boolean needsLinear) { + final Resources res = context.getResources(); final int colorTemperatureMin = res.getInteger( R.integer.config_displayWhiteBalanceColorTemperatureMin); @@ -113,32 +70,13 @@ public void setUp(Context context, boolean needsLinear) { final int colorTemperature = res.getInteger( R.integer.config_displayWhiteBalanceColorTemperatureDefault); - synchronized (mLock) { - mDisplayColorSpaceRGB = displayColorSpaceRGB; - mDisplayNominalWhiteXYZ = displayNominalWhiteXYZ; - mTemperatureMin = colorTemperatureMin; - mTemperatureMax = colorTemperatureMax; - mTemperatureDefault = colorTemperature; - mSetUp = true; - } - - setMatrix(mTemperatureDefault); - } - - @Override - public float[] getMatrix() { - return mSetUp && isActivated() ? mMatrixDisplayWhiteBalance - : ColorDisplayService.MATRIX_IDENTITY; + mTemperatureMin = colorTemperatureMin; + mTemperatureMax = colorTemperatureMax; + mTemperatureDefault = colorTemperature; } @Override public void setMatrix(int cct) { - if (!mSetUp) { - Slog.w(ColorDisplayService.TAG, - "Can't set display white balance temperature: uninitialized"); - return; - } - if (cct < mTemperatureMin) { Slog.w(ColorDisplayService.TAG, "Requested display color temperature is below allowed minimum"); @@ -153,44 +91,10 @@ public void setMatrix(int cct) { mCurrentColorTemperature = cct; // Adapt the display's nominal white point to match the requested CCT value - mCurrentColorTemperatureXYZ = ColorSpace.cctToXyz(cct); - - mChromaticAdaptationMatrix = - ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.CAT16, - mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ); - - // Convert the adaptation matrix to RGB space - float[] result = ColorSpace.mul3x3(mChromaticAdaptationMatrix, - mDisplayColorSpaceRGB.getTransform()); - result = ColorSpace.mul3x3(mDisplayColorSpaceRGB.getInverseTransform(), result); - - // Normalize the transform matrix to peak white value in RGB space - final float adaptedMaxR = result[0] + result[3] + result[6]; - final float adaptedMaxG = result[1] + result[4] + result[7]; - final float adaptedMaxB = result[2] + result[5] + result[8]; - final float denum = Math.max(Math.max(adaptedMaxR, adaptedMaxG), adaptedMaxB); - - Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0); - for (int i = 0; i < result.length; i++) { - result[i] /= denum; - if (!isColorMatrixCoeffValid(result[i])) { - Slog.e(ColorDisplayService.TAG, "Invalid DWB color matrix"); - return; - } - } - - java.lang.System.arraycopy(result, 0, mMatrixDisplayWhiteBalance, 0, 3); - java.lang.System.arraycopy(result, 3, mMatrixDisplayWhiteBalance, 4, 3); - java.lang.System.arraycopy(result, 6, mMatrixDisplayWhiteBalance, 8, 3); + setMatrixLocked(ColorSpace.cctToXyz(cct)); } - Slog.d(ColorDisplayService.TAG, "setDisplayWhiteBalanceTemperatureMatrix: cct = " + cct - + " matrix = " + matrixToString(mMatrixDisplayWhiteBalance, 16)); - } - - @Override - public int getLevel() { - return LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE; + Slog.d(ColorDisplayService.TAG, "setDisplayWhiteBalanceTemperatureMatrix: cct = " + cct); } @Override @@ -202,100 +106,15 @@ public boolean isAvailable(Context context) { } @Override - public void dump(PrintWriter pw) { - synchronized (mLock) { - pw.println(" mSetUp = " + mSetUp); - if (!mSetUp) { - return; - } - - pw.println(" mTemperatureMin = " + mTemperatureMin); - pw.println(" mTemperatureMax = " + mTemperatureMax); - pw.println(" mTemperatureDefault = " + mTemperatureDefault); - pw.println(" mCurrentColorTemperature = " + mCurrentColorTemperature); - pw.println(" mCurrentColorTemperatureXYZ = " - + matrixToString(mCurrentColorTemperatureXYZ, 3)); - pw.println(" mDisplayColorSpaceRGB RGB-to-XYZ = " - + matrixToString(mDisplayColorSpaceRGB.getTransform(), 3)); - pw.println(" mChromaticAdaptationMatrix = " - + matrixToString(mChromaticAdaptationMatrix, 3)); - pw.println(" mDisplayColorSpaceRGB XYZ-to-RGB = " - + matrixToString(mDisplayColorSpaceRGB.getInverseTransform(), 3)); - pw.println(" mMatrixDisplayWhiteBalance = " - + matrixToString(mMatrixDisplayWhiteBalance, 4)); - } - } - - private ColorSpace.Rgb makeRgbColorSpaceFromXYZ(float[] redGreenBlueXYZ, float[] whiteXYZ) { - return new ColorSpace.Rgb( - "Display Color Space", - redGreenBlueXYZ, - whiteXYZ, - 2.2f // gamma, unused for display white balance - ); + protected void dumpLocked(PrintWriter pw) { + pw.println(" mTemperatureMin = " + mTemperatureMin); + pw.println(" mTemperatureMax = " + mTemperatureMax); + pw.println(" mTemperatureDefault = " + mTemperatureDefault); + pw.println(" mCurrentColorTemperature = " + mCurrentColorTemperature); } - private ColorSpace.Rgb getDisplayColorSpaceFromSurfaceControl() { - final IBinder displayToken = SurfaceControl.getInternalDisplayToken(); - if (displayToken == null) { - return null; - } - - DisplayPrimaries primaries = SurfaceControl.getDisplayNativePrimaries(displayToken); - if (primaries == null || primaries.red == null || primaries.green == null - || primaries.blue == null || primaries.white == null) { - return null; - } - - return makeRgbColorSpaceFromXYZ( - new float[]{ - primaries.red.X, primaries.red.Y, primaries.red.Z, - primaries.green.X, primaries.green.Y, primaries.green.Z, - primaries.blue.X, primaries.blue.Y, primaries.blue.Z, - }, - new float[]{primaries.white.X, primaries.white.Y, primaries.white.Z} - ); - } - - private ColorSpace.Rgb getDisplayColorSpaceFromResources(Resources res) { - final String[] displayPrimariesValues = res.getStringArray( - R.array.config_displayWhiteBalanceDisplayPrimaries); - float[] displayRedGreenBlueXYZ = - new float[NUM_DISPLAY_PRIMARIES_VALS - NUM_VALUES_PER_PRIMARY]; - float[] displayWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY]; - - for (int i = 0; i < displayRedGreenBlueXYZ.length; i++) { - displayRedGreenBlueXYZ[i] = Float.parseFloat(displayPrimariesValues[i]); - } - - for (int i = 0; i < displayWhiteXYZ.length; i++) { - displayWhiteXYZ[i] = Float.parseFloat( - displayPrimariesValues[displayRedGreenBlueXYZ.length + i]); - } - - return makeRgbColorSpaceFromXYZ(displayRedGreenBlueXYZ, displayWhiteXYZ); - } - - private boolean isColorMatrixCoeffValid(float coeff) { - if (Float.isNaN(coeff) || Float.isInfinite(coeff)) { - return false; - } - - return true; - } - - private boolean isColorMatrixValid(float[] matrix) { - if (matrix == null || matrix.length != COLORSPACE_MATRIX_LENGTH) { - return false; - } - - for (int i = 0; i < matrix.length; i++) { - if (!isColorMatrixCoeffValid(matrix[i])) { - return false; - } - } - - return true; + @Override + public int getLevel() { + return LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE; } - } From 09392bd47a164f1fd104fef572dfbf16a0b69624 Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Thu, 8 Jul 2021 18:32:36 -0700 Subject: [PATCH 06/11] display: Improve matrix formatting for easier debugging Change-Id: Ib806b91a1d838be86bb9095d1d5e2447aad37a5e --- .../java/com/android/server/display/color/TintController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/display/color/TintController.java b/services/core/java/com/android/server/display/color/TintController.java index 422dd328d2b6..279a7b5d3c25 100644 --- a/services/core/java/com/android/server/display/color/TintController.java +++ b/services/core/java/com/android/server/display/color/TintController.java @@ -116,7 +116,7 @@ static String matrixToString(float[] matrix, int columns) { if (i % columns == 0) { sb.append("\n "); } - sb.append(String.format("%9.6f", matrix[i])); + sb.append(String.format("%9.6f, ", matrix[i])); } return sb.toString(); } From 6d2f98ebe40bbaad0aafbaeb1f48814fe19f1fed Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Thu, 8 Jul 2021 18:40:18 -0700 Subject: [PATCH 07/11] display: ColorBalance: Add support for non-linear native color spaces On some devices and/or color modes, color transforms are applied in an "unmanaged" native color space, so apply a linear sRGB transformation matrix is wrong. It's not technically possible to implement accurate color balance in such cases, but attempt to accommodate it by assuming non-linear sRGB and applying the sRGB transfer function on the matrix. Change-Id: Ia786bf2468f29d729607ecc3f4a26305440717de --- .../color/ColorBalanceTintController.java | 28 +++++++++++++++++-- .../display/color/ColorDisplayService.java | 5 ++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/services/core/java/com/android/server/display/color/ColorBalanceTintController.java b/services/core/java/com/android/server/display/color/ColorBalanceTintController.java index 0c2db0cb4780..5b5ef7ffb619 100644 --- a/services/core/java/com/android/server/display/color/ColorBalanceTintController.java +++ b/services/core/java/com/android/server/display/color/ColorBalanceTintController.java @@ -30,9 +30,11 @@ final class ColorBalanceTintController extends TintController { private final float[] mMatrix = new float[16]; + private boolean mNeedsLinear; @Override public void setUp(Context context, boolean needsLinear) { + mNeedsLinear = needsLinear; } @Override @@ -43,9 +45,21 @@ public float[] getMatrix() { @Override public void setMatrix(int rgb) { Matrix.setIdentityM(mMatrix, 0); - mMatrix[0] = ((float) Color.red(rgb)) / 255.0f; - mMatrix[5] = ((float) Color.green(rgb)) / 255.0f; - mMatrix[10] = ((float) Color.blue(rgb)) / 255.0f; + + float red = ((float) Color.red(rgb)) / 255.0f; + float green = ((float) Color.green(rgb)) / 255.0f; + float blue = ((float) Color.blue(rgb)) / 255.0f; + + if (!mNeedsLinear) { + // Convert to non-linear sRGB as the assumed native color space + red = linearToSrgb(red); + green = linearToSrgb(green); + blue = linearToSrgb(blue); + } + + mMatrix[0] = red; + mMatrix[5] = green; + mMatrix[10] = blue; } @Override @@ -82,4 +96,12 @@ public static String channelToKey(int channel) { throw new IllegalArgumentException("Unknown channel: " + channel); } } + + private static float linearToSrgb(float x) { + if (x >= 0.0031308) { + return (1.055f) * ((float) Math.pow(x, 1.0f / 2.4f)) - 0.055f; + } else { + return 12.92f * x; + } + } } diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index d132ca2c2584..0d3be47fc242 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -539,6 +539,11 @@ private void onDisplayColorModeChanged(int mode) { if (mDisplayWhiteBalanceTintController.isAvailable(getContext())) { updateDisplayWhiteBalanceStatus(); } + + if (mColorBalanceTintController.isAvailable(getContext())) { + mColorBalanceTintController.setUp(getContext(), + DisplayTransformManager.needsLinearColorMatrix(mode)); + } } private void onAccessibilityActivated() { From d4665768d95f682e7a0df19bea851224a832b35b Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Thu, 8 Jul 2021 18:43:03 -0700 Subject: [PATCH 08/11] display: ChromaticAdaptation: Fix matrix formatting in logs Google specified 16 columns for the 4x4 transformation matrix, causing the entire thing to get printed on a single line. Fix it to improve readability. Change-Id: I99a5bb35694d0703db3bf806af2a632dc021e5f6 --- .../display/color/ChromaticAdaptationTintController.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/display/color/ChromaticAdaptationTintController.java b/services/core/java/com/android/server/display/color/ChromaticAdaptationTintController.java index e048e9ba23c9..7f10da195d3e 100644 --- a/services/core/java/com/android/server/display/color/ChromaticAdaptationTintController.java +++ b/services/core/java/com/android/server/display/color/ChromaticAdaptationTintController.java @@ -105,11 +105,13 @@ protected void setMatrixLocked(float[] targetXyz) { // Adapt the display's nominal white point to match the requested CCT value mCurrentTargetXYZ = targetXyz; + // XYZ -> LMS -> [CAT] -> LMS -> XYZ mChromaticAdaptationMatrix = ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.CAT16, mDisplayNominalWhiteXYZ, mCurrentTargetXYZ); // Convert the adaptation matrix to RGB space + // RGB -> XYZ -> LMS -> [CAT] -> LMS -> XYZ -> RGB float[] result = ColorSpace.mul3x3(mChromaticAdaptationMatrix, mDisplayColorSpaceRGB.getTransform()); result = ColorSpace.mul3x3(mDisplayColorSpaceRGB.getInverseTransform(), result); @@ -135,7 +137,7 @@ protected void setMatrixLocked(float[] targetXyz) { String xyzString = "[" + targetXyz[0] + "," + targetXyz[1] + "," + targetXyz[2] + "]"; Slog.d(ColorDisplayService.TAG, "setChromaticAdaptationMatrix: xyz = " + xyzString - + " matrix = " + matrixToString(mMatrix, 16)); + + " matrix = " + matrixToString(mMatrix, 4)); } @Override From 8aea8712d2defb55e1bcd16e61a1d5326e2d3b1a Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Thu, 8 Jul 2021 18:44:23 -0700 Subject: [PATCH 09/11] display: NightDisplay: Use chromatic adaptation when possible Night Light is currently implemented with a simple RGB tint (similar to the "wrong von Kries transform" but in linear sRGB instead of XYZ), which distorts some colors in addition to changing the neutral CCT. Switch to using full-blown chromatic adaptation (CAT16 + von Kries transform) instead to improve color quality when Night Light is active. Because accurate chromatic adaptation is only possible in linear color spaces, the old logic still needs to be used as a fallback in cases where color transformations can only be applied in the native (device) color space. This uses the existing CCT->RGB tint coefficients for the native color space, which is the only option because we can't make assumptions about the native color space (even if it's sRGB, we can't accommodate it with only matrices). Change-Id: I762610ed3c2cc417307485fd0a6a6d499b00c7d5 --- .../display/color/ColorDisplayService.java | 59 +++++++++++-------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index 0d3be47fc242..15efc7a57eed 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -47,6 +47,7 @@ import android.content.pm.PackageManagerInternal; import android.content.res.Resources; import android.database.ContentObserver; +import android.graphics.ColorSpace; import android.hardware.display.ColorDisplayManager; import android.hardware.display.ColorDisplayManager.AutoMode; import android.hardware.display.ColorDisplayManager.ColorMode; @@ -1249,46 +1250,54 @@ public float[] evaluate(float fraction, float[] startValue, float[] endValue) { } } - private final class NightDisplayTintController extends TintController { + private final class NightDisplayTintController extends ChromaticAdaptationTintController { - private final float[] mMatrix = new float[16]; - private final float[] mColorTempCoefficients = new float[9]; + private final float[] mNativeTempCoefficients = new float[9]; private Boolean mIsAvailable; private Integer mColorTemp; + private boolean mNeedsLinear; /** * Set coefficients based on whether the color matrix is linear or not. */ @Override - public void setUp(Context context, boolean needsLinear) { - final String[] coefficients = context.getResources().getStringArray(needsLinear - ? R.array.config_nightDisplayColorTemperatureCoefficients - : R.array.config_nightDisplayColorTemperatureCoefficientsNative); - for (int i = 0; i < 9 && i < coefficients.length; i++) { - mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]); + protected void setUpLocked(Context context, boolean needsLinear) { + mNeedsLinear = needsLinear; + if (needsLinear) { + final String[] coefficients = context.getResources().getStringArray( + R.array.config_nightDisplayColorTemperatureCoefficientsNative); + for (int i = 0; i < 9 && i < coefficients.length; i++) { + mNativeTempCoefficients[i] = Float.parseFloat(coefficients[i]); + } } } @Override public void setMatrix(int cct) { - if (mMatrix.length != 16) { - Slog.d(TAG, "The display transformation matrix must be 4x4"); - return; - } + Slog.d(ColorDisplayService.TAG, "setNightDisplayTemperatureMatrix: cct = " + cct); - Matrix.setIdentityM(mMatrix, 0); - - final float squareTemperature = cct * cct; - final float red = squareTemperature * mColorTempCoefficients[0] - + cct * mColorTempCoefficients[1] + mColorTempCoefficients[2]; - final float green = squareTemperature * mColorTempCoefficients[3] - + cct * mColorTempCoefficients[4] + mColorTempCoefficients[5]; - final float blue = squareTemperature * mColorTempCoefficients[6] - + cct * mColorTempCoefficients[7] + mColorTempCoefficients[8]; - mMatrix[0] = red; - mMatrix[5] = green; - mMatrix[10] = blue; + if (mNeedsLinear) { + // Use full-fledged chromatic adaptation if possible + synchronized (mLock) { + setMatrixLocked(ColorSpace.cctToXyz(cct)); + } + } else { + // Chromatic adaptation path can't handle non-linear (native) color spaces, so + // fall back to the legacy CCT-based tint path + Matrix.setIdentityM(mMatrix, 0); + + final float squareTemperature = cct * cct; + final float red = squareTemperature * mNativeTempCoefficients[0] + + cct * mNativeTempCoefficients[1] + mNativeTempCoefficients[2]; + final float green = squareTemperature * mNativeTempCoefficients[3] + + cct * mNativeTempCoefficients[4] + mNativeTempCoefficients[5]; + final float blue = squareTemperature * mNativeTempCoefficients[6] + + cct * mNativeTempCoefficients[7] + mNativeTempCoefficients[8]; + mMatrix[0] = red; + mMatrix[5] = green; + mMatrix[10] = blue; + } } @Override From 8eaf33a44f0019db0d521e9bee89e3479faf44a1 Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Tue, 3 Aug 2021 00:08:23 -0700 Subject: [PATCH 10/11] display: Fix night display matrix in unmanaged color modes Legacy RGB tinting with CCT-based coefficients is used in *unmanaged* color modes (i.e. needsLinear=false), so initialize them properly. This fixes black screens (all-zero matrix due to zero-initialized night display CCT coefficients in non-linear mode) when booting in an unmanaged color mode, such as Saturated on the Pixel 2 series. Change-Id: I60884142d9ed58be6bbb590d32f62253340910e3 --- .../com/android/server/display/color/ColorDisplayService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index 15efc7a57eed..2a3c20842e83 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -1264,7 +1264,7 @@ private final class NightDisplayTintController extends ChromaticAdaptationTintCo @Override protected void setUpLocked(Context context, boolean needsLinear) { mNeedsLinear = needsLinear; - if (needsLinear) { + if (!needsLinear) { final String[] coefficients = context.getResources().getStringArray( R.array.config_nightDisplayColorTemperatureCoefficientsNative); for (int i = 0; i < 9 && i < coefficients.length; i++) { From 638052d22b6a3386840782032dafb6c3f13ae83c Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Tue, 3 Aug 2021 18:05:31 -0700 Subject: [PATCH 11/11] display: Perform screen-off fade out animation in linear sRGB The sRGB transfer function is a piecewise function with linear and gamma 2.4 parts, not involving cosine or other magic constants. Fade colors in linear sRGB instead of non-linear sRGB + magic gamma to minimize color distortion as the animation progresses. Change-Id: I57db834f938cc63b7298af1c9dfe8c284dc6abe2 --- core/res/res/raw/color_fade_frag.frag | 26 ++++++++++++++----- .../com/android/server/display/ColorFade.java | 15 +++-------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/core/res/res/raw/color_fade_frag.frag b/core/res/res/raw/color_fade_frag.frag index 29975d5f7b5e..26cbd9e15e45 100644 --- a/core/res/res/raw/color_fade_frag.frag +++ b/core/res/res/raw/color_fade_frag.frag @@ -3,12 +3,26 @@ precision mediump float; uniform samplerExternalOES texUnit; uniform float opacity; -uniform float gamma; varying vec2 UV; -void main() -{ - vec4 color = texture2D(texUnit, UV); - vec3 rgb = pow(color.rgb * opacity, vec3(gamma)); - gl_FragColor = vec4(rgb, 1.0); +vec3 srgbTransfer(vec3 c) { + vec3 gamma = 1.055 * pow(c, vec3(1.0/2.4)) - 0.055; + vec3 linear = 12.92 * c; + bvec3 selectParts = lessThan(c, vec3(0.0031308)); + return mix(gamma, linear, selectParts); +} + +vec3 srgbTransferInv(vec3 c) { + vec3 gamma = pow((c + 0.055)/1.055, vec3(2.4)); + vec3 linear = c / 12.92; + bvec3 selectParts = lessThan(c, vec3(0.04045)); + return mix(gamma, linear, selectParts); +} + +void main() { + vec3 inRgb = srgbTransferInv(texture2D(texUnit, UV).rgb); + vec3 fade = inRgb * opacity * opacity; + vec3 outRgb = srgbTransfer(fade); + + gl_FragColor = vec4(outRgb, 1.0); } diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java index 430a767401f0..7aca8838c11f 100644 --- a/services/core/java/com/android/server/display/ColorFade.java +++ b/services/core/java/com/android/server/display/ColorFade.java @@ -106,7 +106,7 @@ final class ColorFade implements ScreenStateAnimator { private final float mProjMatrix[] = new float[16]; private final int[] mGLBuffers = new int[2]; private int mTexCoordLoc, mVertexLoc, mTexUnitLoc, mProjMatrixLoc, mTexMatrixLoc; - private int mOpacityLoc, mGammaLoc; + private int mOpacityLoc; private int mProgram; // Vertex and corresponding texture coordinates. @@ -253,7 +253,6 @@ private boolean initGLShaders(Context context) { mTexMatrixLoc = GLES20.glGetUniformLocation(mProgram, "tex_matrix"); mOpacityLoc = GLES20.glGetUniformLocation(mProgram, "opacity"); - mGammaLoc = GLES20.glGetUniformLocation(mProgram, "gamma"); mTexUnitLoc = GLES20.glGetUniformLocation(mProgram, "texUnit"); GLES20.glUseProgram(mProgram); @@ -397,12 +396,7 @@ public boolean draw(float level) { GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); // Draw the frame. - double one_minus_level = 1 - level; - double cos = Math.cos(Math.PI * one_minus_level); - double sign = cos < 0 ? -1 : 1; - float opacity = (float) -Math.pow(one_minus_level, 2) + 1; - float gamma = (float) ((0.5d * sign * Math.pow(cos, 2) + 0.5d) * 0.9d + 0.1d); - drawFaded(opacity, 1.f / gamma); + drawFaded(level); if (checkGlErrors("drawFrame")) { return false; } @@ -414,9 +408,9 @@ public boolean draw(float level) { return showSurface(1.0f); } - private void drawFaded(float opacity, float gamma) { + private void drawFaded(float opacity) { if (DEBUG) { - Slog.d(TAG, "drawFaded: opacity=" + opacity + ", gamma=" + gamma); + Slog.d(TAG, "drawFaded: opacity=" + opacity); } // Use shaders GLES20.glUseProgram(mProgram); @@ -425,7 +419,6 @@ private void drawFaded(float opacity, float gamma) { GLES20.glUniformMatrix4fv(mProjMatrixLoc, 1, false, mProjMatrix, 0); GLES20.glUniformMatrix4fv(mTexMatrixLoc, 1, false, mTexMatrix, 0); GLES20.glUniform1f(mOpacityLoc, opacity); - GLES20.glUniform1f(mGammaLoc, gamma); // Use textures GLES20.glActiveTexture(GLES20.GL_TEXTURE0);