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/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/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; 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); 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..7f10da195d3e --- /dev/null +++ b/services/core/java/com/android/server/display/color/ChromaticAdaptationTintController.java @@ -0,0 +1,240 @@ +/* + * 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; + + // 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); + + // 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, 4)); + } + + @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/ColorBalanceTintController.java b/services/core/java/com/android/server/display/color/ColorBalanceTintController.java new file mode 100644 index 000000000000..5b5ef7ffb619 --- /dev/null +++ b/services/core/java/com/android/server/display/color/ColorBalanceTintController.java @@ -0,0 +1,107 @@ +/* + * 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]; + private boolean mNeedsLinear; + + @Override + public void setUp(Context context, boolean needsLinear) { + mNeedsLinear = needsLinear; + } + + @Override + public float[] getMatrix() { + return Arrays.copyOf(mMatrix, mMatrix.length); + } + + @Override + public void setMatrix(int rgb) { + Matrix.setIdentityM(mMatrix, 0); + + 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 + 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); + } + } + + 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 16d41150e502..2a3c20842e83 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; @@ -110,7 +111,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; @@ -118,6 +119,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 +134,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 +361,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 +396,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 +441,10 @@ public void onChange(boolean selfChange, Uri uri) { updateDisplayWhiteBalanceStatus(); } + + if (mColorBalanceTintController.isAvailable(getContext())) { + mHandler.sendEmptyMessage(MSG_APPLY_DISPLAY_COLOR_BALANCE); + } } private void tearDown() { @@ -520,6 +540,11 @@ private void onDisplayColorModeChanged(int mode) { if (mDisplayWhiteBalanceTintController.isAvailable(getContext())) { updateDisplayWhiteBalanceStatus(); } + + if (mColorBalanceTintController.isAvailable(getContext())) { + mColorBalanceTintController.setUp(getContext(), + DisplayTransformManager.needsLinearColorMatrix(mode)); + } } private void onAccessibilityActivated() { @@ -930,6 +955,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)"); @@ -1206,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; - } - - Matrix.setIdentityM(mMatrix, 0); + Slog.d(ColorDisplayService.TAG, "setNightDisplayTemperatureMatrix: cct = " + cct); - 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 @@ -1468,6 +1520,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 +1767,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; 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..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.BRADFORD, - 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; } - } 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(); }