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.content.Context;
22 import android.content.res.Resources;
23 import android.graphics.ColorSpace;
24 import android.hardware.display.ColorDisplayManager;
25 import android.opengl.Matrix;
26 import android.os.IBinder;
27 import android.util.Slog;
28 import android.view.SurfaceControl;
29 import android.view.SurfaceControl.DisplayPrimaries;
30 
31 import com.android.internal.R;
32 import com.android.internal.annotations.VisibleForTesting;
33 
34 import java.io.PrintWriter;
35 import java.lang.System;
36 
37 final class DisplayWhiteBalanceTintController extends TintController {
38 
39     // Three chromaticity coordinates per color: X, Y, and Z
40     private static final int NUM_VALUES_PER_PRIMARY = 3;
41     // Four colors: red, green, blue, and white
42     private static final int NUM_DISPLAY_PRIMARIES_VALS = 4 * NUM_VALUES_PER_PRIMARY;
43     private static final int COLORSPACE_MATRIX_LENGTH = 9;
44 
45     private final Object mLock = new Object();
46     @VisibleForTesting
47     int mTemperatureMin;
48     @VisibleForTesting
49     int mTemperatureMax;
50     private int mTemperatureDefault;
51     @VisibleForTesting
52     float[] mDisplayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
53     @VisibleForTesting
54     ColorSpace.Rgb mDisplayColorSpaceRGB;
55     private float[] mChromaticAdaptationMatrix;
56     @VisibleForTesting
57     int mCurrentColorTemperature;
58     private float[] mCurrentColorTemperatureXYZ;
59     @VisibleForTesting
60     boolean mSetUp = false;
61     private float[] mMatrixDisplayWhiteBalance = new float[16];
62     private Boolean mIsAvailable;
63 
64     @Override
setUp(Context context, boolean needsLinear)65     public void setUp(Context context, boolean needsLinear) {
66         mSetUp = false;
67         final Resources res = context.getResources();
68 
69         ColorSpace.Rgb displayColorSpaceRGB = getDisplayColorSpaceFromSurfaceControl();
70         if (displayColorSpaceRGB == null) {
71             Slog.w(ColorDisplayService.TAG,
72                     "Failed to get display color space from SurfaceControl, trying res");
73             displayColorSpaceRGB = getDisplayColorSpaceFromResources(res);
74             if (displayColorSpaceRGB == null) {
75                 Slog.e(ColorDisplayService.TAG, "Failed to get display color space from resources");
76                 return;
77             }
78         }
79 
80         // Make sure display color space is valid
81         if (!isColorMatrixValid(displayColorSpaceRGB.getTransform())) {
82             Slog.e(ColorDisplayService.TAG, "Invalid display color space RGB-to-XYZ transform");
83             return;
84         }
85         if (!isColorMatrixValid(displayColorSpaceRGB.getInverseTransform())) {
86             Slog.e(ColorDisplayService.TAG, "Invalid display color space XYZ-to-RGB transform");
87             return;
88         }
89 
90         final String[] nominalWhiteValues = res.getStringArray(
91                 R.array.config_displayWhiteBalanceDisplayNominalWhite);
92         float[] displayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
93         for (int i = 0; i < nominalWhiteValues.length; i++) {
94             displayNominalWhiteXYZ[i] = Float.parseFloat(nominalWhiteValues[i]);
95         }
96 
97         final int colorTemperatureMin = res.getInteger(
98                 R.integer.config_displayWhiteBalanceColorTemperatureMin);
99         if (colorTemperatureMin <= 0) {
100             Slog.e(ColorDisplayService.TAG,
101                     "Display white balance minimum temperature must be greater than 0");
102             return;
103         }
104 
105         final int colorTemperatureMax = res.getInteger(
106                 R.integer.config_displayWhiteBalanceColorTemperatureMax);
107         if (colorTemperatureMax < colorTemperatureMin) {
108             Slog.e(ColorDisplayService.TAG,
109                     "Display white balance max temp must be greater or equal to min");
110             return;
111         }
112 
113         final int colorTemperature = res.getInteger(
114                 R.integer.config_displayWhiteBalanceColorTemperatureDefault);
115 
116         synchronized (mLock) {
117             mDisplayColorSpaceRGB = displayColorSpaceRGB;
118             mDisplayNominalWhiteXYZ = displayNominalWhiteXYZ;
119             mTemperatureMin = colorTemperatureMin;
120             mTemperatureMax = colorTemperatureMax;
121             mTemperatureDefault = colorTemperature;
122             mSetUp = true;
123         }
124 
125         setMatrix(mTemperatureDefault);
126     }
127 
128     @Override
getMatrix()129     public float[] getMatrix() {
130         return mSetUp && isActivated() ? mMatrixDisplayWhiteBalance
131                 : ColorDisplayService.MATRIX_IDENTITY;
132     }
133 
134     @Override
setMatrix(int cct)135     public void setMatrix(int cct) {
136         if (!mSetUp) {
137             Slog.w(ColorDisplayService.TAG,
138                     "Can't set display white balance temperature: uninitialized");
139             return;
140         }
141 
142         if (cct < mTemperatureMin) {
143             Slog.w(ColorDisplayService.TAG,
144                     "Requested display color temperature is below allowed minimum");
145             cct = mTemperatureMin;
146         } else if (cct > mTemperatureMax) {
147             Slog.w(ColorDisplayService.TAG,
148                     "Requested display color temperature is above allowed maximum");
149             cct = mTemperatureMax;
150         }
151 
152         synchronized (mLock) {
153             mCurrentColorTemperature = cct;
154 
155             // Adapt the display's nominal white point to match the requested CCT value
156             mCurrentColorTemperatureXYZ = ColorSpace.cctToXyz(cct);
157 
158             mChromaticAdaptationMatrix =
159                     ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD,
160                             mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ);
161 
162             // Convert the adaptation matrix to RGB space
163             float[] result = ColorSpace.mul3x3(mChromaticAdaptationMatrix,
164                     mDisplayColorSpaceRGB.getTransform());
165             result = ColorSpace.mul3x3(mDisplayColorSpaceRGB.getInverseTransform(), result);
166 
167             // Normalize the transform matrix to peak white value in RGB space
168             final float adaptedMaxR = result[0] + result[3] + result[6];
169             final float adaptedMaxG = result[1] + result[4] + result[7];
170             final float adaptedMaxB = result[2] + result[5] + result[8];
171             final float denum = Math.max(Math.max(adaptedMaxR, adaptedMaxG), adaptedMaxB);
172 
173             Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0);
174             for (int i = 0; i < result.length; i++) {
175                 result[i] /= denum;
176                 if (!isColorMatrixCoeffValid(result[i])) {
177                     Slog.e(ColorDisplayService.TAG, "Invalid DWB color matrix");
178                     return;
179                 }
180             }
181 
182             java.lang.System.arraycopy(result, 0, mMatrixDisplayWhiteBalance, 0, 3);
183             java.lang.System.arraycopy(result, 3, mMatrixDisplayWhiteBalance, 4, 3);
184             java.lang.System.arraycopy(result, 6, mMatrixDisplayWhiteBalance, 8, 3);
185         }
186 
187         Slog.d(ColorDisplayService.TAG, "setDisplayWhiteBalanceTemperatureMatrix: cct = " + cct
188                 + " matrix = " + matrixToString(mMatrixDisplayWhiteBalance, 16));
189     }
190 
191     @Override
getLevel()192     public int getLevel() {
193         return LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
194     }
195 
196     @Override
isAvailable(Context context)197     public boolean isAvailable(Context context) {
198         if (mIsAvailable == null) {
199             mIsAvailable = ColorDisplayManager.isDisplayWhiteBalanceAvailable(context);
200         }
201         return mIsAvailable;
202     }
203 
204     @Override
dump(PrintWriter pw)205     public void dump(PrintWriter pw) {
206         synchronized (mLock) {
207             pw.println("    mSetUp = " + mSetUp);
208             if (!mSetUp) {
209                 return;
210             }
211 
212             pw.println("    mTemperatureMin = " + mTemperatureMin);
213             pw.println("    mTemperatureMax = " + mTemperatureMax);
214             pw.println("    mTemperatureDefault = " + mTemperatureDefault);
215             pw.println("    mCurrentColorTemperature = " + mCurrentColorTemperature);
216             pw.println("    mCurrentColorTemperatureXYZ = "
217                     + matrixToString(mCurrentColorTemperatureXYZ, 3));
218             pw.println("    mDisplayColorSpaceRGB RGB-to-XYZ = "
219                     + matrixToString(mDisplayColorSpaceRGB.getTransform(), 3));
220             pw.println("    mChromaticAdaptationMatrix = "
221                     + matrixToString(mChromaticAdaptationMatrix, 3));
222             pw.println("    mDisplayColorSpaceRGB XYZ-to-RGB = "
223                     + matrixToString(mDisplayColorSpaceRGB.getInverseTransform(), 3));
224             pw.println("    mMatrixDisplayWhiteBalance = "
225                     + matrixToString(mMatrixDisplayWhiteBalance, 4));
226         }
227     }
228 
makeRgbColorSpaceFromXYZ(float[] redGreenBlueXYZ, float[] whiteXYZ)229     private ColorSpace.Rgb makeRgbColorSpaceFromXYZ(float[] redGreenBlueXYZ, float[] whiteXYZ) {
230         return new ColorSpace.Rgb(
231                 "Display Color Space",
232                 redGreenBlueXYZ,
233                 whiteXYZ,
234                 2.2f // gamma, unused for display white balance
235         );
236     }
237 
getDisplayColorSpaceFromSurfaceControl()238     private ColorSpace.Rgb getDisplayColorSpaceFromSurfaceControl() {
239         final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
240         if (displayToken == null) {
241             return null;
242         }
243 
244         DisplayPrimaries primaries = SurfaceControl.getDisplayNativePrimaries(displayToken);
245         if (primaries == null || primaries.red == null || primaries.green == null
246                 || primaries.blue == null || primaries.white == null) {
247             return null;
248         }
249 
250         return makeRgbColorSpaceFromXYZ(
251                 new float[]{
252                         primaries.red.X, primaries.red.Y, primaries.red.Z,
253                         primaries.green.X, primaries.green.Y, primaries.green.Z,
254                         primaries.blue.X, primaries.blue.Y, primaries.blue.Z,
255                 },
256                 new float[]{primaries.white.X, primaries.white.Y, primaries.white.Z}
257         );
258     }
259 
getDisplayColorSpaceFromResources(Resources res)260     private ColorSpace.Rgb getDisplayColorSpaceFromResources(Resources res) {
261         final String[] displayPrimariesValues = res.getStringArray(
262                 R.array.config_displayWhiteBalanceDisplayPrimaries);
263         float[] displayRedGreenBlueXYZ =
264                 new float[NUM_DISPLAY_PRIMARIES_VALS - NUM_VALUES_PER_PRIMARY];
265         float[] displayWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
266 
267         for (int i = 0; i < displayRedGreenBlueXYZ.length; i++) {
268             displayRedGreenBlueXYZ[i] = Float.parseFloat(displayPrimariesValues[i]);
269         }
270 
271         for (int i = 0; i < displayWhiteXYZ.length; i++) {
272             displayWhiteXYZ[i] = Float.parseFloat(
273                     displayPrimariesValues[displayRedGreenBlueXYZ.length + i]);
274         }
275 
276         return makeRgbColorSpaceFromXYZ(displayRedGreenBlueXYZ, displayWhiteXYZ);
277     }
278 
isColorMatrixCoeffValid(float coeff)279     private boolean isColorMatrixCoeffValid(float coeff) {
280         if (Float.isNaN(coeff) || Float.isInfinite(coeff)) {
281             return false;
282         }
283 
284         return true;
285     }
286 
isColorMatrixValid(float[] matrix)287     private boolean isColorMatrixValid(float[] matrix) {
288         if (matrix == null || matrix.length != COLORSPACE_MATRIX_LENGTH) {
289             return false;
290         }
291 
292         for (int i = 0; i < matrix.length; i++) {
293             if (!isColorMatrixCoeffValid(matrix[i])) {
294                 return false;
295             }
296         }
297 
298         return true;
299     }
300 
301 }
302