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