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