• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.display.color;
18 
19 import static com.android.server.display.color.DisplayTransformManager.LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Size;
23 import android.content.Context;
24 import android.content.res.Resources;
25 import android.graphics.ColorSpace;
26 import android.hardware.display.ColorDisplayManager;
27 import android.opengl.Matrix;
28 import android.os.IBinder;
29 import android.util.Slog;
30 import android.view.SurfaceControl;
31 import android.view.SurfaceControl.DisplayPrimaries;
32 
33 import com.android.internal.R;
34 import com.android.internal.annotations.VisibleForTesting;
35 
36 import java.io.PrintWriter;
37 
38 final class DisplayWhiteBalanceTintController extends TintController {
39 
40     // Three chromaticity coordinates per color: X, Y, and Z
41     private static final int NUM_VALUES_PER_PRIMARY = 3;
42     // Four colors: red, green, blue, and white
43     private static final int NUM_DISPLAY_PRIMARIES_VALS = 4 * NUM_VALUES_PER_PRIMARY;
44     private static final int COLORSPACE_MATRIX_LENGTH = 9;
45 
46     private final Object mLock = new Object();
47     @VisibleForTesting
48     int mTemperatureMin;
49     @VisibleForTesting
50     int mTemperatureMax;
51     private int mTemperatureDefault;
52     @VisibleForTesting
53     float[] mDisplayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
54     @VisibleForTesting
55     ColorSpace.Rgb mDisplayColorSpaceRGB;
56     private float[] mChromaticAdaptationMatrix;
57     @VisibleForTesting
58     int mCurrentColorTemperature;
59     private float[] mCurrentColorTemperatureXYZ;
60     @VisibleForTesting
61     boolean mSetUp = false;
62     private final float[] mMatrixDisplayWhiteBalance = new float[16];
63     private long mTransitionDuration;
64     private Boolean mIsAvailable;
65     // This feature becomes disallowed if the device is in an unsupported strong/light state.
66     private boolean mIsAllowed = true;
67 
68     @Override
setUp(Context context, boolean needsLinear)69     public void setUp(Context context, boolean needsLinear) {
70         mSetUp = false;
71         final Resources res = context.getResources();
72 
73         // Initialize with the config value for light mode, so it starts in the right state.
74         setAllowed(res.getBoolean(R.bool.config_displayWhiteBalanceLightModeAllowed));
75 
76         ColorSpace.Rgb displayColorSpaceRGB = getDisplayColorSpaceFromSurfaceControl();
77         if (displayColorSpaceRGB == null) {
78             Slog.w(ColorDisplayService.TAG,
79                     "Failed to get display color space from SurfaceControl, trying res");
80             displayColorSpaceRGB = getDisplayColorSpaceFromResources(res);
81             if (displayColorSpaceRGB == null) {
82                 Slog.e(ColorDisplayService.TAG, "Failed to get display color space from resources");
83                 return;
84             }
85         }
86 
87         // Make sure display color space is valid
88         if (!isColorMatrixValid(displayColorSpaceRGB.getTransform())) {
89             Slog.e(ColorDisplayService.TAG, "Invalid display color space RGB-to-XYZ transform");
90             return;
91         }
92         if (!isColorMatrixValid(displayColorSpaceRGB.getInverseTransform())) {
93             Slog.e(ColorDisplayService.TAG, "Invalid display color space XYZ-to-RGB transform");
94             return;
95         }
96 
97         final String[] nominalWhiteValues = res.getStringArray(
98                 R.array.config_displayWhiteBalanceDisplayNominalWhite);
99         float[] displayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
100         for (int i = 0; i < nominalWhiteValues.length; i++) {
101             displayNominalWhiteXYZ[i] = Float.parseFloat(nominalWhiteValues[i]);
102         }
103 
104         final int colorTemperatureMin = res.getInteger(
105                 R.integer.config_displayWhiteBalanceColorTemperatureMin);
106         if (colorTemperatureMin <= 0) {
107             Slog.e(ColorDisplayService.TAG,
108                     "Display white balance minimum temperature must be greater than 0");
109             return;
110         }
111 
112         final int colorTemperatureMax = res.getInteger(
113                 R.integer.config_displayWhiteBalanceColorTemperatureMax);
114         if (colorTemperatureMax < colorTemperatureMin) {
115             Slog.e(ColorDisplayService.TAG,
116                     "Display white balance max temp must be greater or equal to min");
117             return;
118         }
119 
120         final int colorTemperature = res.getInteger(
121                 R.integer.config_displayWhiteBalanceColorTemperatureDefault);
122 
123         mTransitionDuration = res.getInteger(
124                 R.integer.config_displayWhiteBalanceTransitionTime);
125 
126         synchronized (mLock) {
127             mDisplayColorSpaceRGB = displayColorSpaceRGB;
128             mDisplayNominalWhiteXYZ = displayNominalWhiteXYZ;
129             mTemperatureMin = colorTemperatureMin;
130             mTemperatureMax = colorTemperatureMax;
131             mTemperatureDefault = colorTemperature;
132             mSetUp = true;
133         }
134 
135         setMatrix(mTemperatureDefault);
136     }
137 
138     @Override
getMatrix()139     public float[] getMatrix() {
140         return mSetUp && isActivated() ? mMatrixDisplayWhiteBalance
141                 : ColorDisplayService.MATRIX_IDENTITY;
142     }
143 
144     /**
145      * Multiplies two 3x3 matrices, represented as non-null arrays of 9 floats.
146      *
147      * @param lhs 3x3 matrix, as a non-null array of 9 floats
148      * @param rhs 3x3 matrix, as a non-null array of 9 floats
149      * @return A new array of 9 floats containing the result of the multiplication
150      *         of rhs by lhs
151      */
152     @NonNull
153     @Size(9)
mul3x3(@onNull @ize9) float[] lhs, @NonNull @Size(9) float[] rhs)154     private static float[] mul3x3(@NonNull @Size(9) float[] lhs, @NonNull @Size(9) float[] rhs) {
155         float[] r = new float[9];
156         r[0] = lhs[0] * rhs[0] + lhs[3] * rhs[1] + lhs[6] * rhs[2];
157         r[1] = lhs[1] * rhs[0] + lhs[4] * rhs[1] + lhs[7] * rhs[2];
158         r[2] = lhs[2] * rhs[0] + lhs[5] * rhs[1] + lhs[8] * rhs[2];
159         r[3] = lhs[0] * rhs[3] + lhs[3] * rhs[4] + lhs[6] * rhs[5];
160         r[4] = lhs[1] * rhs[3] + lhs[4] * rhs[4] + lhs[7] * rhs[5];
161         r[5] = lhs[2] * rhs[3] + lhs[5] * rhs[4] + lhs[8] * rhs[5];
162         r[6] = lhs[0] * rhs[6] + lhs[3] * rhs[7] + lhs[6] * rhs[8];
163         r[7] = lhs[1] * rhs[6] + lhs[4] * rhs[7] + lhs[7] * rhs[8];
164         r[8] = lhs[2] * rhs[6] + lhs[5] * rhs[7] + lhs[8] * rhs[8];
165         return r;
166     }
167 
168     @Override
setMatrix(int cct)169     public void setMatrix(int cct) {
170         if (!mSetUp) {
171             Slog.w(ColorDisplayService.TAG,
172                     "Can't set display white balance temperature: uninitialized");
173             return;
174         }
175 
176         if (cct < mTemperatureMin) {
177             Slog.w(ColorDisplayService.TAG,
178                     "Requested display color temperature is below allowed minimum");
179             cct = mTemperatureMin;
180         } else if (cct > mTemperatureMax) {
181             Slog.w(ColorDisplayService.TAG,
182                     "Requested display color temperature is above allowed maximum");
183             cct = mTemperatureMax;
184         }
185 
186         synchronized (mLock) {
187             mCurrentColorTemperature = cct;
188 
189             // Adapt the display's nominal white point to match the requested CCT value
190             mCurrentColorTemperatureXYZ = ColorSpace.cctToXyz(cct);
191 
192             mChromaticAdaptationMatrix =
193                     ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD,
194                             mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ);
195 
196             // Convert the adaptation matrix to RGB space
197             float[] result = mul3x3(mChromaticAdaptationMatrix,
198                     mDisplayColorSpaceRGB.getTransform());
199             result = mul3x3(mDisplayColorSpaceRGB.getInverseTransform(), result);
200 
201             // Normalize the transform matrix to peak white value in RGB space
202             final float adaptedMaxR = result[0] + result[3] + result[6];
203             final float adaptedMaxG = result[1] + result[4] + result[7];
204             final float adaptedMaxB = result[2] + result[5] + result[8];
205             final float denum = Math.max(Math.max(adaptedMaxR, adaptedMaxG), adaptedMaxB);
206 
207             Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0);
208             for (int i = 0; i < result.length; i++) {
209                 result[i] /= denum;
210                 if (!isColorMatrixCoeffValid(result[i])) {
211                     Slog.e(ColorDisplayService.TAG, "Invalid DWB color matrix");
212                     return;
213                 }
214             }
215 
216             java.lang.System.arraycopy(result, 0, mMatrixDisplayWhiteBalance, 0, 3);
217             java.lang.System.arraycopy(result, 3, mMatrixDisplayWhiteBalance, 4, 3);
218             java.lang.System.arraycopy(result, 6, mMatrixDisplayWhiteBalance, 8, 3);
219         }
220 
221         Slog.d(ColorDisplayService.TAG, "setDisplayWhiteBalanceTemperatureMatrix: cct = " + cct
222                 + " matrix = " + matrixToString(mMatrixDisplayWhiteBalance, 16));
223     }
224 
225     @Override
getLevel()226     public int getLevel() {
227         return LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
228     }
229 
230     @Override
isAvailable(Context context)231     public boolean isAvailable(Context context) {
232         if (mIsAvailable == null) {
233             mIsAvailable = ColorDisplayManager.isDisplayWhiteBalanceAvailable(context);
234         }
235         return mIsAvailable;
236     }
237 
238     @Override
getTransitionDurationMilliseconds()239     public long getTransitionDurationMilliseconds() {
240         return mTransitionDuration;
241     }
242 
243     @Override
dump(PrintWriter pw)244     public void dump(PrintWriter pw) {
245         synchronized (mLock) {
246             pw.println("    mSetUp = " + mSetUp);
247             if (!mSetUp) {
248                 return;
249             }
250 
251             pw.println("    mTemperatureMin = " + mTemperatureMin);
252             pw.println("    mTemperatureMax = " + mTemperatureMax);
253             pw.println("    mTemperatureDefault = " + mTemperatureDefault);
254             pw.println("    mCurrentColorTemperature = " + mCurrentColorTemperature);
255             pw.println("    mCurrentColorTemperatureXYZ = "
256                     + matrixToString(mCurrentColorTemperatureXYZ, 3));
257             pw.println("    mDisplayColorSpaceRGB RGB-to-XYZ = "
258                     + matrixToString(mDisplayColorSpaceRGB.getTransform(), 3));
259             pw.println("    mChromaticAdaptationMatrix = "
260                     + matrixToString(mChromaticAdaptationMatrix, 3));
261             pw.println("    mDisplayColorSpaceRGB XYZ-to-RGB = "
262                     + matrixToString(mDisplayColorSpaceRGB.getInverseTransform(), 3));
263             pw.println("    mMatrixDisplayWhiteBalance = "
264                     + matrixToString(mMatrixDisplayWhiteBalance, 4));
265             pw.println("    mIsAllowed = " + mIsAllowed);
266         }
267     }
268 
getLuminance()269     public float getLuminance() {
270         synchronized (mLock) {
271             if (mChromaticAdaptationMatrix != null && mChromaticAdaptationMatrix.length == 9) {
272                 // Compute only the luminance (y) value of the xyz * [1 1 1] transform.
273                 return 1 / (mChromaticAdaptationMatrix[1] + mChromaticAdaptationMatrix[4]
274                         + mChromaticAdaptationMatrix[7]);
275             } else {
276                 return -1;
277             }
278         }
279     }
280 
setAllowed(boolean allowed)281     public void setAllowed(boolean allowed) {
282         mIsAllowed = allowed;
283     }
284 
isAllowed()285     public boolean isAllowed() {
286         return mIsAllowed;
287     }
288 
makeRgbColorSpaceFromXYZ(float[] redGreenBlueXYZ, float[] whiteXYZ)289     private ColorSpace.Rgb makeRgbColorSpaceFromXYZ(float[] redGreenBlueXYZ, float[] whiteXYZ) {
290         return new ColorSpace.Rgb(
291                 "Display Color Space",
292                 redGreenBlueXYZ,
293                 whiteXYZ,
294                 2.2f // gamma, unused for display white balance
295         );
296     }
297 
getDisplayColorSpaceFromSurfaceControl()298     private ColorSpace.Rgb getDisplayColorSpaceFromSurfaceControl() {
299         final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
300         if (displayToken == null) {
301             return null;
302         }
303 
304         DisplayPrimaries primaries = SurfaceControl.getDisplayNativePrimaries(displayToken);
305         if (primaries == null || primaries.red == null || primaries.green == null
306                 || primaries.blue == null || primaries.white == null) {
307             return null;
308         }
309 
310         return makeRgbColorSpaceFromXYZ(
311                 new float[]{
312                         primaries.red.X, primaries.red.Y, primaries.red.Z,
313                         primaries.green.X, primaries.green.Y, primaries.green.Z,
314                         primaries.blue.X, primaries.blue.Y, primaries.blue.Z,
315                 },
316                 new float[]{primaries.white.X, primaries.white.Y, primaries.white.Z}
317         );
318     }
319 
getDisplayColorSpaceFromResources(Resources res)320     private ColorSpace.Rgb getDisplayColorSpaceFromResources(Resources res) {
321         final String[] displayPrimariesValues = res.getStringArray(
322                 R.array.config_displayWhiteBalanceDisplayPrimaries);
323         float[] displayRedGreenBlueXYZ =
324                 new float[NUM_DISPLAY_PRIMARIES_VALS - NUM_VALUES_PER_PRIMARY];
325         float[] displayWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
326 
327         for (int i = 0; i < displayRedGreenBlueXYZ.length; i++) {
328             displayRedGreenBlueXYZ[i] = Float.parseFloat(displayPrimariesValues[i]);
329         }
330 
331         for (int i = 0; i < displayWhiteXYZ.length; i++) {
332             displayWhiteXYZ[i] = Float.parseFloat(
333                     displayPrimariesValues[displayRedGreenBlueXYZ.length + i]);
334         }
335 
336         return makeRgbColorSpaceFromXYZ(displayRedGreenBlueXYZ, displayWhiteXYZ);
337     }
338 
isColorMatrixCoeffValid(float coeff)339     private boolean isColorMatrixCoeffValid(float coeff) {
340         if (Float.isNaN(coeff) || Float.isInfinite(coeff)) {
341             return false;
342         }
343 
344         return true;
345     }
346 
isColorMatrixValid(float[] matrix)347     private boolean isColorMatrixValid(float[] matrix) {
348         if (matrix == null || matrix.length != COLORSPACE_MATRIX_LENGTH) {
349             return false;
350         }
351 
352         for (int i = 0; i < matrix.length; i++) {
353             if (!isColorMatrixCoeffValid(matrix[i])) {
354                 return false;
355             }
356         }
357 
358         return true;
359     }
360 
361 }
362