diff options
3 files changed, 110 insertions, 7 deletions
diff --git a/core/java/com/android/internal/graphics/ColorUtils.java b/core/java/com/android/internal/graphics/ColorUtils.java index 6c1efa43ac86..8b2a2dc38e95 100644 --- a/core/java/com/android/internal/graphics/ColorUtils.java +++ b/core/java/com/android/internal/graphics/ColorUtils.java @@ -106,6 +106,31 @@ public final class ColorUtils { } /** + * 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 * {@code background}. @@ -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; @@ -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 { @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index eabf07bcbbd6..5b2b50bfe2f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -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; @@ -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; @@ -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); @@ -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()); } diff --git a/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java b/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java new file mode 100644 index 000000000000..73df9a09ea75 --- /dev/null +++ b/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java @@ -0,0 +1,41 @@ +/* + * 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); + + } +} |