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