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