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 synchronized (mLock) { 153 mCurrentColorTemperature = cct; 154 155 // Adapt the display's nominal white point to match the requested CCT value 156 mCurrentColorTemperatureXYZ = ColorSpace.cctToXyz(cct); 157 158 mChromaticAdaptationMatrix = 159 ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD, 160 mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ); 161 162 // Convert the adaptation matrix to RGB space 163 float[] result = ColorSpace.mul3x3(mChromaticAdaptationMatrix, 164 mDisplayColorSpaceRGB.getTransform()); 165 result = ColorSpace.mul3x3(mDisplayColorSpaceRGB.getInverseTransform(), result); 166 167 // Normalize the transform matrix to peak white value in RGB space 168 final float adaptedMaxR = result[0] + result[3] + result[6]; 169 final float adaptedMaxG = result[1] + result[4] + result[7]; 170 final float adaptedMaxB = result[2] + result[5] + result[8]; 171 final float denum = Math.max(Math.max(adaptedMaxR, adaptedMaxG), adaptedMaxB); 172 173 Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0); 174 for (int i = 0; i < result.length; i++) { 175 result[i] /= denum; 176 if (!isColorMatrixCoeffValid(result[i])) { 177 Slog.e(ColorDisplayService.TAG, "Invalid DWB color matrix"); 178 return; 179 } 180 } 181 182 java.lang.System.arraycopy(result, 0, mMatrixDisplayWhiteBalance, 0, 3); 183 java.lang.System.arraycopy(result, 3, mMatrixDisplayWhiteBalance, 4, 3); 184 java.lang.System.arraycopy(result, 6, mMatrixDisplayWhiteBalance, 8, 3); 185 } 186 187 Slog.d(ColorDisplayService.TAG, "setDisplayWhiteBalanceTemperatureMatrix: cct = " + cct 188 + " matrix = " + matrixToString(mMatrixDisplayWhiteBalance, 16)); 189 } 190 191 @Override getLevel()192 public int getLevel() { 193 return LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE; 194 } 195 196 @Override isAvailable(Context context)197 public boolean isAvailable(Context context) { 198 if (mIsAvailable == null) { 199 mIsAvailable = ColorDisplayManager.isDisplayWhiteBalanceAvailable(context); 200 } 201 return mIsAvailable; 202 } 203 204 @Override dump(PrintWriter pw)205 public void dump(PrintWriter pw) { 206 synchronized (mLock) { 207 pw.println(" mSetUp = " + mSetUp); 208 if (!mSetUp) { 209 return; 210 } 211 212 pw.println(" mTemperatureMin = " + mTemperatureMin); 213 pw.println(" mTemperatureMax = " + mTemperatureMax); 214 pw.println(" mTemperatureDefault = " + mTemperatureDefault); 215 pw.println(" mCurrentColorTemperature = " + mCurrentColorTemperature); 216 pw.println(" mCurrentColorTemperatureXYZ = " 217 + matrixToString(mCurrentColorTemperatureXYZ, 3)); 218 pw.println(" mDisplayColorSpaceRGB RGB-to-XYZ = " 219 + matrixToString(mDisplayColorSpaceRGB.getTransform(), 3)); 220 pw.println(" mChromaticAdaptationMatrix = " 221 + matrixToString(mChromaticAdaptationMatrix, 3)); 222 pw.println(" mDisplayColorSpaceRGB XYZ-to-RGB = " 223 + matrixToString(mDisplayColorSpaceRGB.getInverseTransform(), 3)); 224 pw.println(" mMatrixDisplayWhiteBalance = " 225 + matrixToString(mMatrixDisplayWhiteBalance, 4)); 226 } 227 } 228 makeRgbColorSpaceFromXYZ(float[] redGreenBlueXYZ, float[] whiteXYZ)229 private ColorSpace.Rgb makeRgbColorSpaceFromXYZ(float[] redGreenBlueXYZ, float[] whiteXYZ) { 230 return new ColorSpace.Rgb( 231 "Display Color Space", 232 redGreenBlueXYZ, 233 whiteXYZ, 234 2.2f // gamma, unused for display white balance 235 ); 236 } 237 getDisplayColorSpaceFromSurfaceControl()238 private ColorSpace.Rgb getDisplayColorSpaceFromSurfaceControl() { 239 final IBinder displayToken = SurfaceControl.getInternalDisplayToken(); 240 if (displayToken == null) { 241 return null; 242 } 243 244 DisplayPrimaries primaries = SurfaceControl.getDisplayNativePrimaries(displayToken); 245 if (primaries == null || primaries.red == null || primaries.green == null 246 || primaries.blue == null || primaries.white == null) { 247 return null; 248 } 249 250 return makeRgbColorSpaceFromXYZ( 251 new float[]{ 252 primaries.red.X, primaries.red.Y, primaries.red.Z, 253 primaries.green.X, primaries.green.Y, primaries.green.Z, 254 primaries.blue.X, primaries.blue.Y, primaries.blue.Z, 255 }, 256 new float[]{primaries.white.X, primaries.white.Y, primaries.white.Z} 257 ); 258 } 259 getDisplayColorSpaceFromResources(Resources res)260 private ColorSpace.Rgb getDisplayColorSpaceFromResources(Resources res) { 261 final String[] displayPrimariesValues = res.getStringArray( 262 R.array.config_displayWhiteBalanceDisplayPrimaries); 263 float[] displayRedGreenBlueXYZ = 264 new float[NUM_DISPLAY_PRIMARIES_VALS - NUM_VALUES_PER_PRIMARY]; 265 float[] displayWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY]; 266 267 for (int i = 0; i < displayRedGreenBlueXYZ.length; i++) { 268 displayRedGreenBlueXYZ[i] = Float.parseFloat(displayPrimariesValues[i]); 269 } 270 271 for (int i = 0; i < displayWhiteXYZ.length; i++) { 272 displayWhiteXYZ[i] = Float.parseFloat( 273 displayPrimariesValues[displayRedGreenBlueXYZ.length + i]); 274 } 275 276 return makeRgbColorSpaceFromXYZ(displayRedGreenBlueXYZ, displayWhiteXYZ); 277 } 278 isColorMatrixCoeffValid(float coeff)279 private boolean isColorMatrixCoeffValid(float coeff) { 280 if (Float.isNaN(coeff) || Float.isInfinite(coeff)) { 281 return false; 282 } 283 284 return true; 285 } 286 isColorMatrixValid(float[] matrix)287 private boolean isColorMatrixValid(float[] matrix) { 288 if (matrix == null || matrix.length != COLORSPACE_MATRIX_LENGTH) { 289 return false; 290 } 291 292 for (int i = 0; i < matrix.length; i++) { 293 if (!isColorMatrixCoeffValid(matrix[i])) { 294 return false; 295 } 296 } 297 298 return true; 299 } 300 301 } 302