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