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 android.view.Display.DEFAULT_DISPLAY;
20 
21 import static com.android.server.display.color.DisplayTransformManager.LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.Size;
26 import android.content.Context;
27 import android.content.res.Resources;
28 import android.graphics.ColorSpace;
29 import android.hardware.display.ColorDisplayManager;
30 import android.hardware.display.DisplayManagerInternal;
31 import android.opengl.Matrix;
32 import android.util.Slog;
33 import android.view.SurfaceControl.DisplayPrimaries;
34 
35 import com.android.internal.R;
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.server.display.feature.DisplayManagerFlags;
38 
39 import java.io.PrintWriter;
40 
41 final class DisplayWhiteBalanceTintController extends ColorTemperatureTintController {
42 
43     // Three chromaticity coordinates per color: X, Y, and Z
44     private static final int NUM_VALUES_PER_PRIMARY = 3;
45     // Four colors: red, green, blue, and white
46     private static final int NUM_DISPLAY_PRIMARIES_VALS = 4 * NUM_VALUES_PER_PRIMARY;
47     private static final int COLORSPACE_MATRIX_LENGTH = 9;
48 
49     private final Object mLock = new Object();
50     @VisibleForTesting
51     int mTemperatureMin;
52     @VisibleForTesting
53     int mTemperatureMax;
54     private int mTemperatureDefault;
55     @VisibleForTesting
56     float[] mDisplayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
57     private int mDisplayNominalWhiteCct;
58     @VisibleForTesting
59     ColorSpace.Rgb mDisplayColorSpaceRGB;
60     private float[] mChromaticAdaptationMatrix;
61     // The temperature currently represented in the matrix.
62     @VisibleForTesting
63     int mCurrentColorTemperature;
64     private float[] mCurrentColorTemperatureXYZ;
65     @VisibleForTesting
66     boolean mSetUp = false;
67     private final float[] mMatrixDisplayWhiteBalance = new float[16];
68     private long mTransitionDuration;
69     private long mTransitionDurationIncrease;
70     private long mTransitionDurationDecrease;
71     private Boolean mIsAvailable;
72     // This feature becomes disallowed if the device is in an unsupported strong/light state.
73     private boolean mIsAllowed = true;
74     private int mTargetCct;
75     private int mAppliedCct;
76     private CctEvaluator mCctEvaluator;
77 
78     private final DisplayManagerInternal mDisplayManagerInternal;
79 
80     private final DisplayManagerFlags mDisplayManagerFlags;
81 
DisplayWhiteBalanceTintController(DisplayManagerInternal dm, DisplayManagerFlags displayManagerFlags)82     DisplayWhiteBalanceTintController(DisplayManagerInternal dm,
83             DisplayManagerFlags displayManagerFlags) {
84         mDisplayManagerInternal = dm;
85         mDisplayManagerFlags = displayManagerFlags;
86     }
87 
88     @Override
setUp(Context context, boolean needsLinear)89     public void setUp(Context context, boolean needsLinear) {
90         mSetUp = false;
91         final Resources res = context.getResources();
92 
93         // Initialize with the config value for light mode, so it starts in the right state.
94         setAllowed(res.getBoolean(R.bool.config_displayWhiteBalanceLightModeAllowed));
95 
96         ColorSpace.Rgb displayColorSpaceRGB = getDisplayColorSpaceFromSurfaceControl();
97         if (displayColorSpaceRGB == null) {
98             Slog.w(ColorDisplayService.TAG,
99                     "Failed to get display color space from SurfaceControl, trying res");
100             displayColorSpaceRGB = getDisplayColorSpaceFromResources(res);
101             if (displayColorSpaceRGB == null) {
102                 Slog.e(ColorDisplayService.TAG, "Failed to get display color space from resources");
103                 return;
104             }
105         }
106 
107         // Make sure display color space is valid
108         if (!isColorMatrixValid(displayColorSpaceRGB.getTransform())) {
109             Slog.e(ColorDisplayService.TAG, "Invalid display color space RGB-to-XYZ transform");
110             return;
111         }
112         if (!isColorMatrixValid(displayColorSpaceRGB.getInverseTransform())) {
113             Slog.e(ColorDisplayService.TAG, "Invalid display color space XYZ-to-RGB transform");
114             return;
115         }
116 
117         final String[] nominalWhiteValues = res.getStringArray(
118                 R.array.config_displayWhiteBalanceDisplayNominalWhite);
119         float[] displayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
120         for (int i = 0; i < nominalWhiteValues.length; i++) {
121             displayNominalWhiteXYZ[i] = Float.parseFloat(nominalWhiteValues[i]);
122         }
123 
124         final int displayNominalWhiteCct = res.getInteger(
125                 R.integer.config_displayWhiteBalanceDisplayNominalWhiteCct);
126 
127         final int colorTemperatureMin = res.getInteger(
128                 R.integer.config_displayWhiteBalanceColorTemperatureMin);
129         if (colorTemperatureMin <= 0) {
130             Slog.e(ColorDisplayService.TAG,
131                     "Display white balance minimum temperature must be greater than 0");
132             return;
133         }
134 
135         final int colorTemperatureMax = res.getInteger(
136                 R.integer.config_displayWhiteBalanceColorTemperatureMax);
137         if (colorTemperatureMax < colorTemperatureMin) {
138             Slog.e(ColorDisplayService.TAG,
139                     "Display white balance max temp must be greater or equal to min");
140             return;
141         }
142 
143         final int defaultTemperature = res.getInteger(
144                 R.integer.config_displayWhiteBalanceColorTemperatureDefault);
145 
146         mTransitionDuration = res.getInteger(
147                 R.integer.config_displayWhiteBalanceTransitionTime);
148 
149         if (!mDisplayManagerFlags.isAdaptiveTone2Enabled()) {
150             mTransitionDurationDecrease = mTransitionDuration;
151             mTransitionDurationIncrease = mTransitionDuration;
152         } else {
153             mTransitionDurationIncrease = res.getInteger(
154                     R.integer.config_displayWhiteBalanceTransitionTimeIncrease);
155             mTransitionDurationDecrease = res.getInteger(
156                     R.integer.config_displayWhiteBalanceTransitionTimeDecrease);
157         }
158 
159         int[] cctRangeMinimums = res.getIntArray(
160                 R.array.config_displayWhiteBalanceDisplayRangeMinimums);
161         int[] steps = res.getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
162 
163         synchronized (mLock) {
164             mDisplayColorSpaceRGB = displayColorSpaceRGB;
165             mDisplayNominalWhiteXYZ = displayNominalWhiteXYZ;
166             mDisplayNominalWhiteCct = displayNominalWhiteCct;
167             mTargetCct = mDisplayNominalWhiteCct;
168             mAppliedCct = mDisplayNominalWhiteCct;
169             mTemperatureMin = colorTemperatureMin;
170             mTemperatureMax = colorTemperatureMax;
171             mTemperatureDefault = defaultTemperature;
172             mSetUp = true;
173             mCctEvaluator = new CctEvaluator(mTemperatureMin, mTemperatureMax,
174                     cctRangeMinimums, steps);
175         }
176 
177         setMatrix(mTemperatureDefault);
178     }
179 
180     @Override
getMatrix()181     public float[] getMatrix() {
182         if (!mSetUp || !isActivated()) {
183             return ColorDisplayService.MATRIX_IDENTITY;
184         }
185         computeMatrixForCct(mAppliedCct);
186         return mMatrixDisplayWhiteBalance;
187     }
188 
189     @Override
getTargetCct()190     public int getTargetCct() {
191         return mTargetCct;
192     }
193 
194     /**
195      * Multiplies two 3x3 matrices, represented as non-null arrays of 9 floats.
196      *
197      * @param lhs 3x3 matrix, as a non-null array of 9 floats
198      * @param rhs 3x3 matrix, as a non-null array of 9 floats
199      * @return A new array of 9 floats containing the result of the multiplication
200      *         of rhs by lhs
201      */
202     @NonNull
203     @Size(9)
mul3x3(@onNull @ize9) float[] lhs, @NonNull @Size(9) float[] rhs)204     private static float[] mul3x3(@NonNull @Size(9) float[] lhs, @NonNull @Size(9) float[] rhs) {
205         float[] r = new float[9];
206         r[0] = lhs[0] * rhs[0] + lhs[3] * rhs[1] + lhs[6] * rhs[2];
207         r[1] = lhs[1] * rhs[0] + lhs[4] * rhs[1] + lhs[7] * rhs[2];
208         r[2] = lhs[2] * rhs[0] + lhs[5] * rhs[1] + lhs[8] * rhs[2];
209         r[3] = lhs[0] * rhs[3] + lhs[3] * rhs[4] + lhs[6] * rhs[5];
210         r[4] = lhs[1] * rhs[3] + lhs[4] * rhs[4] + lhs[7] * rhs[5];
211         r[5] = lhs[2] * rhs[3] + lhs[5] * rhs[4] + lhs[8] * rhs[5];
212         r[6] = lhs[0] * rhs[6] + lhs[3] * rhs[7] + lhs[6] * rhs[8];
213         r[7] = lhs[1] * rhs[6] + lhs[4] * rhs[7] + lhs[7] * rhs[8];
214         r[8] = lhs[2] * rhs[6] + lhs[5] * rhs[7] + lhs[8] * rhs[8];
215         return r;
216     }
217 
218     @Override
setMatrix(int cct)219     public void setMatrix(int cct) {
220         setTargetCct(cct);
221         computeMatrixForCct(mTargetCct);
222     }
223 
224     @Override
setTargetCct(int cct)225     public void setTargetCct(int cct) {
226         if (!mSetUp) {
227             Slog.w(ColorDisplayService.TAG,
228                     "Can't set display white balance temperature: uninitialized");
229             return;
230         }
231 
232         if (cct < mTemperatureMin) {
233             Slog.w(ColorDisplayService.TAG,
234                     "Requested display color temperature is below allowed minimum");
235             mTargetCct = mTemperatureMin;
236         } else if (cct > mTemperatureMax) {
237             Slog.w(ColorDisplayService.TAG,
238                     "Requested display color temperature is above allowed maximum");
239             mTargetCct = mTemperatureMax;
240         } else {
241             mTargetCct = cct;
242         }
243     }
244 
245     @Override
getDisabledCct()246     public int getDisabledCct() {
247         return mDisplayNominalWhiteCct;
248     }
249 
250     @Override
computeMatrixForCct(int cct)251     public float[] computeMatrixForCct(int cct) {
252         if (!mSetUp || cct == 0) {
253             Slog.w(ColorDisplayService.TAG, "Couldn't compute matrix for cct=" + cct);
254             return ColorDisplayService.MATRIX_IDENTITY;
255         }
256 
257         synchronized (mLock) {
258             mCurrentColorTemperature = cct;
259 
260             if (cct == mDisplayNominalWhiteCct && !isActivated()) {
261                 // DWB is finished turning off. Clear the matrix.
262                 Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0);
263             } else {
264                 computeMatrixForCctLocked(cct);
265             }
266 
267             Slog.d(ColorDisplayService.TAG, "computeDisplayWhiteBalanceMatrix: cct =" + cct
268                     + " matrix =" + matrixToString(mMatrixDisplayWhiteBalance, 16));
269 
270             return mMatrixDisplayWhiteBalance;
271         }
272     }
273 
computeMatrixForCctLocked(int cct)274     private void computeMatrixForCctLocked(int cct) {
275         // Adapt the display's nominal white point to match the requested CCT value
276         mCurrentColorTemperatureXYZ = ColorSpace.cctToXyz(cct);
277 
278         mChromaticAdaptationMatrix =
279                 ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD,
280                         mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ);
281 
282         // Convert the adaptation matrix to RGB space
283         float[] result = mul3x3(mChromaticAdaptationMatrix,
284                 mDisplayColorSpaceRGB.getTransform());
285         result = mul3x3(mDisplayColorSpaceRGB.getInverseTransform(), result);
286 
287         // Normalize the transform matrix to peak white value in RGB space
288         final float adaptedMaxR = result[0] + result[3] + result[6];
289         final float adaptedMaxG = result[1] + result[4] + result[7];
290         final float adaptedMaxB = result[2] + result[5] + result[8];
291         final float denum = Math.max(Math.max(adaptedMaxR, adaptedMaxG), adaptedMaxB);
292 
293         Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0);
294 
295         for (int i = 0; i < result.length; i++) {
296             result[i] /= denum;
297             if (!isColorMatrixCoeffValid(result[i])) {
298                 Slog.e(ColorDisplayService.TAG, "Invalid DWB color matrix");
299                 return;
300             }
301         }
302 
303         java.lang.System.arraycopy(result, 0, mMatrixDisplayWhiteBalance, 0, 3);
304         java.lang.System.arraycopy(result, 3, mMatrixDisplayWhiteBalance, 4, 3);
305         java.lang.System.arraycopy(result, 6, mMatrixDisplayWhiteBalance, 8, 3);
306     }
307 
308     @Override
getAppliedCct()309     int getAppliedCct() {
310         return mAppliedCct;
311     }
312 
313     @Override
setAppliedCct(int cct)314     void setAppliedCct(int cct) {
315         mAppliedCct = cct;
316     }
317 
318     @Override
319     @Nullable
getEvaluator()320     CctEvaluator getEvaluator() {
321         return mCctEvaluator;
322     }
323 
324     @Override
getLevel()325     public int getLevel() {
326         return LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
327     }
328 
329     @Override
isAvailable(Context context)330     public boolean isAvailable(Context context) {
331         if (mIsAvailable == null) {
332             mIsAvailable = ColorDisplayManager.isDisplayWhiteBalanceAvailable(context);
333         }
334         return mIsAvailable;
335     }
336 
337     @Override
getTransitionDurationMilliseconds()338     public long getTransitionDurationMilliseconds() {
339         return mTransitionDuration;
340     }
341 
342     @Override
getTransitionDurationMilliseconds(boolean isIncreasing)343     public long getTransitionDurationMilliseconds(boolean isIncreasing) {
344         return isIncreasing ? mTransitionDurationIncrease : mTransitionDurationDecrease;
345     }
346 
347     @Override
dump(PrintWriter pw)348     public void dump(PrintWriter pw) {
349         synchronized (mLock) {
350             pw.println("    mSetUp = " + mSetUp);
351             if (!mSetUp) {
352                 return;
353             }
354 
355             pw.println("    mTemperatureMin = " + mTemperatureMin);
356             pw.println("    mTemperatureMax = " + mTemperatureMax);
357             pw.println("    mTemperatureDefault = " + mTemperatureDefault);
358             pw.println("    mDisplayNominalWhiteCct = " + mDisplayNominalWhiteCct);
359             pw.println("    mCurrentColorTemperature = " + mCurrentColorTemperature);
360             pw.println("    mTargetCct = " + mTargetCct);
361             pw.println("    mAppliedCct = " + mAppliedCct);
362             pw.println("    mCurrentColorTemperatureXYZ = "
363                     + matrixToString(mCurrentColorTemperatureXYZ, 3));
364             pw.println("    mDisplayColorSpaceRGB RGB-to-XYZ = "
365                     + matrixToString(mDisplayColorSpaceRGB.getTransform(), 3));
366             pw.println("    mChromaticAdaptationMatrix = "
367                     + matrixToString(mChromaticAdaptationMatrix, 3));
368             pw.println("    mDisplayColorSpaceRGB XYZ-to-RGB = "
369                     + matrixToString(mDisplayColorSpaceRGB.getInverseTransform(), 3));
370             pw.println("    mMatrixDisplayWhiteBalance = "
371                     + matrixToString(mMatrixDisplayWhiteBalance, 4));
372             pw.println("    mIsAllowed = " + mIsAllowed);
373             pw.println("    mTransitionDuration = " + mTransitionDuration);
374             pw.println("    mTransitionDurationIncrease = " + mTransitionDurationIncrease);
375             pw.println("    mTransitionDurationDecrease = " + mTransitionDurationDecrease);
376         }
377     }
378 
getLuminance()379     public float getLuminance() {
380         synchronized (mLock) {
381             if (mChromaticAdaptationMatrix != null && mChromaticAdaptationMatrix.length == 9) {
382                 // Compute only the luminance (y) value of the xyz * [1 1 1] transform.
383                 return 1 / (mChromaticAdaptationMatrix[1] + mChromaticAdaptationMatrix[4]
384                         + mChromaticAdaptationMatrix[7]);
385             } else {
386                 return -1;
387             }
388         }
389     }
390 
setAllowed(boolean allowed)391     public void setAllowed(boolean allowed) {
392         mIsAllowed = allowed;
393     }
394 
isAllowed()395     public boolean isAllowed() {
396         return mIsAllowed;
397     }
398 
makeRgbColorSpaceFromXYZ(float[] redGreenBlueXYZ, float[] whiteXYZ)399     private ColorSpace.Rgb makeRgbColorSpaceFromXYZ(float[] redGreenBlueXYZ, float[] whiteXYZ) {
400         return new ColorSpace.Rgb(
401                 "Display Color Space",
402                 redGreenBlueXYZ,
403                 whiteXYZ,
404                 2.2f // gamma, unused for display white balance
405         );
406     }
407 
getDisplayColorSpaceFromSurfaceControl()408     private ColorSpace.Rgb getDisplayColorSpaceFromSurfaceControl() {
409         DisplayPrimaries primaries =
410                 mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY);
411         if (primaries == null || primaries.red == null || primaries.green == null
412                 || primaries.blue == null || primaries.white == null) {
413             return null;
414         }
415 
416         return makeRgbColorSpaceFromXYZ(
417                 new float[]{
418                         primaries.red.X, primaries.red.Y, primaries.red.Z,
419                         primaries.green.X, primaries.green.Y, primaries.green.Z,
420                         primaries.blue.X, primaries.blue.Y, primaries.blue.Z,
421                 },
422                 new float[]{primaries.white.X, primaries.white.Y, primaries.white.Z}
423         );
424     }
425 
getDisplayColorSpaceFromResources(Resources res)426     private ColorSpace.Rgb getDisplayColorSpaceFromResources(Resources res) {
427         final String[] displayPrimariesValues = res.getStringArray(
428                 R.array.config_displayWhiteBalanceDisplayPrimaries);
429         float[] displayRedGreenBlueXYZ =
430                 new float[NUM_DISPLAY_PRIMARIES_VALS - NUM_VALUES_PER_PRIMARY];
431         float[] displayWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
432 
433         for (int i = 0; i < displayRedGreenBlueXYZ.length; i++) {
434             displayRedGreenBlueXYZ[i] = Float.parseFloat(displayPrimariesValues[i]);
435         }
436 
437         for (int i = 0; i < displayWhiteXYZ.length; i++) {
438             displayWhiteXYZ[i] = Float.parseFloat(
439                     displayPrimariesValues[displayRedGreenBlueXYZ.length + i]);
440         }
441 
442         return makeRgbColorSpaceFromXYZ(displayRedGreenBlueXYZ, displayWhiteXYZ);
443     }
444 
isColorMatrixCoeffValid(float coeff)445     private boolean isColorMatrixCoeffValid(float coeff) {
446         return !Float.isNaN(coeff) && !Float.isInfinite(coeff);
447     }
448 
isColorMatrixValid(float[] matrix)449     private boolean isColorMatrixValid(float[] matrix) {
450         if (matrix == null || matrix.length != COLORSPACE_MATRIX_LENGTH) {
451             return false;
452         }
453 
454         for (float value : matrix) {
455             if (!isColorMatrixCoeffValid(value)) {
456                 return false;
457             }
458         }
459 
460         return true;
461     }
462 
463 }
464