blob: d2c6cd9f1007462df3a81e60a6fa98427371ff91 [file] [log] [blame]
Christine Franks6caba0f2019-03-07 17:48:25 -08001/*
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
17package com.android.server.display.color;
18
19import static com.android.server.display.color.DisplayTransformManager.LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
20
21import android.content.Context;
22import android.content.res.Resources;
23import android.graphics.ColorSpace;
24import android.hardware.display.ColorDisplayManager;
25import android.opengl.Matrix;
26import android.os.IBinder;
27import android.util.Slog;
28import android.view.SurfaceControl;
29import android.view.SurfaceControl.DisplayPrimaries;
30
31import com.android.internal.R;
32import com.android.internal.annotations.VisibleForTesting;
33
34import java.io.PrintWriter;
Daniel Solomonbd3d1d22019-04-15 20:36:02 -070035import java.lang.System;
Christine Franks6caba0f2019-03-07 17:48:25 -080036
37final 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;
Daniel Solomonbd3d1d22019-04-15 20:36:02 -070043 private static final int COLORSPACE_MATRIX_LENGTH = 9;
Christine Franks6caba0f2019-03-07 17:48:25 -080044
45 private final Object mLock = new Object();
46 @VisibleForTesting
47 int mTemperatureMin;
48 @VisibleForTesting
49 int mTemperatureMax;
50 private int mTemperatureDefault;
Daniel Solomonbd3d1d22019-04-15 20:36:02 -070051 @VisibleForTesting
52 float[] mDisplayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
Christine Franks6caba0f2019-03-07 17:48:25 -080053 @VisibleForTesting
54 ColorSpace.Rgb mDisplayColorSpaceRGB;
55 private float[] mChromaticAdaptationMatrix;
56 @VisibleForTesting
57 int mCurrentColorTemperature;
58 private float[] mCurrentColorTemperatureXYZ;
Daniel Solomonbd3d1d22019-04-15 20:36:02 -070059 @VisibleForTesting
60 boolean mSetUp = false;
Christine Franks6caba0f2019-03-07 17:48:25 -080061 private float[] mMatrixDisplayWhiteBalance = new float[16];
62 private Boolean mIsAvailable;
63
64 @Override
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
Daniel Solomonbd3d1d22019-04-15 20:36:02 -070080 // 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
Christine Franks6caba0f2019-03-07 17:48:25 -080090 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
129 public float[] getMatrix() {
130 return mSetUp && isActivated() ? mMatrixDisplayWhiteBalance
131 : ColorDisplayService.MATRIX_IDENTITY;
132 }
133
134 @Override
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);
Christine Franks6caba0f2019-03-07 17:48:25 -0800174
175 Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0);
Daniel Solomonbd3d1d22019-04-15 20:36:02 -0700176 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
Christine Franks6caba0f2019-03-07 17:48:25 -0800184 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
191 public int getLevel() {
192 return LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
193 }
194
195 @Override
196 public boolean isAvailable(Context context) {
197 if (mIsAvailable == null) {
198 mIsAvailable = ColorDisplayManager.isDisplayWhiteBalanceAvailable(context);
199 }
200 return mIsAvailable;
201 }
202
203 @Override
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 */
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
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
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
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 }
Daniel Solomonbd3d1d22019-04-15 20:36:02 -0700299
300 private boolean isColorMatrixCoeffValid(float coeff) {
301 if (Float.isNaN(coeff) || Float.isInfinite(coeff)) {
302 return false;
303 }
304
305 return true;
306 }
307
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
Christine Franks6caba0f2019-03-07 17:48:25 -0800322}