Christine Franks | 6caba0f | 2019-03-07 17:48:25 -0800 | [diff] [blame] | 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; |
Daniel Solomon | bd3d1d2 | 2019-04-15 20:36:02 -0700 | [diff] [blame] | 35 | import java.lang.System; |
Christine Franks | 6caba0f | 2019-03-07 17:48:25 -0800 | [diff] [blame] | 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; |
Daniel Solomon | bd3d1d2 | 2019-04-15 20:36:02 -0700 | [diff] [blame] | 43 | private static final int COLORSPACE_MATRIX_LENGTH = 9; |
Christine Franks | 6caba0f | 2019-03-07 17:48:25 -0800 | [diff] [blame] | 44 | |
| 45 | private final Object mLock = new Object(); |
| 46 | @VisibleForTesting |
| 47 | int mTemperatureMin; |
| 48 | @VisibleForTesting |
| 49 | int mTemperatureMax; |
| 50 | private int mTemperatureDefault; |
Daniel Solomon | bd3d1d2 | 2019-04-15 20:36:02 -0700 | [diff] [blame] | 51 | @VisibleForTesting |
| 52 | float[] mDisplayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY]; |
Christine Franks | 6caba0f | 2019-03-07 17:48:25 -0800 | [diff] [blame] | 53 | @VisibleForTesting |
| 54 | ColorSpace.Rgb mDisplayColorSpaceRGB; |
| 55 | private float[] mChromaticAdaptationMatrix; |
| 56 | @VisibleForTesting |
| 57 | int mCurrentColorTemperature; |
| 58 | private float[] mCurrentColorTemperatureXYZ; |
Daniel Solomon | bd3d1d2 | 2019-04-15 20:36:02 -0700 | [diff] [blame] | 59 | @VisibleForTesting |
| 60 | boolean mSetUp = false; |
Christine Franks | 6caba0f | 2019-03-07 17:48:25 -0800 | [diff] [blame] | 61 | private float[] mMatrixDisplayWhiteBalance = new float[16]; |
| 62 | private Boolean mIsAvailable; |
| 63 | |
| 64 | @Override |
| 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 | |
Daniel Solomon | bd3d1d2 | 2019-04-15 20:36:02 -0700 | [diff] [blame] | 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 | |
Christine Franks | 6caba0f | 2019-03-07 17:48:25 -0800 | [diff] [blame] | 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 |
| 129 | public float[] getMatrix() { |
| 130 | return mSetUp && isActivated() ? mMatrixDisplayWhiteBalance |
| 131 | : ColorDisplayService.MATRIX_IDENTITY; |
| 132 | } |
| 133 | |
| 134 | @Override |
| 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 | |
Christine Franks | 6caba0f | 2019-03-07 17:48:25 -0800 | [diff] [blame] | 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); |
Christine Franks | 6caba0f | 2019-03-07 17:48:25 -0800 | [diff] [blame] | 172 | |
| 173 | Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0); |
Daniel Solomon | bd3d1d2 | 2019-04-15 20:36:02 -0700 | [diff] [blame] | 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 | |
Christine Franks | 6caba0f | 2019-03-07 17:48:25 -0800 | [diff] [blame] | 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 | } |
Anthony Han | 00a490b | 2019-08-26 14:00:28 -0700 | [diff] [blame] | 186 | |
| 187 | Slog.d(ColorDisplayService.TAG, "setDisplayWhiteBalanceTemperatureMatrix: cct = " + cct |
| 188 | + " matrix = " + matrixToString(mMatrixDisplayWhiteBalance, 16)); |
Christine Franks | 6caba0f | 2019-03-07 17:48:25 -0800 | [diff] [blame] | 189 | } |
| 190 | |
| 191 | @Override |
| 192 | public int getLevel() { |
| 193 | return LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE; |
| 194 | } |
| 195 | |
| 196 | @Override |
| 197 | public boolean isAvailable(Context context) { |
| 198 | if (mIsAvailable == null) { |
| 199 | mIsAvailable = ColorDisplayManager.isDisplayWhiteBalanceAvailable(context); |
| 200 | } |
| 201 | return mIsAvailable; |
| 202 | } |
| 203 | |
| 204 | @Override |
| 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 | |
Christine Franks | 6caba0f | 2019-03-07 17:48:25 -0800 | [diff] [blame] | 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 | |
| 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 | |
| 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 | } |
Daniel Solomon | bd3d1d2 | 2019-04-15 20:36:02 -0700 | [diff] [blame] | 278 | |
| 279 | private boolean isColorMatrixCoeffValid(float coeff) { |
| 280 | if (Float.isNaN(coeff) || Float.isInfinite(coeff)) { |
| 281 | return false; |
| 282 | } |
| 283 | |
| 284 | return true; |
| 285 | } |
| 286 | |
| 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 | |
Christine Franks | 6caba0f | 2019-03-07 17:48:25 -0800 | [diff] [blame] | 301 | } |