Loading core/java/com/android/internal/graphics/ColorUtils.java +52 −5 Original line number Diff line number Diff line Loading @@ -105,6 +105,31 @@ public final class ColorUtils { return Math.max(luminance1, luminance2) / Math.min(luminance1, luminance2); } /** * Calculates the minimum alpha value which can be applied to {@code background} so that would * have a contrast value of at least {@code minContrastRatio} when alpha blended to * {@code foreground}. * * @param foreground the foreground color * @param background the background color, opacity will be ignored * @param minContrastRatio the minimum contrast ratio * @return the alpha value in the range 0-255, or -1 if no value could be calculated */ public static int calculateMinimumBackgroundAlpha(@ColorInt int foreground, @ColorInt int background, float minContrastRatio) { // Ignore initial alpha that the background might have since this is // what we're trying to calculate. background = setAlphaComponent(background, 255); final int leastContrastyColor = setAlphaComponent(foreground, 255); return binaryAlphaSearch(foreground, background, minContrastRatio, (fg, bg, alpha) -> { int testBackground = blendARGB(leastContrastyColor, bg, alpha/255f); // Float rounding might set this alpha to something other that 255, // raising an exception in calculateContrast. testBackground = setAlphaComponent(testBackground, 255); return calculateContrast(fg, testBackground); }); } /** * Calculates the minimum alpha value which can be applied to {@code foreground} so that would * have a contrast value of at least {@code minContrastRatio} when compared to Loading @@ -122,14 +147,33 @@ public final class ColorUtils { + Integer.toHexString(background)); } ContrastCalculator contrastCalculator = (fg, bg, alpha) -> { int testForeground = setAlphaComponent(fg, alpha); return calculateContrast(testForeground, bg); }; // First lets check that a fully opaque foreground has sufficient contrast int testForeground = setAlphaComponent(foreground, 255); double testRatio = calculateContrast(testForeground, background); double testRatio = contrastCalculator.calculateContrast(foreground, background, 255); if (testRatio < minContrastRatio) { // Fully opaque foreground does not have sufficient contrast, return error return -1; } foreground = setAlphaComponent(foreground, 255); return binaryAlphaSearch(foreground, background, minContrastRatio, contrastCalculator); } /** * Calculates the alpha value using binary search based on a given contrast evaluation function * and target contrast that needs to be satisfied. * * @param foreground the foreground color * @param background the opaque background color * @param minContrastRatio the minimum contrast ratio * @param calculator function that calculates contrast * @return the alpha value in the range 0-255, or -1 if no value could be calculated */ private static int binaryAlphaSearch(@ColorInt int foreground, @ColorInt int background, float minContrastRatio, ContrastCalculator calculator) { // Binary search to find a value with the minimum value which provides sufficient contrast int numIterations = 0; int minAlpha = 0; Loading @@ -139,9 +183,8 @@ public final class ColorUtils { (maxAlpha - minAlpha) > MIN_ALPHA_SEARCH_PRECISION) { final int testAlpha = (minAlpha + maxAlpha) / 2; testForeground = setAlphaComponent(foreground, testAlpha); testRatio = calculateContrast(testForeground, background); final double testRatio = calculator.calculateContrast(foreground, background, testAlpha); if (testRatio < minContrastRatio) { minAlpha = testAlpha; } else { Loading Loading @@ -615,4 +658,8 @@ public final class ColorUtils { return result; } private interface ContrastCalculator { double calculateContrast(int foreground, int background, int alpha); } } No newline at end of file packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +17 −2 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import android.view.animation.PathInterpolator; import com.android.internal.colorextraction.ColorExtractor; import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.internal.colorextraction.ColorExtractor.OnColorsChangedListener; import com.android.internal.graphics.ColorUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dependency; import com.android.systemui.R; Loading Loading @@ -88,6 +89,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, private boolean mNeedsDrawableColorUpdate; protected float mScrimBehindAlpha; protected float mScrimBehindAlphaResValue; protected float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD; protected float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING; Loading Loading @@ -142,7 +144,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mUnlockMethodCache = UnlockMethodCache.getInstance(context); mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context); mLightBarController = lightBarController; mScrimBehindAlpha = context.getResources().getFloat(R.dimen.scrim_behind_alpha); mScrimBehindAlphaResValue = context.getResources().getFloat(R.dimen.scrim_behind_alpha); // Scrim alpha is initially set to the value on the resource but might be changed // to make sure that text on top of it is legible. mScrimBehindAlpha = mScrimBehindAlphaResValue; mColorExtractor = Dependency.get(SysuiColorExtractor.class); mColorExtractor.addOnColorsChangedListener(this); Loading Loading @@ -342,20 +347,30 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } protected void updateScrims() { // Make sure we have the right gradients // Make sure we have the right gradients and their opacities will satisfy GAR. if (mNeedsDrawableColorUpdate) { mNeedsDrawableColorUpdate = false; final GradientColors currentScrimColors; if (mKeyguardShowing) { // Always animate color changes if we're seeing the keyguard mScrimInFront.setColors(mLockColors, true /* animated */); mScrimBehind.setColors(mLockColors, true /* animated */); currentScrimColors = mLockColors; } else { // Only animate scrim color if the scrim view is actually visible boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0; boolean animateScrimBehind = mScrimBehind.getViewAlpha() != 0; mScrimInFront.setColors(mSystemColors, animateScrimInFront); mScrimBehind.setColors(mSystemColors, animateScrimBehind); currentScrimColors = mSystemColors; } // Calculate minimum scrim opacity for white or black text. int textColor = currentScrimColors.supportsDarkText() ? Color.BLACK : Color.WHITE; int mainColor = currentScrimColors.getMainColor(); float minOpacity = ColorUtils.calculateMinimumBackgroundAlpha(textColor, mainColor, 4.5f /* minimumContrast */) / 255f; mScrimBehindAlpha = Math.max(mScrimBehindAlphaResValue, minOpacity); mLightBarController.setScrimColor(mScrimInFront.getColors()); } Loading tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java 0 → 100644 +41 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.internal.graphics; import android.graphics.Color; import android.support.test.filters.SmallTest; import org.junit.Test; import static org.junit.Assert.assertTrue; @SmallTest public class ColorUtilsTest { @Test public void calculateMinimumBackgroundAlpha_satisfiestContrast() { int alpha = ColorUtils.calculateMinimumBackgroundAlpha(Color.WHITE, Color.BLACK, 4.5f); assertTrue("Alpha doesn't need to be 255 to satisfy contrast", alpha < 255); int worstCase = ColorUtils.blendARGB(Color.WHITE, Color.BLACK, alpha/255f); worstCase = ColorUtils.setAlphaComponent(worstCase, 255); double contrast = ColorUtils.calculateContrast(Color.WHITE, worstCase); assertTrue("Blended color should satisfy contrast", contrast >= 4.5); } } Loading
core/java/com/android/internal/graphics/ColorUtils.java +52 −5 Original line number Diff line number Diff line Loading @@ -105,6 +105,31 @@ public final class ColorUtils { return Math.max(luminance1, luminance2) / Math.min(luminance1, luminance2); } /** * Calculates the minimum alpha value which can be applied to {@code background} so that would * have a contrast value of at least {@code minContrastRatio} when alpha blended to * {@code foreground}. * * @param foreground the foreground color * @param background the background color, opacity will be ignored * @param minContrastRatio the minimum contrast ratio * @return the alpha value in the range 0-255, or -1 if no value could be calculated */ public static int calculateMinimumBackgroundAlpha(@ColorInt int foreground, @ColorInt int background, float minContrastRatio) { // Ignore initial alpha that the background might have since this is // what we're trying to calculate. background = setAlphaComponent(background, 255); final int leastContrastyColor = setAlphaComponent(foreground, 255); return binaryAlphaSearch(foreground, background, minContrastRatio, (fg, bg, alpha) -> { int testBackground = blendARGB(leastContrastyColor, bg, alpha/255f); // Float rounding might set this alpha to something other that 255, // raising an exception in calculateContrast. testBackground = setAlphaComponent(testBackground, 255); return calculateContrast(fg, testBackground); }); } /** * Calculates the minimum alpha value which can be applied to {@code foreground} so that would * have a contrast value of at least {@code minContrastRatio} when compared to Loading @@ -122,14 +147,33 @@ public final class ColorUtils { + Integer.toHexString(background)); } ContrastCalculator contrastCalculator = (fg, bg, alpha) -> { int testForeground = setAlphaComponent(fg, alpha); return calculateContrast(testForeground, bg); }; // First lets check that a fully opaque foreground has sufficient contrast int testForeground = setAlphaComponent(foreground, 255); double testRatio = calculateContrast(testForeground, background); double testRatio = contrastCalculator.calculateContrast(foreground, background, 255); if (testRatio < minContrastRatio) { // Fully opaque foreground does not have sufficient contrast, return error return -1; } foreground = setAlphaComponent(foreground, 255); return binaryAlphaSearch(foreground, background, minContrastRatio, contrastCalculator); } /** * Calculates the alpha value using binary search based on a given contrast evaluation function * and target contrast that needs to be satisfied. * * @param foreground the foreground color * @param background the opaque background color * @param minContrastRatio the minimum contrast ratio * @param calculator function that calculates contrast * @return the alpha value in the range 0-255, or -1 if no value could be calculated */ private static int binaryAlphaSearch(@ColorInt int foreground, @ColorInt int background, float minContrastRatio, ContrastCalculator calculator) { // Binary search to find a value with the minimum value which provides sufficient contrast int numIterations = 0; int minAlpha = 0; Loading @@ -139,9 +183,8 @@ public final class ColorUtils { (maxAlpha - minAlpha) > MIN_ALPHA_SEARCH_PRECISION) { final int testAlpha = (minAlpha + maxAlpha) / 2; testForeground = setAlphaComponent(foreground, testAlpha); testRatio = calculateContrast(testForeground, background); final double testRatio = calculator.calculateContrast(foreground, background, testAlpha); if (testRatio < minContrastRatio) { minAlpha = testAlpha; } else { Loading Loading @@ -615,4 +658,8 @@ public final class ColorUtils { return result; } private interface ContrastCalculator { double calculateContrast(int foreground, int background, int alpha); } } No newline at end of file
packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +17 −2 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import android.view.animation.PathInterpolator; import com.android.internal.colorextraction.ColorExtractor; import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.internal.colorextraction.ColorExtractor.OnColorsChangedListener; import com.android.internal.graphics.ColorUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dependency; import com.android.systemui.R; Loading Loading @@ -88,6 +89,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, private boolean mNeedsDrawableColorUpdate; protected float mScrimBehindAlpha; protected float mScrimBehindAlphaResValue; protected float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD; protected float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING; Loading Loading @@ -142,7 +144,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mUnlockMethodCache = UnlockMethodCache.getInstance(context); mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context); mLightBarController = lightBarController; mScrimBehindAlpha = context.getResources().getFloat(R.dimen.scrim_behind_alpha); mScrimBehindAlphaResValue = context.getResources().getFloat(R.dimen.scrim_behind_alpha); // Scrim alpha is initially set to the value on the resource but might be changed // to make sure that text on top of it is legible. mScrimBehindAlpha = mScrimBehindAlphaResValue; mColorExtractor = Dependency.get(SysuiColorExtractor.class); mColorExtractor.addOnColorsChangedListener(this); Loading Loading @@ -342,20 +347,30 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } protected void updateScrims() { // Make sure we have the right gradients // Make sure we have the right gradients and their opacities will satisfy GAR. if (mNeedsDrawableColorUpdate) { mNeedsDrawableColorUpdate = false; final GradientColors currentScrimColors; if (mKeyguardShowing) { // Always animate color changes if we're seeing the keyguard mScrimInFront.setColors(mLockColors, true /* animated */); mScrimBehind.setColors(mLockColors, true /* animated */); currentScrimColors = mLockColors; } else { // Only animate scrim color if the scrim view is actually visible boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0; boolean animateScrimBehind = mScrimBehind.getViewAlpha() != 0; mScrimInFront.setColors(mSystemColors, animateScrimInFront); mScrimBehind.setColors(mSystemColors, animateScrimBehind); currentScrimColors = mSystemColors; } // Calculate minimum scrim opacity for white or black text. int textColor = currentScrimColors.supportsDarkText() ? Color.BLACK : Color.WHITE; int mainColor = currentScrimColors.getMainColor(); float minOpacity = ColorUtils.calculateMinimumBackgroundAlpha(textColor, mainColor, 4.5f /* minimumContrast */) / 255f; mScrimBehindAlpha = Math.max(mScrimBehindAlphaResValue, minOpacity); mLightBarController.setScrimColor(mScrimInFront.getColors()); } Loading
tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java 0 → 100644 +41 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.internal.graphics; import android.graphics.Color; import android.support.test.filters.SmallTest; import org.junit.Test; import static org.junit.Assert.assertTrue; @SmallTest public class ColorUtilsTest { @Test public void calculateMinimumBackgroundAlpha_satisfiestContrast() { int alpha = ColorUtils.calculateMinimumBackgroundAlpha(Color.WHITE, Color.BLACK, 4.5f); assertTrue("Alpha doesn't need to be 255 to satisfy contrast", alpha < 255); int worstCase = ColorUtils.blendARGB(Color.WHITE, Color.BLACK, alpha/255f); worstCase = ColorUtils.setAlphaComponent(worstCase, 255); double contrast = ColorUtils.calculateContrast(Color.WHITE, worstCase); assertTrue("Blended color should satisfy contrast", contrast >= 4.5); } }