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.content.Context; 22 import android.content.res.Resources; 23 import android.graphics.ColorSpace; 24 import android.hardware.display.ColorDisplayManager; 25 import android.opengl.Matrix; 26 import android.os.IBinder; 27 import android.util.Slog; 28 import android.view.SurfaceControl; 29 import android.view.SurfaceControl.DisplayPrimaries; 30 31 import com.android.internal.R; 32 import com.android.internal.annotations.VisibleForTesting; 33 34 import java.io.PrintWriter; 35 import java.lang.System; 36 37 final 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; 43 private static final int COLORSPACE_MATRIX_LENGTH = 9; 44 45 private final Object mLock = new Object(); 46 @VisibleForTesting 47 int mTemperatureMin; 48 @VisibleForTesting 49 int mTemperatureMax; 50 private int mTemperatureDefault; 51 @VisibleForTesting 52 float[] mDisplayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY]; 53 @VisibleForTesting 54 ColorSpace.Rgb mDisplayColorSpaceRGB; 55 private float[] mChromaticAdaptationMatrix; 56 @VisibleForTesting 57 int mCurrentColorTemperature; 58 private float[] mCurrentColorTemperatureXYZ; 59 @VisibleForTesting 60 boolean mSetUp = false; 61 private float[] mMatrixDisplayWhiteBalance = new float[16]; 62 private Boolean mIsAvailable; 63 64 @Override setUp(Context context, boolean needsLinear)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 80 // 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 90 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 getMatrix()129 public float[] getMatrix() { 130 return mSetUp && isActivated() ? mMatrixDisplayWhiteBalance 131 : ColorDisplayService.MATRIX_IDENTITY; 132 } 133 134 @Override setMatrix(int cct)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); 174 175 Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0); 176 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 184 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 getLevel()191 public int getLevel() { 192 return LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE; 193 } 194 195 @Override isAvailable(Context context)196 public boolean isAvailable(Context context) { 197 if (mIsAvailable == null) { 198 mIsAvailable = ColorDisplayManager.isDisplayWhiteBalanceAvailable(context); 199 } 200 return mIsAvailable; 201 } 202 203 @Override dump(PrintWriter pw)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 */ matrixToString(float[] matrix, int columns)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 makeRgbColorSpaceFromXYZ(float[] redGreenBlueXYZ, float[] whiteXYZ)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 getDisplayColorSpaceFromSurfaceControl()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 getDisplayColorSpaceFromResources(Resources res)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 } 299 isColorMatrixCoeffValid(float coeff)300 private boolean isColorMatrixCoeffValid(float coeff) { 301 if (Float.isNaN(coeff) || Float.isInfinite(coeff)) { 302 return false; 303 } 304 305 return true; 306 } 307 isColorMatrixValid(float[] matrix)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 322 } 323