• 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.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         Slog.d(ColorDisplayService.TAG, "setDisplayWhiteBalanceTemperatureMatrix: cct = " + cct);
153 
154         synchronized (mLock) {
155             mCurrentColorTemperature = cct;
156 
157             // Adapt the display's nominal white point to match the requested CCT value
158             mCurrentColorTemperatureXYZ = ColorSpace.cctToXyz(cct);
159 
160             mChromaticAdaptationMatrix =
161                     ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD,
162                             mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ);
163 
164             // Convert the adaptation matrix to RGB space
165             float[] result = ColorSpace.mul3x3(mChromaticAdaptationMatrix,
166                     mDisplayColorSpaceRGB.getTransform());
167             result = ColorSpace.mul3x3(mDisplayColorSpaceRGB.getInverseTransform(), result);
168 
169             // Normalize the transform matrix to peak white value in RGB space
170             final float adaptedMaxR = result[0] + result[3] + result[6];
171             final float adaptedMaxG = result[1] + result[4] + result[7];
172             final float adaptedMaxB = result[2] + result[5] + result[8];
173             final float denum = Math.max(Math.max(adaptedMaxR, adaptedMaxG), adaptedMaxB);
174 
175             Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0);
176             for (int i = 0; i < result.length; i++) {
177                 result[i] /= denum;
178                 if (!isColorMatrixCoeffValid(result[i])) {
179                     Slog.e(ColorDisplayService.TAG, "Invalid DWB color matrix");
180                     return;
181                 }
182             }
183 
184             java.lang.System.arraycopy(result, 0, mMatrixDisplayWhiteBalance, 0, 3);
185             java.lang.System.arraycopy(result, 3, mMatrixDisplayWhiteBalance, 4, 3);
186             java.lang.System.arraycopy(result, 6, mMatrixDisplayWhiteBalance, 8, 3);
187         }
188     }
189 
190     @Override
getLevel()191     public int getLevel() {
192         return LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
193     }
194 
195     @Override
isAvailable(Context context)196     public boolean isAvailable(Context context) {
197         if (mIsAvailable == null) {
198             mIsAvailable = ColorDisplayManager.isDisplayWhiteBalanceAvailable(context);
199         }
200         return mIsAvailable;
201     }
202 
203     @Override
dump(PrintWriter pw)204     public void dump(PrintWriter pw) {
205         synchronized (mLock) {
206             pw.println("    mSetUp = " + mSetUp);
207             if (!mSetUp) {
208                 return;
209             }
210 
211             pw.println("    mTemperatureMin = " + mTemperatureMin);
212             pw.println("    mTemperatureMax = " + mTemperatureMax);
213             pw.println("    mTemperatureDefault = " + mTemperatureDefault);
214             pw.println("    mCurrentColorTemperature = " + mCurrentColorTemperature);
215             pw.println("    mCurrentColorTemperatureXYZ = "
216                     + matrixToString(mCurrentColorTemperatureXYZ, 3));
217             pw.println("    mDisplayColorSpaceRGB RGB-to-XYZ = "
218                     + matrixToString(mDisplayColorSpaceRGB.getTransform(), 3));
219             pw.println("    mChromaticAdaptationMatrix = "
220                     + matrixToString(mChromaticAdaptationMatrix, 3));
221             pw.println("    mDisplayColorSpaceRGB XYZ-to-RGB = "
222                     + matrixToString(mDisplayColorSpaceRGB.getInverseTransform(), 3));
223             pw.println("    mMatrixDisplayWhiteBalance = "
224                     + matrixToString(mMatrixDisplayWhiteBalance, 4));
225         }
226     }
227 
228     /**
229      * Format a given matrix into a string.
230      *
231      * @param matrix the matrix to format
232      * @param columns number of columns in the matrix
233      */
matrixToString(float[] matrix, int columns)234     private String matrixToString(float[] matrix, int columns) {
235         if (matrix == null || columns <= 0) {
236             Slog.e(ColorDisplayService.TAG, "Invalid arguments when formatting matrix to string");
237             return "";
238         }
239 
240         final StringBuilder sb = new StringBuilder("");
241         for (int i = 0; i < matrix.length; i++) {
242             if (i % columns == 0) {
243                 sb.append("\n      ");
244             }
245             sb.append(String.format("%9.6f", matrix[i]));
246         }
247         return sb.toString();
248     }
249 
makeRgbColorSpaceFromXYZ(float[] redGreenBlueXYZ, float[] whiteXYZ)250     private ColorSpace.Rgb makeRgbColorSpaceFromXYZ(float[] redGreenBlueXYZ, float[] whiteXYZ) {
251         return new ColorSpace.Rgb(
252                 "Display Color Space",
253                 redGreenBlueXYZ,
254                 whiteXYZ,
255                 2.2f // gamma, unused for display white balance
256         );
257     }
258 
getDisplayColorSpaceFromSurfaceControl()259     private ColorSpace.Rgb getDisplayColorSpaceFromSurfaceControl() {
260         final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
261         if (displayToken == null) {
262             return null;
263         }
264 
265         DisplayPrimaries primaries = SurfaceControl.getDisplayNativePrimaries(displayToken);
266         if (primaries == null || primaries.red == null || primaries.green == null
267                 || primaries.blue == null || primaries.white == null) {
268             return null;
269         }
270 
271         return makeRgbColorSpaceFromXYZ(
272                 new float[]{
273                         primaries.red.X, primaries.red.Y, primaries.red.Z,
274                         primaries.green.X, primaries.green.Y, primaries.green.Z,
275                         primaries.blue.X, primaries.blue.Y, primaries.blue.Z,
276                 },
277                 new float[]{primaries.white.X, primaries.white.Y, primaries.white.Z}
278         );
279     }
280 
getDisplayColorSpaceFromResources(Resources res)281     private ColorSpace.Rgb getDisplayColorSpaceFromResources(Resources res) {
282         final String[] displayPrimariesValues = res.getStringArray(
283                 R.array.config_displayWhiteBalanceDisplayPrimaries);
284         float[] displayRedGreenBlueXYZ =
285                 new float[NUM_DISPLAY_PRIMARIES_VALS - NUM_VALUES_PER_PRIMARY];
286         float[] displayWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
287 
288         for (int i = 0; i < displayRedGreenBlueXYZ.length; i++) {
289             displayRedGreenBlueXYZ[i] = Float.parseFloat(displayPrimariesValues[i]);
290         }
291 
292         for (int i = 0; i < displayWhiteXYZ.length; i++) {
293             displayWhiteXYZ[i] = Float.parseFloat(
294                     displayPrimariesValues[displayRedGreenBlueXYZ.length + i]);
295         }
296 
297         return makeRgbColorSpaceFromXYZ(displayRedGreenBlueXYZ, displayWhiteXYZ);
298     }
299 
isColorMatrixCoeffValid(float coeff)300     private boolean isColorMatrixCoeffValid(float coeff) {
301         if (Float.isNaN(coeff) || Float.isInfinite(coeff)) {
302             return false;
303         }
304 
305         return true;
306     }
307 
isColorMatrixValid(float[] matrix)308     private boolean isColorMatrixValid(float[] matrix) {
309         if (matrix == null || matrix.length != COLORSPACE_MATRIX_LENGTH) {
310             return false;
311         }
312 
313         for (int i = 0; i < matrix.length; i++) {
314             if (!isColorMatrixCoeffValid(matrix[i])) {
315                 return false;
316             }
317         }
318 
319         return true;
320     }
321 
322 }
323